news 2026/10/7 5:31:05

JSP+Servlet+MySQL教务管理系统源码:从环境部署到事务改造实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+Servlet+MySQL教务管理系统源码:从环境部署到事务改造实战

简介:这是一份基于 JSP+Servlet+MySQL 构建的教务管理系统完整源码,适合 Java Web 初学者、课程设计或毕业设计选题者,用于理解传统 SSM 之外的原生 MVC 分层开发思路。项目覆盖学生信息、教师资料、课程与考试安排等核心模块,包含完整业务逻辑与数据库脚本。压缩包共 530 个文件,约 9.32MB,以 jsp 页面、java 源码、class 编译文件、sql 脚本及 jar 依赖库为主,另含 gif、jpg 等界面素材与 swf、js、css、xml 配置资源,目录结构清晰便于对照学习。已有 719 人学习浏览。代码中可看到 UserManager、LoginAction、ModifyUserAction 等功能类,配合 JSTL、EL 表达式、C3P0 连接池及 MVC 设计模式,演示了从表单请求、Servlet 业务处理到 MySQL 数据持久化的完整链路,是一份能直接运行、适合拆解研读的实战型教学项目。

1. JSP+Servlet+MySQL 教务管理系统:为什么这本老源码今天还有人翻

又到毕业设计季,我看到不少人在翻 JSP+Servlet+MySQL 的教务管理系统源码。这组合听起来老,但它把登录认证、角色权限、选课事务这些 Java Web 核心问题完整串了一遍,是少数能让你站在答辩台上把每一行代码都讲清楚的项目。适合两类人:拿它做课程设计底子的学生,和要接手学校遗留系统的开发者。前者求跑通,后者求看懂。这篇按配环境、导数据库、跑通选课、权限控制、排坑的顺序,把这套老源码从头拆到尾。

2. 先配环境:JDK、Tomcat、MySQL 的版本搭配与部署前准备

2.1 版本搭配:老项目为什么对 Tomcat 和 JDK 这么挑剔

JSP 源码最常见的问题不是代码本身,而是环境不匹配。这套教务系统如果是 2015 到 2018 年之间的产物,代码里 import 的是 javax.servlet.*,用的是 JSP 2.x 规范。你要是拿 Tomcat 10 来跑,启动时直接报错——Tomcat 10 把包名从 javax 换成了 jakarta,老代码里import javax.servlet.http.HttpServlet全部失效。所以第一原则是:老项目不要用新容器。JDK 8 配 Tomcat 8.5 或 9.0 是最稳的搭配,JDK 也不要往 11、17 上凑,老代码里有些反射操作在更高 JDK 上会出现莫名的访问限制错误。

连接 MySQL 这一侧同样敏感。很多这套源码配套的 SQL 脚本是按 MySQL 5.x 写的,表结构用 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 没问题,但驱动是个坎。mysql-connector-java 5.1.x 连 MySQL 8.x 会碰到认证插件不兼容,具体报错后面有专门一节讲。如果你现在要新装数据库,我一般建议直接装 MySQL 5.7.44,这是 5.7 系列的收尾版本,之后 5.7 分支不再发新版,也正是这类 JSP 项目最常见的运行环境。想用 MySQL 8.x 的话,驱动必须换成 8.0.x 的 connector,连接 URL 里还要额外加 allowPublicKeyRetrieval=true。

先确定三样环境,Windows 下打开命令行验证:

# 确认 JDK 版本,8 和 8u201 之前的老版本建议升级 java -version # 确认 JAVA_HOME,注意指向 JDK 根目录而不是 bin echo %JAVA_HOME% # 确认 Tomcat 版本,启动前先看版本信息 catalina.bat version

没设 JAVA_HOME 时,Tomcat 的 startup.bat 会直接失败,窗口一闪而过。我见过不少同事只装了 JRE 就以为完事,实际上老版本 Tomcat 启动要依赖 JDK 里的编译器工具,只装 JRE 同样跑不起来。下面是一张我按这套源码总结的版本参照表,照着配能少走弯路:

