ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从JDBC到Servlet:JSP网上招标系统源码实战与避坑指南

从JDBC到Servlet:JSP网上招标系统源码实战与避坑指南 简介一套基于 JSPServletJDBC 的网上招标系统面向需要毕业设计参考或 Java Web 开发入门的读者定位为完整可运行的课程设计/毕设项目。系统核心是会员发布任务与接收任务注册用户端支持查看网站流程与新闻公告、发布留言、管理已发布信息、发布威客在线信息以及查看成交记录管理员端支持编辑系统用户、管理网站留言和数据备份形成了一个较为完整的招标业务闭环。数据库采用 SQL Server开发工具为 MyEclipse整体代码按 Servlet、JDBC、JSP 分层组织便于对照学习 Web 开发中的请求处理、数据库交互与页面展示。压缩包大小约 4.7MB内含项目源码及配套配置文件可在 MyEclipse 中导入并部署运行。目前已有 5305 人学习浏览适合希望通过真实案例理解招标业务逻辑、掌握 JSP 与 JDBC 整合技巧的中初级开发者。1. jsp网上招标系统一个老项目为什么还值得拆开看jsp网上招标系统说白了就是一套“发任务—接任务—成交记录”全流程的威客平台注册用户可以发布威客在线信息、查看自己已发布的内容、给网站留言、查看成交记录管理员在后台维护用户信息、删除留言、做数据备份。技术栈非常有年代感——Servlet 做控制层JDBC 直接连 SQL ServerJSP 渲染页面开发工具用 MyEclipse。它适合两类人一是课程设计或毕业设计需要交 JSP 项目的在校生二是想拿老代码练重构的 Java 工程师。我是后者拿到这套系统先不急着跑而是把表结构和代码骨架拆开看当年的 JDBC 项目是怎么组织、又在哪些环节埋了坑。下面按数据模型、环境搭建、代码实现、避坑排查四个部分把拆解过程写透。2. 业务建模与表结构发标、接单、留言的数据怎么落地任何 JSP 老项目第一件事都不是打开 JSP 页面而是把业务流翻译成数据流。这套系统虽然功能不多但角色和状态关系很清晰一边是注册用户发任务、接任务、留言一边是管理员管用户、管留言、备份数据。先把业务模型立住后面写 DAO 和 Servlet 才不迷糊。2.1 先理业务谁是发布者谁接收状态怎么流转用户端和管理端的权限是分开的。注册用户登录后能看到网站流程和新闻公告能发布任务项目里叫“威客在线信息”能查看自己已发布的任务也能浏览和接收别人的任务成交后形成记录。管理员侧的功能点则更偏后台维护编辑用户信息、删除留言、做数据备份。两边合起来覆盖了一个最小可用的在线招标闭环。角色主要能力对应模块注册用户查公告、发任务、接任务、留言、看成交记录用户端功能管理员用户增删改、留言删除、数据备份后台管理任务是这个系统的核心实体。接单动作不能想怎么写就怎么写我一般会给任务加一个状态字段来控制流转最常见的取值是这样0 表示待接收1 表示已成交2 表示关闭或过期。会员在列表页看到 status0 的任务才能点“接收”管理员或发布者可以把任务置为 2 关闭。status含义谁可以操作0待接收登录用户可以浏览、接收1已成交不可继续接收系统写入成交记录2已关闭前端默认不展示这套状态设计在资源包里不一定写得这么规范可能是字符串存“open/closed”也可能是 0/1 两个值。建议拿到代码先看 DAO 里的 update 和 select 语句确认它用了几种状态再决定要不要补全。别小看这一步很多老项目翻车就是状态没控制好任务被两个人同时接收。2.2 SQL Server 建表脚本字段、类型和主键怎么写围绕业务至少要设计 5 张表用户表、任务表、留言表、公告新闻表、成交记录表。包里的表名可能叫 t_user、t_task、t_message、t_news、t_deal也可能叫别的命名不是关键字段映射才是。表名用途关键字段t_user登录账号与角色user_id, username, password, role, reg_timet_task任务发布与接单task_id, title, content, price, publisher_id, status, publish_timet_message用户留言msg_id, user_id, content, create_timet_news新闻公告与流程说明news_id, title, content, create_timet_deal成交记录deal_id, task_id, receiver_id, amount, deal_timeSQL Server 和 MySQL 有个明显差异自增主键不用 AUTO_INCREMENT而是 IDENTITY(1,1)。这个点最容易在从 MySQL 迁过来的同学手里翻车直接写 AUTO_INCREMENT 会当场报语法错误。下面是最常见的用户表建表语句CREATE TABLE t_user ( user_id INT IDENTITY(1,1) PRIMARY KEY, -- 自增主键等价于 MySQL 的自增 username NVARCHAR(50) NOT NULL UNIQUE, -- 登录名唯一 password NVARCHAR(50) NOT NULL, -- 明文存储仅限学习项目 role NVARCHAR(20) NOT NULL DEFAULT user, -- user / admin reg_time DATETIME NOT NULL DEFAULT GETDATE() );这里用 NVARCHAR 而不是 VARCHAR是为了避免中文乱码。SQL Server 的排序规则不同VARCHAR 在中文环境下很容易出现“???”或乱码NVARCHAR 按 Unicode 存储兼容性更好。密码字段在这个老项目里大概率是明文正式做二次开发时应该换成 MD5 加盐或 BCrypt这个不在原需求里但属于动手必改项。任务表单独看金额字段我会用 DECIMAL(18,2)不用 FLOAT 或 DOUBLE因为浮点数计算金额会出现 0.10.2 不等于 0.3 那种问题。任务内容可能较长用 NVARCHAR(MAX)。CREATE TABLE t_task ( task_id INT IDENTITY(1,1) PRIMARY KEY, title NVARCHAR(100) NOT NULL, content NVARCHAR(MAX) NOT NULL, price DECIMAL(18,2) NOT NULL DEFAULT 0, -- 预算金额避免浮点 publisher_id INT NOT NULL REFERENCES t_user(user_id), status INT NOT NULL DEFAULT 0, -- 0待接收 1已成交 2关闭 publish_time DATETIME NOT NULL DEFAULT GETDATE() );建表顺序要特别注意外键依赖先建 t_user再建 t_task、t_message、t_deal否则外键引用的表还不存在。资源包里的脚本如果顺序乱了第一次执行会失败手动调整一下即可。提示SQL Server 里凡是存中文的字段优先选 NVARCHAR后端写入和页面展示会省掉一大半乱码问题。2.3 MyEclipse 工程目录拿到 zip 包后先认一遍解压之后不要直接双击一个 index.jsp 就以为能跑。JSP 项目的入口在 web.xml整个工程的可执行路径都围绕它展开。典型的 MyEclipse Web 工程目录长这样jspZhaobiao/ ├── src/ │ ├── com/xxx/servlet/ # 控制器 │ ├── com/xxx/dao/ # JDBC 数据操作 │ ├── com/xxx/model/ # User、Task 等实体 │ └── com/xxx/util/ # DBUtil 连接工具 └── WebRoot/ ├── WEB-INF/ │ ├── web.xml # Servlet 映射与欢迎页 │ └── lib/ # sqljdbc4.jar 等第三方包 ├── css/ js/ images/ └── *.jsp # 页面文件看目录时我会先打开 web.xml把所有 servlet-mapping 列出来它等于一份代码地图。比如一个登录接口是这样注册的servlet servlet-nameloginServlet/servlet-name servlet-classcom.xxx.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameloginServlet/servlet-name url-pattern/login/url-pattern /servlet-mappingservlet-class 必须带完整包名url-pattern 前必须加斜杠。老项目最常见的 404 事故就是 url-pattern 和 JSP 里 form 的 action 路径对不上。拿到包后对照这个文件核对一遍比直接跑起来效率高很多也能顺手把项目里有哪些功能摸清楚。3. 复现环境JDK、Tomcat、SQL Server 与 MyEclipse 的接线细节JSP 老项目对环境比 Spring Boot 敏感得多。技术栈越旧版本搭配越要讲究。我复现时踩过的顺序是先把数据库连通再跑工程最后才动页面。数据库连不上时所有 JSP 页面都会卡在调用 JDBC 的那一步报错信息还不直观。3.1 版本搭配老项目别用太新的编译器硬跑这套系统的技术栈是 Servlet JDBC JSP属于 Java Web 早期形态。常见配置是 JDK 1.7 或 1.8Tomcat 7 或 8SQL Server 2008 R2 或 2012MyEclipse 2014 或 2015。不是说新版工具跑不了而是新工具默认字符集、Servlet 版本、Tomcat 附带 jar 都可能不一样老代码里那种用 scriptlet 写死的输出在 Tomcat 9 上偶尔会出现兼容问题。组件建议版本避坑点JDK1.7 / 1.8编译级别不要超过 Tomcat 支持的 class 版本Tomcat7.x / 8.x与 MyEclipse 自带服务器版本匹配即可SQL Server2008 R2 / 2012老脚本在此版本下执行最稳MyEclipse2014 / 2015内置 MyEclipse Tomcat部署一键完成如果你电脑只装了 JDK 11 以上也不是不能用但要把 Eclipse 的 Java Compiler 编译级别调低并且建议换用 Tomcat 8.5 之后的版本。老代码里如果用了sun.misc.*这类内部类高版本 JDK 下会直接编译失败那时别怀疑代码先查是不是版本问题。3.2 JDBC 连接配置URL 别按 MySQL 的习惯写JDBC 连 SQL Server 的配置通常放在 db.properties 里由 DBUtil 统一读取。注意 URL 的分隔符是分号不是 MySQL 那种?参数风格。jdbc.drivercom.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.urljdbc:sqlserver://localhost:1433;DatabaseNamezhaobiao jdbc.usernamesa jdbc.password123456DatabaseName前面是分号和 MySQL 的?useSSLfalse完全不同。驱动类名也不要写旧版的com.microsoft.jdbc.sqlserver.SQLServerDriver那是 SQL Server 2000 时代的写法现在基本失效了。当前主流驱动是 Microsoft JDBC Driver 4.x 以上包名是 sqljdbc4.jar 或 mssql-jdbc 系列。连数据库的 Java 工具类骨架我一般这样写public class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { try (InputStream in DBUtil.class.getResourceAsStream(/db.properties)) { Properties p new Properties(); p.load(in); driver p.getProperty(jdbc.driver); url p.getProperty(jdbc.url); username p.getProperty(jdbc.username); password p.getProperty(jdbc.password); Class.forName(driver); // 注册 JDBC 驱动 } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }静态块里做驱动注册保证类第一次被加载时驱动就位。数据库连接每次 new 一个确实很低效但老项目基本都是这样先能跑通再谈优化。如果你想入门连接池把 DBUtil 改成用 DBCP 或 C3P0改动范围只在 getConnection 内部对上层 DAO 透明。连接之前还有两步容易被忽略确认 SQL Server 的 TCP/IP 协议已启用确认 1433 端口能被访问。这两步不做JDBC 会一直报超时。注意sqljdbc4.jar 必须放进 WEB-INF/lib只加到 Build Path 不会被打进发布目录。3.3 部署到 TomcatMyEclipse 里的操作顺序我在 MyEclipse 里复现这类工程的固定流程是导入工程 → 检查编译级别 → 把驱动 jar 放进 WEB-INF/lib → 启动服务器 → 看控制台日志。导入工程时直接 File Import Existing Projects into Workspace选中解压后的根目录。导入后第一步不是启动而是确认 Java 编译级别。右键工程 Properties Java Compiler把 Compiler compliance level 和已安装的 JDK 对齐。接着看 Project Facets勾选 Dynamic Web Module 版本这个版本要和 Tomcat 匹配。如果 zip 包自带 .classpath 和 .project 配置换电脑换 JDK 后通常也需要重新校准一次。驱动 jar 的位置很关键必须放在WebRoot/WEB-INF/lib下。MyEclipse 构建时会自动打包进发布目录如果只把 jar 加到 Build Path 而没放进 lib开发时编译不报错启动后 Tomcat 会报 ClassNotFoundException因为发布的 war 包里根本没有那个 jar。发布时我用 Run As MyEclipse Server Application选择已配置好的 Tomcat。启动后先看 Console 有没有Deployment ... has finished或Server startup输出再打开浏览器访问http://localhost:8080/工程名/。如果直接跳登录页或首页说明工程本身没问题剩下的就是数据库那层。4. 核心代码骨架任务发布、接单、后台管理的实现套路这套系统的功能点很集中用户登录、任务发布、任务接收、留言管理、成交记录、后台维护。JSP 旧项目没什么架构魔法就是 Servlet 收参数、DAO 拼 SQL、JSP 展示三件事。把一条主线任务从发布到成交走通其他模块照葫芦画瓢就行。4.1 从登录开始Servlet、DAO、JSP 的分工登录接口最能体现三层分工。JSP 负责放表单Servlet 负责取参数和转发DAO 负责查数据库。先看 DAO 的方法public User findByUsernameAndPassword(String username, String password) { String sql SELECT * FROM t_user WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setUserId(rs.getInt(user_id)); u.setUsername(rs.getString(username)); u.setPassword(rs.getString(password)); u.setRole(rs.getString(role)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }这段代码有两个关键点。第一用 PreparedStatement 的?占位符传参而不是把用户名直接拼进 SQL 字符串。字符串拼接一旦遇到 or 11这种输入SQL 注入就发生了。老项目里这种情况很常见属于拿到手必须改的隐患。第二try-with-resources 会自动关闭 Connection、Statement、ResultSet不用手写一堆 finally。Servlet 侧接收参数并决定跳转方向WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 先处理编码 String username req.getParameter(username); String password req.getParameter(password); User user new UserDAO().findByUsernameAndPassword(username, password); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(index.jsp); // 登录成功重定向 } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }这里要区分sendRedirect和forward重定向是浏览器重新发一次请求地址栏会变转发是服务器内部跳转地址栏不变。登录成功用重定向能避免刷新页面时重复提交登录表单。这个细节后面章节还会再踩一次。4.2 发布任务从表单到入库的完整链路用户发布任务JSP 表单收集 title、content、price 三项提交到/publishTask。页面代码很简单form action${pageContext.request.contextPath}/publishTask methodpost label任务标题/label input typetext nametitle maxlength100 required / label任务内容/label textarea namecontent rows6 required/textarea label预算金额/label input typenumber nameprice step0.01 min0 required / button typesubmit发布/button /form${pageContext.request.contextPath}会动态输出工程根路径这是老 JSP 项目里防止路径写死的标准写法。如果直接写死/jspZhaobiao/publishTask换部署名后全部链接都会失效。Servlet 端处理入库String title req.getParameter(title); String content req.getParameter(content); String priceStr req.getParameter(price); User loginUser (User) req.getSession().getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(login.jsp); return; } double price 0; if (priceStr ! null !priceStr.trim().isEmpty()) { price Double.parseDouble(priceStr); } String sql INSERT INTO t_task (title, content, price, publisher_id, status) VALUES (?,?,?,?,0); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, title); ps.setString(2, content); ps.setBigDecimal(3, BigDecimal.valueOf(price)); // 金额用 BigDecimal 写入 ps.setInt(4, loginUser.getUserId()); ps.executeUpdate(); resp.sendRedirect(taskList.jsp); // 提交后重定向防止刷新重复发布 } catch (SQLException e) { req.setAttribute(error, 发布失败 e.getMessage()); req.getRequestDispatcher(publish.jsp).forward(req, resp); }这里有三个值得记的点。第一先从 session 取用户取不到就踢回登录页权限判断要在业务代码里做不能只靠页面隐藏。第二金额用BigDecimal.valueOf而不是直接 setDouble避免精度问题。第三发布成功后 sendRedirect否则用户在结果页按 F5浏览器会重新提交表单库里凭空多出几条任务。4.3 接收任务与成交记录一个事务把两件事绑在一起接单不只是“改一个字段”。从业务上讲会员接收任务时发生了两件事任务状态从 0 变成 1成交表插入一条记录。这两件事必须同时成功或同时失败。如果只改了状态但没写进成交记录后台统计就对不上反过来只写了记录状态没变任务还能被别人接收。// 前提taskId 来自页面参数receiverId 来自 session 中的当前用户 Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 开启事务 String update UPDATE t_task SET status1 WHERE task_id? AND status0; try (PreparedStatement ps conn.prepareStatement(update)) { ps.setInt(1, taskId); int count ps.executeUpdate(); if (count 0) { throw new SQLException(任务不存在或已被接收); } } String insert INSERT INTO t_deal (task_id, receiver_id, deal_time) VALUES (?,?,GETDATE()); try (PreparedStatement ps conn.prepareStatement(insert)) { ps.setInt(1, taskId); ps.setInt(2, receiverId); ps.executeUpdate(); } conn.commit(); // 两步都成功才提交 } catch (SQLException e) { conn.rollback(); // 任何一步失败都回滚 e.printStackTrace(); resp.sendRedirect(taskList.jsp?errorreceive_failed); } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } }注意UPDATE ... WHERE task_id? AND status0这个条件它相当于一个乐观锁。只有任务状态还是 0 时才能更新成功两个会员同时点接收时数据库行锁会让第二个 update 的影响行数为 0从而走异常分支不会覆盖第一个人的成果。这是老 JDBC 项目里最朴素也最有效的并发控制方式。成交记录表可以冗余存一份任务标题和金额这样查询成交记录时不需要每次 join 任务表。老项目数据量不大时这种冗余是省事的做法。4.4 后台管理用户列表、留言删除与数据备份管理员模块比用户端更简单核心是“列表 删除”。以留言管理为例管理员登录后访问 MessageListServlet查询全部留言放入 request再转发到 JSP 用 JSTL 循环显示c:forEach varmsg items${messageList} tr td${msg.content}/td td${msg.createTime}/td td a hrefdeleteMessage?id${msg.msgId} onclickreturn confirm(确认删除该留言)删除/a /td /tr /c:forEachonclickreturn confirm(...)是简单的防误删机制点了删除后先弹确认框取消就不会跳转。真正删数据时DeleteMessageServlet 里要再校验一次 session 里的 role 是不是 admin不能因为导航栏只对管理员显示就认为 URL 是安全的。很多 JSP 项目的权限漏洞就是这么来的后台链接隐藏了但接口谁都能访问。“查看已发布的信息”在用户端本质就是一张个人信息展示页面从 session 取出当前登录用户调 DAO 里的findByPublisherId(userId)把 taskList 放进 request再在 JSP 循环展示。和留言管理列表的区别只在于查询条件多了 user_id。管理员侧的“数据备份”在小系统里通常有两种实现把关键表导出成 SQL 文件或者直接调用 SQL Server 的备份命令。如果你打算在系统里做一键备份最简单的代码级方案是执行这条语句BACKUP DATABASE zhaobiao TO DISK D:\backup\zhaobiao.bak WITH INIT;执行备份的账号需要有 db_backupoperator 或 sysadmin 权限。老项目里更常见的做法是把数据通过 JDBC 查出来生成 INSERT 语句写进文本文件这种方式不依赖数据库服务器磁盘权限但只备份了数据表结构、索引、触发器还得单独处理。实用一点讲直接在 SQL Server Agent 里建每周备份的作业比在 JSP 页面里做这个功能靠谱得多。5. 避坑排查JSP 网上招标系统最常见的 5 个翻车点JSP 老项目的坑相对固定基本都集中在驱动、端口、编码、重复提交、缓存这五个方向。我把复现过程中最容易翻车的情况按“现象 → 原因 → 解决”记下来供你对着查。5.1 现象一运行就报 ClassNotFoundException驱动加载不了启动 Tomcat 后访问任意页面后台抛java.lang.ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver或者页面直接 500。原因sqljdbc 驱动 jar 没有打进发布目录。最常见的是把 jar 放进了工程根目录或某个临时文件夹只加到 Build Path没有放到WEB-INF/lib下。MyEclipse 发布时只复制 WEB-INF/lib 里的 jar其他地方的一律不管。有些情况是 jar 版本太旧驱动类路径本身就不对。解决把 sqljdbc4.jar 复制到WebRoot/WEB-INF/lib右键项目 Refresh然后 Clean 一下工程再重启 Tomcat。如果还不行打开发布目录确认WEB-INF/lib下面真实存在该 jar或者用压缩工具直接打开部署后的 war 包检查这一步排查最直观。驱动类名建议写com.microsoft.sqlserver.jdbc.SQLServerDriver这是 Microsoft JDBC 4.x 以后的标准类名。5.2 现象SQL Server 连接超时报 Connection refusedJDBC 配置看起来没问题但每次获取连接都要等很久最后抛 Connection refused或者提示The TCP/IP connection to the host ... has failed。原因SQL Server 默认安装时 TCP/IP 协议可能是禁用的。Java 的 JDBC 走的是 TCP/IP 1433 端口协议没开就必然连不上。另一个常见原因是防火墙把 1433 拦了或者 SQL Server Browser 服务没启动导致无法动态解析实例名。解决打开“SQL Server 配置管理器”在“SQL Server 网络配置”里找到实例的“TCP/IP”右键启用。修改后必须重启 SQL Server 服务这一步经常被忽略。然后确认 1433 端口在监听命令行执行netstat -an | findstr 1433能看到 LISTENING 说明端口起来了。防火墙方面Windows 防火墙添加入站规则放行 1433 即可。最后再用telnet localhost 1433验证能连上再去跑 Java 程序。5.3 现象页面中文全部乱码显示问号页面标题、任务内容、留言在浏览器里显示成“????”或乱码数据库里查出来也是乱码。原因JSP 的 pageEncoding、Servlet 里的 setCharacterEncoding、数据库的排序规则三者不一致。这套系统用 SQL Server数据库默认排序规则可能是 Chinese_PRC_CI_AS和页面的 UTF-8 编码混在一起中文就会乱。另外POST 请求如果没有req.setCharacterEncoding(UTF-8)Servlet 按 ISO-8859-1 解码参数中文直接变乱码。解决全链路统一 UTF-8。JSP 页面头部写% page languagejava pageEncodingUTF-8 contentTypetext/html; charsetUTF-8 %Servlet 在 doPost 开头调用req.setCharacterEncoding(UTF-8)数据库表字段用 NVARCHAR 而不是 VARCHAR。如果表已经建好且数据已经乱掉光改页面没用要先把数据清掉再确认表结构是 NVARCHAR 后重新插入。这个坑一旦数据进了库清洗成本很高所以建表阶段就要盯住字段类型。5.4 现象表单提交成功后按 F5 刷新数据重复出现发布任务或留言成功后用户习惯性按 F5 刷新结果页面提示重新提交数据库里出现一模一样的多条记录。原因Servlet 处理完请求后用的是forward跳转到结果页页面地址栏还是原来那个/publishTask。刷新时浏览器重新发了一次 POSTServlet 又执行了一遍 insert。这是 JSP 老项目里最常见的重复数据来源不是 SQL 写错了是跳转方式选错了。解决提交成功的分支一律用sendRedirect让浏览器地址栏变成新的 GET 地址。这个方法在 4.2 节已经出现过这里再强调一遍。如果只想在页面层做简单缓解可以在表单提交按钮上绑定一次点击禁用onsubmitthis.submit.disabledtrue但这挡不住 F5。根治办法还是 Post/Redirect/Get也就是 PRG 模式。提示把提交成功的分支改成 sendRedirect是根治重复提交最有效的办法没有之一。5.5 现象改了 JSP 代码页面刷新后还是旧的修改了 JSP 或 CSS浏览器怎么刷新都没变化后台 Console 也不报错看起来像代码没生效。原因Tomcat 对 JSP 是首次访问时编译成 class 并缓存在 work 目录MyEclipse 重新发布时如果没有 Cleanwork 目录里的旧 class 还会继续被使用。浏览器侧CSS 和 JS 文件也可能命中本地缓存地址不变时根本不会重新请求。解决先做一次手动重部署。MyEclipse 里右键工程 Clean再删掉 Tomcat 的work/Catalina/localhost/工程名目录下的已编译内容重启服务器。浏览器按 CtrlShiftR 强制刷新。如果是 CSS 文件频繁被缓存可以在页面引用时加版本参数link relstylesheet hrefcss/style.css?v3。JSP 页面本身如果开了 Tomcat 的自动重载有时重新访问该页面就会触发重新编译前提是你确认项目已经重新发布。6. 进阶给老系统补三个实用小功能6.1 分页用 ROW_NUMBER() 给任务列表减负原需求里没写分页但任务表超过几百条后列表页会明显变慢。SQL Server 的分页我建议用 ROW_NUMBER()老版本兼容性好SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY publish_time DESC) AS row_num, * FROM t_task ) t WHERE row_num BETWEEN 1 AND 10;页码变化只需要算好起始和结束数字第 page 页就是(page-1)*pageSize1到page*pageSize。在 Servlet 里接收pageNo参数把两个边界值算出来拼进 SQL不改动业务逻辑就能给列表减负。6.2 页面加载完自动刷新适合展示页不适合表单页如果你想把前台大厅做成任务轮播让 JSP 页面每隔一段时间自动刷新一次最省事的办法是页面加载完成后启动定时器window.onload function () { setTimeout(function () { location.reload(); }, 30000); };这段代码会让页面在加载完 30 秒后刷新一次不用引入 WebSocket 或 Ajax纯服务端渲染也能做到“准实时”。但有一条红线绝对不能用在发任务、留言这类 POST 后转发过来的页面否则会触发重复提交。如果既要自动刷新又不能丢表单正确做法是用 Ajax 只刷新列表区域而不是整页 reload。6.3 已发布信息列表回显给个人信息展示页补一个空态“查看已发布的信息”在资源包里一般是直接 forEach 循环用户第一次登录时列表为空页面会显示一片空白。我在类似场景里习惯补一个空态判断c:choose c:when test${empty taskList} p您还没有发布过任务点击上方按钮去发布。/p /c:when c:otherwise c:forEach vartask items${taskList} div${task.title} —— 预算 ${task.price} 元状态${task.status}/div /c:forEach /c:otherwise /c:choose这里用 JSTL 的 c:choose比在 JSP 里写 if 更干净。taskList 由 TaskListServlet 根据当前登录用户的 user_id 查询后放进 request。老系统如果没用 JSTL 而是写% for (...) %也可以在 scriptlet 里先判断 size 是否为 0效果一样可读性会差一些。从那以后我每次接手 JSP 老项目都会强制先做三件事跑一遍数据库建表脚本、核对 web.xml 的全部 servlet-mapping、把所有提交成功的分支改成 sendRedirect。这三步做完项目基本就能正常运行后面再怎么加功能都不容易翻车。希望这几段拆解能帮你把这份 jsp网上招标系统.zip 顺利跑起来并且改成自己想要的版本。本文还有配套的精品资源点击获取
返回列表