组件推荐版本说明
JDK8u201 以上兼容旧编译代码,启动快
Tomcat8.5.x 或 9.0.x仍是 javax 包名规范
MySQL5.7.44老 SQL 脚本兼容性最好
mysql-connector-java5.1.49配 MySQL 5.7,认证插件不冲突

2.2 从 zip 到能跑:解压、目录结构识别与部署方式

拿到 zip 压缩包,先别急着双击解压到桌面。先用压缩软件打开看一眼目录结构。这类源码常见的组织方式有两种:一种是带 .project 和 .classpath 的 Eclipse 工程,直接用 IDE 导入再用;另一种是已经打好的 Web 应用目录,里面有 WEB-INF 和一堆 jsp 页面,这种可以直接拷进 Tomcat 的 webapps 目录。你解压后看到的东西和下面这个结构应该大差不差:

edu_mis/ ├─ WEB-INF/ │ ├─ web.xml # Servlet 映射、Session 超时与欢迎页配置 │ ├─ lib/ # JDBC 驱动、JSTL 标签库的 jar 文件 │ └─ classes/ # 编译后的 Servlet 字节码 ├─ css/ js/ images/ ├─ login.jsp ├─ index.jsp └─ student/ # 学生端页面 ├─ course_list.jsp └─ select_course.jsp

WEB-INF 里的内容浏览器不能直接访问,这是 Servlet 规范的保护机制。所以教务系统的 JSP 才按角色分包放在不同目录,登录后跳转到各自的首页。如果你用 IDEA 导入,注意把 lib 下的 jar 添加为 Library,把 classes 设为编译输出目录;直接拷贝部署就省事多了,整个 edu_mis 目录放进 webapps,启动 Tomcat,访问http://localhost:8080/edu_mis/login.jsp就能看到登录页。

打开 web.xml 时有三个配置值得多看一眼。第一个是 servlet-mapping,它决定/login、/selectCourse这些路径分别由哪个 Servlet 处理,表单提交路径和映射对不上就会 404;第二个是 session-config 里的超时时间,默认 30 分钟;第三个是 welcome-file-list,访问根路径时先进哪个页面。改错任何一个,项目都转不起来。

2.3 数据库准备:SQL 脚本导入与连接账号处理

把脚本导入 MySQL 是跑通整个项目前的最后一步。常见做法是在命令行里执行,Windows 下打开 cmd,先登录再 source:

# 登录 MySQL,输入密码进入交互模式 mysql -uroot -p # 进入 MySQL 后执行脚本,路径用正斜杠或双反斜杠 source C:/path/to/edu_mis.sql;

或者直接重定向导入,注意 -p 后面紧跟密码,没有空格:

mysql -uroot -p123456 < C:/path/to/edu_mis.sql

这里有个高频问题:SQL 脚本用 Windows 记事本打开过,包含中文注释,导入时报语法错误或者注释乱码。原因是脚本文件被存成了 GBK 编码。解决办法是把脚本另存为 UTF-8 编码再重新导入。导入完成后进 MySQL 执行SHOW TABLES;,看到 user、course、student_course 这些表就说明建库成功。

接下来处理连接账号。绝大多数这类源码的数据库连接信息写在 DBUtil.java 或 db.properties 里,默认是 root/123456 之类。你本地 MySQL 的 root 密码大概率不是这个,所以要改成本地密码,或者建一个匹配的专用账号。我一般会单独建一个账号,不让 root 直接出现在代码里——课程设计可以偷懒,但接手遗留系统时这个习惯能省掉不少麻烦。

3. 跑通第一个选课流程:从数据库表到 Servlet 后端逻辑

3.1 表结构拆解:user、course、student_course 三张表的关系

教务管理系统的数据模型,核心就三张表。user 表存的是管理员、教师、学生三类账号,用一个 role 字段区分,好处是登录逻辑只查这一张表,不用分开写三套验证。course 表存课程基本信息和容量,其中 capacity 是容量,selected 是已选人数。student_course 是选课关系表,记录谁选了哪门课、考了多少分。这个模型很典型,答辩时老师最常问的就是为什么把成绩放在关系表上而不是课程表里——因为一名学生一门课只有一条成绩记录,放课程表里会存不下所有学生的成绩。

选课关系表一定要有联合唯一键,也就是 UNIQUE KEY uk_student_course(student_id, course_id),从数据库层面杜绝重复选课。这比在 Java 代码里先查再插更可靠,两个操作之间存在时间窗口,并发时照样会插重。一个完整的建表脚本大概长这样:

CREATE DATABASE IF NOT EXISTS edu_mis DEFAULT CHARACTER SET utf8mb4; USE edu_mis; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), role CHAR(1) DEFAULT '3' COMMENT '1=管理员 2=教师 3=学生', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, teacher_id INT, capacity INT DEFAULT 50, selected INT DEFAULT 0, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) DEFAULT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

几个参数要能讲清楚:user 表同时容纳教师和学生,这叫单表继承,角色靠 role 区分,是这类小项目最常见的做法;course 表把 teacher_id 设为外键指向 user,表示开课教师;student_course 不设外键也行,但联合唯一键必须有。外键在清空重导时容易卡住,比如你先 DELETE 再 INSERT 会因外键约束被拒绝,顺序要反过来。

3.2 选课核心链路:DAO 层手动控制事务

选课功能综合了整份源码里最难的一块——事务。它要做四件事:查课程是否已满、查学生是否重复选课、插入选课记录、更新已选人数。几步必须作为整体成功或失败,否则会出现「选课记录插进去了但人数没加一」这种脏数据。这就要把事务边界划清楚。

教学风格的选课 DAO 方法常见写法如下:

public boolean selectCourse(int studentId, int courseId) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,手动控制事务 // 1. 锁住课程行,防止并发超选 String lockSql = "SELECT capacity, selected FROM course WHERE id = ? FOR UPDATE"; ps = conn.prepareStatement(lockSql); ps.setInt(1, courseId); rs = ps.executeQuery(); if (!rs.next()) return false; // 课程不存在 int capacity = rs.getInt("capacity"); int selected = rs.getInt("selected"); if (selected >= capacity) { throw new SQLException("课程已满"); } // 2. 检查是否重复选课 String checkSql = "SELECT COUNT(*) FROM student_course WHERE student_id = ? AND course_id = ?"; ps = conn.prepareStatement(checkSql); ps.setInt(1, studentId); ps.setInt(2, courseId); rs = ps.executeQuery(); if (rs.next() && rs.getInt(1) > 0) { throw new SQLException("已选过该课程"); } // 3. 插入选课记录 String insertSql = "INSERT INTO student_course(student_id, course_id, select_time) VALUES(?, ?, NOW())"; ps = conn.prepareStatement(insertSql); ps.setInt(1, studentId); ps.setInt(2, courseId); ps.executeUpdate(); // 4. 更新已选人数 String updateSql = "UPDATE course SET selected = selected + 1 WHERE id = ?"; ps = conn.prepareStatement(updateSql); ps.setInt(1, courseId); ps.executeUpdate(); conn.commit(); // 全部成功后统一提交 return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn != null) conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } } }

这段代码里三个参数值得琢磨:setAutoCommit(false) 关闭自动提交,后面所有语句都在一个事务里,explicit 调用 commit 或 rollback 才真正落库;FOR UPDATE是行级锁,两个学生同时选同一门课,后到的会被阻塞到前一个提交或回滚,避免容量被超卖;finally 里把 autoCommit 恢复为 true,是给连接池做个清理,不然连接复用后状态是脏的,下一次拿着事务未提交的连接去查数据,会读到旧快照。

这个方案用 SELECT FOR UPDATE 锁行,高并发下性能一般,但作为课程设计完全够用。你能把这些话讲清楚,老师基本不再追问。想改好的话,方向是把锁粒度放细,或者用 UPDATE course SET selected = selected + 1 WHERE id = ? AND selected < capacity 这种条件更新,这是后话。

3.3 从 Servlet 到 JSP:request 作用域与转发的配合

选课完成后要把结果反馈给页面,这一步就牵扯到 Servlet 和 JSP 之间的数据传递。常见做法是用 request.setAttribute 把消息放进请求作用域,然后 request.getRequestDispatcher(...).forward(...) 做服务端转发,而不是 sendRedirect。两者区别很大:转发是服务端内部跳转,request 里的数据原样带到目标页;重定向是浏览器重新发起一次全新请求,request 里的数据全丢。你如果用重定向再想取 message,取到的一定是 null。

一个选课 Servlet 的典型片段:

@WebServlet("/selectCourse") public class SelectCourseServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从 Session 拿当前登录学生 User user = (User) req.getSession().getAttribute("currentUser"); int courseId = Integer.parseInt(req.getParameter("courseId")); CourseDao dao = new CourseDao(); boolean ok = dao.selectCourse(user.getId(), courseId); // 把结果放进 request,转发给课程列表页 req.setAttribute("message", ok ? "选课成功" : "选课失败,课程已满或已选过"); req.getRequestDispatcher("student/course_list.jsp").forward(req, resp); } }

这里两个容易翻车的点。第一,parseInt 直接取参数时,如果 courseId 传的是空字符串就抛 NumberFormatException,项目里不一定处理,你可以顺手加上 try-catch,给一个友好提示再转发回去。第二,课程列表页大多用 JSTL 的 c:forEach 循环渲染表格,前提是 lib 里有 jstl.jar 和 standard.jar,没放齐的话页面直接报The absolute uri cannot be resolved,这是 JSP 项目里最常见的白屏原因之一。另外列表查询的 SQL 通常按ORDER BY select_time DESC排序,让新选的课排前面,这个细节在页面展示顺序不对时优先检查。

4. 登录认证的完整链路:LoginServlet、Session 与过滤器时机

4.1 登录验证放哪:Servlet 判断和过滤器拦截各管一段

登录逻辑看着简单,写法却分两派。常见做法是:登录请求交给一个 LoginServlet,它查数据库比对用户名和密码,成功就把用户对象放进 Session;其他页面访问权限交给一个过滤器统一拦截,没登录就踢回登录页。这样职责划分清楚,也是这套源码通常采用的方案。

LoginServlet 的核心逻辑大概是这样:

@WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 不设编码的话,中文用户名会乱码,这一行必须在读参数之前 req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); // 源码里多数是 MD5 后比对,明文比对的也有,不建议学 UserDao userDao = new UserDao(); User user = userDao.findByUsernameAndPassword(username, password); if (user == null) { req.setAttribute("errorMsg", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); return; } // 登录成功:用户对象和角色都塞进 Session HttpSession session = req.getSession(true); session.setAttribute("currentUser", user); session.setAttribute("role", user.getRole()); // 按角色分流:管理员、教师和学生进不同首页 String target; if ("1".equals(user.getRole())) { target = "admin/index.jsp"; } else if ("2".equals(user.getRole())) { target = "teacher/index.jsp"; } else { target = "student/index.jsp"; } resp.sendRedirect(req.getContextPath() + "/" + target); } }

这段代码说明三件事:setCharacterEncoding 必须在读取参数之前调用,放后面就失效,这是乱码问题的重灾区;密码一般做一次哈希,MD5 在老源码里见得最多,安全强度不高,但比明文好一点;角色分流决定后面过滤器要怎么设计放行规则。你写完这段,要能回答老师一个问题:密码能不能再加固?能,下面第 6 章会讲。

4.2 Session 里存什么:对象加角色,别只存一个登录标记

登录成功以后,Session 里至少要放两样东西:用户对象和角色。用户对象里有当前登录人的 id、姓名,选课功能要知道当前学生是谁,成绩录入要知道当前教师是谁,这些都在页面上频繁取用;角色字段用于功能入口的显示和权限判断,比如学生登录后只显示选课和查成绩菜单,教师登录后显示录入成绩菜单。

Session 超时时间通常由 web.xml 配置,默认 30 分钟。教务系统操作频率不高,30 分钟基本够用,但录成绩这种慢节奏操作容易被踢回登录页,在线时长不够体验很差。常见做法是把超时调长一点:

<session-config> <session-timeout>60</session-timeout> </session-config>

这个配置的单位是分钟,别填大数字,填 60 就是 60 分钟。还有一个反直觉的点必须记住:request.getSession() 会在没有会话时创建一个新 Session,而 getSession(false) 只在已有会话时返回。过滤器里判断是否登录必须用 getSession(false),否则每个匿名访问都会被系统白送一个 Session,没有登录页也会被反复写会话,内存压力不必要地变大。

4.3 过滤器拦截:URL 白名单怎么配才不误伤静态资源

过滤器的拦截规则决定了整个系统的访问边界。一套最稳妥的规则是:登录页和登录接口放行,CSS、JS、图片放行,其余全部拦截;登录后再根据角色做功能级控制。放行规则配不好会出两个问题:一是样式全丢,二是登录页自己都被拦截,形成死循环。

@WebFilter("/*") public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 白名单:登录页、登录接口、静态资源 String uri = request.getRequestURI(); if (uri.endsWith("login.jsp") || uri.contains("/login") || uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".png") || uri.endsWith(".jpg")) { chain.doFilter(req, resp); return; } // 未登录一律踢回登录页 HttpSession session = request.getSession(false); if (session == null || session.getAttribute("currentUser") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }

这个过滤器有两个值得注意的地方。首先是白名单匹配方式,uri.endsWith(".css") 只匹配以 .css 结尾的路径,如果项目静态资源带版本号比如 style.css?v=1,这个写法会放行失败,样式照样被拦。稳妥做法是改成 uri.contains(".css") 或单独判断资源目录前缀。其次,过滤器只做登录态校验,不做功能级权限校验。学生登录后手敲 URL 访问 teacher/add_score.jsp 是能进去的,页面能不能操作全看 JSP 里有没有再判断一次角色。真要做得严,需要在过滤器里维护一张「角色到可访问路径」的映射表,这是你能写进论文的加分点。

5. 部署与改代码的踩坑清单:乱码、驱动、端口这些坎别反复过

5.1 登录和数据库全中文乱码:三个地方没对齐

现象:登录界面输入中文用户名后查不到数据,数据库里显示成问号,JSP 页面标题也花。

原因:乱码是编码链断裂。JSP 文件本身是 UTF-8,但 Servlet 读请求参数时没设编码,默认按 ISO-8859-1 解码;数据库连接 URL 没指定 characterEncoding;表的默认字符集不是 utf8mb4。三个环节只要一个不对,中文就花。

解决:逐一检查。JSP 头部写<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>;所有读参数的 Servlet 在方法开头加req.setCharacterEncoding("UTF-8");JDBC URL 里加useUnicode=true&characterEncoding=UTF-8,不管 MySQL 5.7 还是 8.x 都加。做完这三处,乱码基本绝迹。注意 setCharacterEncoding 必须在第一次读取参数前调用,放在后面无效是玄学,其实是 Servlet 规范规定请求参数只能解码一次。

5.2 JDBC 驱动与 MySQL 认证插件不匹配

现象:用 mysql-connector-java 5.1.x 连本地 MySQL 8.x,启动或第一次查询报Public Key Retrieval is not allowed,或者直接报Unable to load authentication plugin 'caching_sha2_password'。

原因:MySQL 8 默认用户认证插件是 caching_sha2_password,老驱动 5.1 根本不认识这个插件。

解决:最省事的是让驱动和服务器同代。MySQL 8 配 mysql-connector-java 8.0.x,MySQL 5.7 配 5.1.x。坚持老驱动连新库的话,连接 URL 加allowPublicKeyRetrieval=true&useSSL=false,同时把 MySQL 用户认证方式改回 mysql_native_password。注意这只能用于本机测试,生产上改账号插件会影响安全基线,不建议。另外 MySQL 8.4 开始默认移除了 mysql_native_password,老项目跑 8.4 以上基本只能换驱动。这就是这类 JSP 源码建议锁 MySQL 5.7 的原因。

5.3 Tomcat 启动即退出,窗口一闪而过没报错

现象:双击 startup.bat 窗口闪一下就没,访问 localhost:8080 直接拒连。

原因:最常见两类。第一是 JAVA_HOME 没配置,Tomcat 启动脚本找不到 JDK;第二是 8080 端口被占用,Catalina 抛端口冲突异常后直接退出。

解决:不要在桌面双击 startup.bat,改用命令行执行,窗口就不会闪。看到报错后去 Tomcat 的 logs 目录翻 catalina.out 或当天日期的 localhost 日志,问题定位具体到哪一行。端口占用就改 conf/server.xml 里的<Connector port="8080" ... />,改成 8081 或任意空闲端口,然后访问对应端口。JAVA_HOME 没设就加到系统环境变量,指向 JDK 安装根目录,注意不是 bin 子目录。

5.4 404 与 500:谁拦了你的请求,日志怎么定位

现象:访问http://localhost:8080/edu_mis/login.jsp返回 404,登录后跳转也 404;另一种是页面能开但样式全裸。

原因:404 绝大多数是路径没对齐。部署目录不叫 edu_mis,URL 和 webapps 下的目录名对不上;或者 Servlet 的 @WebServlet 路径和表单提交的路径不一致。500 是后端代码运行时异常,常见的是空指针或 JDBC 连接失败,异常栈里带着具体文件和行号。样式全裸属于过滤器把 CSS 请求也拦了,参照 4.3 的白名单排查。

解决:先确认部署目录名,URL 第一段必须和 webapps 下的目录名一致;再检查 WEB-INF/classes 下有没有编译出的 .class 文件,IDEA 导入时编译输出没配好的话 classes 是空的,Servlet 全部 404;JSP 报 500 就翻 Tomcat 的 logs/localhost.yyyy-mm-dd.log,看栈顶第一行,别从下往上找,第一行才是真实错误位置。

5.5 页面加载后自动刷新一次:一个真实会用上的技巧

现象:被问到频率很高——某个数据显示页,希望打开后自动刷新一次,把刚生成的数据带出来,但不想要定时刷新反复打断操作。

原因:这类页面常见于数据异步生成的场景,比如导入成绩提交后,列表页想自动加载最新结果。裸写location.reload()会死循环,因为 reload 后页面重新加载,又触发 onload,再 reload,浏览器直接卡死。

解决:用一次性标记,或者用 meta 延迟刷新。两种写法都给你:

<%-- 方式一:加 window 标记,保证只刷新一次 --%> <body onload="if(!window._refreshed){window._refreshed=true;location.reload();}"> <%-- 方式二:3 秒后刷新当前页,适合等后端处理完成 --%> <meta http-equiv="Refresh" content="3;url=course_list.jsp">

方式一的window._refreshed是一次性开关,刷新后内存里的标记保留,不会再触发第二次,这是防死循环的关键。方式二 content 里的第一个数字是秒数,省略 url 部分就刷新当前页面。真实项目我更推荐第二种,它给后端处理留了缓冲时间,逻辑也直观。这个技巧用到的地方不多,但真遇到时没有现成答案,值得记下来。

6. 从「能跑」到「能用」:把连接池、密码和异常提示改扎实

跑通只是第一步。这套源码给你的是骨架,想让它在答辩或实际维护中站得住,我建议按下面这个优先级动手改造,每个改动都不大,但收益明显。

第一个改动是密码存储。源码里明文或 MD5 直接存,改法是在注册时用 BCrypt 或 MD5 加盐后存储,登录时不再查WHERE password = ?,而是先按用户名把用户查出来,再用工具类比对哈希。这个改动只动 UserDao 和 LoginServlet 两个文件,但能在答辩时回答「密码安全怎么考虑」这个必问题。

第二个改动是数据库连接。DBUtil 里每次 getConnection 都是 DriverManager 直连,换成连接池以后性能和稳定性都上一个台阶。常见做法是引入 HikariCP,配置一个文件搞定:核心参数是 maximumPoolSize 设 10、minimumIdle 设 5,连接池预热在第一次请求时完成,之后每次取连接都不再有 TCP 建连开销。替换点只有 DBUtil 一个类,Servlet 和 DAO 完全不用动。

第三个改动是异常处理。现在 Servlet 里 SQLException 一旦抛出,Tomcat 就渲染整页红色异常栈,学生能看到 Java 报错,这既不好看也不安全。给系统加一个统一异常处理:Servlet 里 try-catch 后把错误信息 setAttribute 到一个提示页,或者用 web.xml 的 error-page 把 500 请求定向到友好页面。后者配置量最小,但前者能保留具体错误内容,适合排查问题。

第四个改动是 DAO 层去重。源码里每个 DAO 都是重复的写 PreparedStatement、setInt、executeUpdate 套路,抽一个 BaseDao,把这段模板代码收敛起来,新增表时少写一半。这个改造不改变任何功能,但代码量直接可见地变薄了,是答辩展示工程能力的好素材。

我自己的习惯是:拿到这类老源码先跑通,再按连接池、密码、异常这三项做一轮加固,最后才动业务功能。顺序反了容易踩坑——功能改到一半发现底层的数据库连接和异常处理是坏的,排查起来两头都乱。这套 JSP+Servlet+MySQL 的老架构虽然新项目里用得不多了,但把这份代码吃透,你再去看 Spring MVC 的处理流程会顺很多,因为 Servlet 的请求生命周期、转发与重定向、Session 管理这些底子全在里面。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 5:30:38

Flutter纯Dart实现彩票APP:本地预测+离线支付+状态治理

简介&#xff1a;这是一套基于Flutter开发的生产级彩票应用完整源码&#xff0c;面向移动端开发者及Flutter进阶学习者&#xff0c;解决彩票类App从UI构建、业务逻辑到支付集成的一站式开发需求。资源包含506个文件&#xff0c;主体为359个Dart代码文件&#xff08;涵盖注册登录…

作者头像 李华
网站建设 2026/10/7 5:30:35

2026大模型落地实操指南:从选型到部署的工程化避坑手册

1. 这不是一份“排行榜”&#xff0c;而是一张2026年大模型生态的实操导航图你点开这个标题&#xff0c;大概率不是想看又一份“XX大模型全球TOP10”的媒体通稿。我干这行十年&#xff0c;从GPT-2时代开始搭本地推理环境&#xff0c;到今天带团队落地工业级Agent系统&#xff0…

作者头像 李华
网站建设 2026/10/7 5:30:35

Agent-Reach:为智能体装上“手”的工具触达中间层设计实践

如果你跟我一样做过几个大模型落地项目&#xff0c;大概率遇到过这种尴尬场景&#xff1a;模型聊起天来头头是道&#xff0c;可一说到“帮我查一下快递”“把这份周报转成表格发给主管”&#xff0c;它就卡住了。不是模型不够聪明&#xff0c;而是它伸不出手——它只长了嘴&…

作者头像 李华
网站建设 2026/10/7 5:29:56

3A游戏引擎技术全景:图形、物理与脚本引擎核心原理与调优

1. 从玩家到开发者&#xff1a;3A游戏背后的引擎技术全景很多人第一次听到“游戏引擎”这个词&#xff0c;脑子里浮现的可能是Unity或者Unreal的编辑器界面&#xff0c;觉得那不过是个做游戏用的工具。但如果你真正拆开一款3A大作看它的运行时结构&#xff0c;会发现引擎远不止…

作者头像 李华
网站建设 2026/10/7 5:29:55

端侧推理引擎:跨越AI模型与边缘硬件的部署鸿沟

1. 什么是端侧推理引擎&#xff1a;不是“把模型搬上手机”那么简单“端侧推理引擎”这六个字&#xff0c;最近两年在AI工程圈里出现频率高得有点反常——不是出现在论文里&#xff0c;而是扎堆出现在招聘JD、芯片发布会PPT、甚至嵌入式工程师的茶水间闲聊中。但很多人一开口&a…

作者头像 李华
网站建设 2026/10/7 5:29:42

Caveman调试法:用最原始的方式让错误一目了然

“caveman”这个词&#xff0c;直译过来是“穴居人”&#xff0c;听起来跟现代软件开发八竿子打不着。但最近我在排查一个棘手的线上问题时&#xff0c;突然理解了为什么程序员圈子里会有人推崇一种“Caveman式调试法”——把错误信息用最大号字体砸到你脸上&#xff0c;用最原…

作者头像 李华