
简介这是一份基于 JSPServletJDBC 的学生请假信息管理系统课程设计源码面向 Java Web 方向的大学生适合作为课程设计或毕设参考也可用于学习传统 MVC 开发模式。系统按学生、辅导员、宿管员三类角色划分功能学生可填写请假单、查询个人请假审核状态与晚归记录辅导员可审核请假单、导出学生晚归记录并维护学生、班级、课程等基础信息宿管员可进行基础信息管理整体业务流程贴近真实校园管理场景。压缩包共 117 个文件其中 78 个 JSP 页面构成前端界面Java 源码与 class 文件对应后端 servlet 与工具类另有 SQL 数据库脚本、CSS/JS 样式脚本、JAR 依赖库和 Eclipse 项目配置文件资源总大小仅 2.25MB结构紧凑便于导入与二次修改。项目采用经典蓝色 UI前端 JSP 配合 JDBC 访问数据库登录信息明确学生/辅导员/宿管员账号分别为 1、2、3密码同为对应数字导入数据库即可运行。已有 2404 人浏览学习对需要快速搭建完整课设或理解 servletjsp 分层项目骨架的同学具有直接参考价值。1. 学生请假信息管理系统课程设计为什么是 JSPServletJDBC 这套老三样每年课程设计提交前一两个月被问得最多的 JavaWeb 项目学生请假信息管理系统一定排在前三。原因不难猜它的功能边界清晰——学生提交请假、辅导员审批、双方都能查状态技术栈又是教科书里最标准的 JSPServletJDBC一个学期讲的内容全都能装进去。选这个标题的人多半不是想做出什么惊人系统而是想要一个自己能讲清楚、能过答辩、还能在这段代码里学到点真东西的项目。但我要先说一个反直觉的结论这个课设的翻车点从来不在业务逻辑而在版本搭配、中文编码和请求转发这三件小事上。把这三件事提前处理好剩下的只是按部就班写代码。2. 打一个能跑通的最小骨架版本搭配、目录结构、web.xml 与第一个 JSP2.1 为什么课设选了它Servlet 是入口JDBC 是通道JSP 是页面很多同学上来就问能不能直接用 Spring Boot 写。我的建议是课设阶段别急着上框架。Spring Boot 确实省事但你交上去的是一份课程设计答辩老师大概率会追问“请求是怎么从页面到数据库的”“Tomcat 是怎么认识你的 Servlet 的”。这时候 Spring Boot 的内嵌 Tomcat 和自动配置反而成了黑匣子你很难一句话讲清楚请求链路。JSPServletJDBC 的经典三层里每一层都有明确的活JSP 负责把数据渲染成页面Servlet 负责接 HTTP 请求、调业务、做跳转JDBC 负责和 MySQL 打交道。教师端审批、学生端请假、状态查询全部落在这条链路上。这套代码写完之后你对“一次请求到底经历了什么”会有完整的概念这是框架帮你代劳时感受不到的。所以这个技术组合不是因为它新而是因为它“透明度高”。任何一个环节出错你都能从 Tomcat 日志和代码行里定位到具体位置。对你写简历、面试讲项目也是同样逻辑——原生 Servlet 的东西讲明白了框架对你的加分是几何级增长的。2.2 版本搭配别信玄学我常用的 JDK、Tomcat、MySQL 组合这个课设最容易在第一步就劝退人环境版本之间互相不兼容。我现在常用的搭配如下表按优先级排列场景JDKTomcatMySQL说明最稳妥首选JDK 8Tomcat 9.0.xMySQL 8.0教科书兼容性最好网上资料最全机器装了新版 JDKJDK 11Tomcat 9.0.xMySQL 8.0Tomcat 9 支持到 JDK 11 没问题只想轻量跑JDK 8Tomcat 8.5MySQL 5.7老环境驱动用 5.x 也可以不推荐JDK 17Tomcat 8.5MySQL 8.0JSP 编译经常报版本错误这里有一个常见误区版本越新越好。JDK 17 配新 Tomcat 本来可行但很多课程设计给的是老机器的实验室环境Tomcat 8.5 配 JDK 17 会出现 JSP 编译失败而 Tomcat 10 之后包名从 javax.servlet 变成了 jakarta.servlet你从网上复制的代码可能直接报“包不存在”。所以我的习惯是“先问清实验室版本再动工”如果自己搭环境无脑选第一行。2.3 最小工程长这样目录结构、web.xml 与一个能跑的 JSP先看一个能跑通的最小工程长什么样这里以 IDEA 的 Web 工程为例leave-system/ ├── src/ │ └── com/example/leave/ │ ├── servlet/ # 控制器接收请求、调 DAO、跳转 │ ├── dao/ # 数据访问层所有 JDBC 代码 │ ├── entity/ # 实体类Student、LeaveInfo │ └── filter/ # 拦截器登录校验、编码处理 ├── web/ │ ├── WEB-INF/ │ │ ├── lib/ # mysql-connector-java.jar 必须放这里 │ │ └── web.xml # Servlet 3.0/4.0 配置入口 │ ├── login.jsp # 入口页面 │ ├── add.jsp # 请假申请页 │ └── list.jsp # 请假列表页 └── .gitignore注意WEB-INF/lib这个目录JDBC 驱动 jar 只能放这里不能随手丢到web/根目录下否则 Tomcat 启动后找不到驱动类。如果你用 IDEA 的 Artifact 打包要确认lib目录被包含进了WEB-INF。这一步错了后面所有 JDBC 代码都报ClassNotFoundException。然后是web.xml这是整个工程的“总闸”?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-namestudent-leave-system/display-name !-- 欢迎页访问根路径时默认进入登录页 -- welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list !-- 如果你的 Servlet 用注解 WebServlet这里可以留空 如果兼容老写法就在 servlet 里逐个声明 -- /web-app这个文件的关键是把web-app的 version 属性和你用的 Tomcat 对上。Tomcat 9 对应 Servlet 4.0写version4.0能让 JSP 支持 EL 表达式和 JSTL 的默认行为。跑通验证也很简单启动 Tomcat浏览器访问http://localhost:8080/leave-system/能看到login.jsp的静态内容说明骨架没问题。先别急着写业务这一步稳了再往下走。3. 数据库与 JDBC 层请假表设计、连接参数和 DAO 的一手写法3.1 三张表把请假的账记清楚学生表、请假表与字段设计学生请假信息管理系统的命根子在数据库。很多课设翻车不是因为代码而是因为字段设计太随意请假状态用字符串存、时间用VARCHAR存、审批人信息不落表。到了答辩环节老师只要问一句“你如何统计某学生本学期请假次数”“如何查某个辅导员审批过哪些单”你就要为当时的偷懒买单。我一般把数据拆成三张表学生账号表、请假申请表再加一张可选的审批记录表课设简版可以合并进请假表。字段设计如下CREATE DATABASE IF NOT EXISTS leave_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE leave_db; -- 学生/教师账号表role 字段区分身份 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, name VARCHAR(20) NOT NULL COMMENT 姓名, password VARCHAR(64) NOT NULL COMMENT 密码, role TINYINT NOT NULL DEFAULT 1 COMMENT 1学生 2辅导员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT 账号表; -- 请假申请表 CREATE TABLE apply_leave ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 请假单ID, student_id INT NOT NULL COMMENT 申请人ID对应student.id, leave_type TINYINT NOT NULL DEFAULT 0 COMMENT 0事假 1病假 2其他, start_time DATETIME NOT NULL COMMENT 请假开始时间, end_time DATETIME NOT NULL COMMENT 请假结束时间, reason VARCHAR(200) NOT NULL COMMENT 请假事由, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审批 1已通过 2已驳回 3已撤销, apply_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 提交时间, audit_time DATETIME DEFAULT NULL COMMENT 审批时间未审批为NULL, audit_by INT DEFAULT NULL COMMENT 审批人ID未审批为NULL, KEY idx_student (student_id), KEY idx_status (status), CONSTRAINT fk_leave_student FOREIGN KEY (student_id) REFERENCES student(id) ) ENGINEInnoDB COMMENT 请假申请表;这里有几个字段设计是答辩加分项。一个是status用TINYINT而不是VARCHAR后端用常量对应 0/1/2/3数据库层还能加约束避免出现status已通过和status审批通过这种不一致。另一个是audit_time和audit_by允许 NULL没有审批前就是 NULL这比默认值填 0 干净得多。时间字段统一用DATETIME字符串时间排序、比较都是坑。3.2 JDBC 连接 MySQL驱动、URL 参数与 DBUtil 的写法JDBC 连接 MySQL 是整条链路上最容易出幺蛾子的地方。MySQL 5.7 和 8.0 的驱动类名不一样URL 参数也有讲究。我常用的连接工具类如下这段代码你可以直接抄package com.example.leave.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { // 注意characterEncoding、serverTimezone、allowPublicKeyRetrieval 三个参数缺一不可 private static final String URL jdbc:mysql://localhost:3306/leave_db ?useSSLfalse allowPublicKeyRetrievaltrue characterEncodingutf8 serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你自己的密码; static { try { // MySQL 8.x 驱动类名是 com.mysql.cj.jdbc.Driver Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动未找到请检查WEB-INF/lib); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 统一的关闭方法ResultSet - Statement - Connection反序关闭 public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }几个参数逐个说清楚characterEncodingutf8保证中文字段从数据库出来不是乱码serverTimezoneAsia/Shanghai是 MySQL 8 强制要求的时区参数不加会报 “The server time zone value” 的错误allowPublicKeyRetrievaltrue配合useSSLfalse解决 MySQL 8 默认认证插件导致的 “Public Key Retrieval is not allowed” 报错这个坑在第五章细讲。如果你用的是 MySQL 5.7驱动类名改回com.mysql.jdbc.DriverURL 里可以去掉serverTimezone。判断依据很简单你下载的mysql-connector-java是 8.x 还是 5.x8.x 必须用带cj的类名。3.3 DAO 层PreparedStatement、try-with-resources 与 ResultSet 映射数据库连接有了接下来是 DAO。很多同学喜欢在 JSP 或者 Servlet 里直接写 JDBC代码混在一起出了问题没法查。我的习惯是每个实体类配一个 DAO比如LeaveDao.java负责所有和请假表相关的 SQL。看一个查询学生请假列表的方法package com.example.leave.dao; import com.example.leave.entity.LeaveInfo; import java.sql.*; import java.util.ArrayList; import java.util.List; public class LeaveDao { // 查询某个学生的全部请假记录按提交时间倒序 public ListLeaveInfo listByStudent(int studentId) { String sql SELECT * FROM apply_leave WHERE student_id ? ORDER BY apply_time DESC; ListLeaveInfo result new ArrayList(); // try-with-resources 自动关闭 Connection/Statement/ResultSet省去 finally try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); // 占位符从 1 开始禁止用字符串拼接 try (ResultSet rs ps.executeQuery()) { while (rs.next()) { LeaveInfo leave new LeaveInfo(); leave.setId(rs.getInt(id)); leave.setStudentId(rs.getInt(student_id)); leave.setLeaveType(rs.getInt(leave_type)); leave.setStartTime(rs.getTimestamp(start_time)); leave.setEndTime(rs.getTimestamp(end_time)); leave.setReason(rs.getString(reason)); leave.setStatus(rs.getInt(status)); leave.setApplyTime(rs.getTimestamp(apply_time)); // audit_time 允许为 NULL取值前先判空 Timestamp auditTime rs.getTimestamp(audit_time); if (auditTime ! null) { leave.setAuditTime(auditTime); } result.add(leave); } } } catch (SQLException e) { // 课设阶段先打印生产环境应使用日志框架 e.printStackTrace(); } return result; } }这段代码有三个值得坚持的习惯。第一永远用PreparedStatement而不是Statement拼接字符串?占位符能防 SQL 注入也是答辩时老师爱问的点。第二ResultSet的取值方法要和数据库字段类型匹配DATETIME用getTimestampTINYINT用getInt类型错位会报SQLException。第三涉及可空字段时一定判空直接rs.getTimestamp(audit_time)返回 null 没问题但把它塞进实体类的Timestamp属性要注意后续页面的空指针。写到这里可以验证一下链路写一个简单的 main 方法调listByStudent(1)能查出数据说明 JDBC 通了。先不要往下写 Servlet不然出问题时分不清是数据库问题还是接口问题。4. Servlet 与请假状态流转doPost 里的参数解析、审批与登录控制4.1 请求是怎么从 JSP 走到数据库的doGet/doPost 与 forward/redirectJSP 页面里的表单提交后请求流转链路是这样的JSP 表单的 action 指向一个 Servlet 地址Tomcat 根据WebServlet注解找到对应类调用doGet或doPost方法Servlet 从request里取参数调 DAO 访问数据库最后通过转发forward或重定向sendRedirect把结果送回页面。这里最容易糊涂的是 forward 和 redirect 的选择。我的原则是查询列表用 forward数据变更用 redirect。原因很简单——用户提交一张请假单如果直接 forward 回列表页地址栏还停在提交地址用户一按 F5表单又提交了一遍数据库里多出一条重复数据。这是经典 POST/Redirect/GET 问题后面的避坑章还会提。看一个新增请假的 Servlet 核心方法package com.example.leave.servlet; import com.example.leave.dao.LeaveDao; import com.example.leave.entity.Student; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; WebServlet(/leave/add) public class LeaveAddServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { // 1. 从 Session 取当前登录用户没有则回登录页 HttpSession session req.getSession(false); Student student (Student) session.getAttribute(loginUser); if (student null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 2. 参数解析request 里取出来的都是字符串按需转换 String leaveType req.getParameter(leaveType); // 0事假 1病假 String startTime req.getParameter(startTime); // yyyy-MM-dd HH:mm:ss String endTime req.getParameter(endTime); String reason req.getParameter(reason); // 3. 基础校验时间倒挂直接退回填写页errortime 让页面提示 if (startTime null || endTime null || startTime.compareTo(endTime) 0) { resp.sendRedirect(req.getContextPath() /add.jsp?errortime); return; } // 4. 调用 DAO 插入数据拿到自增主键 LeaveDao dao new LeaveDao(); int newId dao.insert( student.getId(), Integer.parseInt(leaveType), startTime, endTime, reason); // 5. 写完后重定向到列表页防止刷新重复提交 resp.sendRedirect(req.getContextPath() /leave/list); } }注意两个细节。req.getSession(false)里的false表示“拿不到 Session 就返回 null”而不是创建一个新 Session用在登录校验上语义更准确。req.getContextPath()是获取应用上下文路径重定向 URL 必须带上它否则部署到http://localhost:8080/leave-system/这种子路径时重定向会跳错地方。4.2 请假状态机待审批、通过、驳回、撤销的四态流转与并发安全请假系统里最关键的业务逻辑是状态流转。我的状态设计是四个值状态值含义学生端可见操作辅导员端可见操作0待审批撤销通过 / 驳回1已通过无无2已驳回无无3已撤销无无这个状态机有个常见的翻车点撤销操作只允许在待审批状态下进行。很多实现只在 Servlet 里用 if 判断比如if (status 0) { cancel }这在单用户测试时没问题但如果学生和辅导员同时操作同一条记录Servlet 里读到的状态可能是旧的导致数据被覆盖。稳妥的做法是把状态约束写进 SQL 条件里public int cancelIfPending(int leaveId, int studentId) { String sql UPDATE apply_leave SET status 3 WHERE id ? AND student_id ? AND status 0; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, leaveId); ps.setInt(2, studentId); return ps.executeUpdate(); // 返回 0 说明状态已被他人变更 } catch (SQLException e) { e.printStackTrace(); return 0; } }executeUpdate返回 0 时说明没有任何行被更新——要么数据不存在要么状态已经不是 0。这种“条件更新”是保证并发安全最简单的方式也是答辩时可以拿出来讲的点。审批操作同理改成WHERE id ? AND status 0辅导员就不用担心批了一张已被学生撤销的单子。4.3 登录态与权限控制一个 WebFilter 拦截所有 /leave/* 请求如果每个 Servlet 开头都写一遍登录校验代码会非常啰嗦也容易漏。我用一个 Filter 统一拦截leave/*路径package com.example.leave.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.*; import java.io.IOException; WebFilter(/leave/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 没有 Session 或者 Session 里没有登录用户一律回登录页 HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 已登录则放行继续执行后面的 Servlet chain.doFilter(request, response); } }这个 Filter 能挡住所有未登录访问但有个边界要注意login.jsp、登录 Servlet 本身不能放在/leave/*下面否则会出现死循环——登录页也要被登录检查拦截用户永远进不去。我的做法是登录页直接放根目录登录接口用WebServlet(/login)不归这个 Filter 管。辅导员身份的操作同理可以在 Filter 里再判断loginUser.getRole() 2或者单独给/leave/audit/*加一个角色校验 Filter。到这里请假提交、列表查询、审批、撤销这条主链路已经通了。JSP 页面就是把这些接口的数据渲染出来比如学生个人信息展示页面既然能查到该生全部请假记录就可以顺带把待审批和已通过的记录分开列表展示状态值用 EL 表达式映射成中文这部分篇幅不多但页面细节决定了整个项目像不像“系统”别只堆一排数字。5. 避坑与常见问题排查课程设计里最容易翻车的 5 个环节5.1 中文乱码的三处源头页面、请求响应与数据库连接现象JSP 页面显示中文正常但提交到数据库后变成???或者页面直接显示一堆乱码。原因中文乱码本质是三层编码不一致——JSP 文件本身的编码、HTTP 请求响应的编码、数据库连接的编码任何一层用的是默认 ISO-8859-1就会乱。解决三层统一成 UTF-8。JSP 文件顶部写% page contentTypetext/html;charsetUTF-8 %Servlet 里在读取任何参数之前调用req.setCharacterEncoding(UTF-8)返回页面时写resp.setContentType(text/html;charsetUTF-8)JDBC URL 里带characterEncodingutf8。建表时统一用utf8mb4这三处搞定全链路不存在乱码。5.2 连 MySQL 8 报 Public Key Retrieval is not allowed现象第一次执行 JDBC 查询时抛异常Caused by: java.sql.SQLException: Public Key Retrieval is not allowed。原因MySQL 8 默认的认证插件是caching_sha2_password非 SSL 连接下客户端要获取服务器公钥而驱动默认不允许自动获取。这个报错只出现在 MySQL 8 加新版驱动组合下。解决JDBC URL 追加两个参数allowPublicKeyRetrievaltrueuseSSLfalse也就是我在 DBUtil 里写的那套。如果你不想改代码也可以在 MySQL 里把用户认证插件改回旧版ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;改数据库用户的方式对老代码兼容性更好但课设阶段我更推荐改 URL不动数据库配置更省事。5.3 JSP 编译报 500Tomcat 与 JDK 版本不匹配的翻车现场现象Tomcat 启动成功访问 JSP 页面却报 500堆栈里有Unable to compile class for JSP或者UnsupportedClassVersionError。原因JSP 在运行时会被翻译成 Java 类再编译Tomcat 自带编译器和 JDK 版本不匹配时就会翻车。常见组合是 JDK 17 配 Tomcat 8.5或者是 Tomcat 10 配网上复制的javax.servlet代码——Tomcat 10 已经改用jakarta.servlet包名老代码全部编译不过。解决直接换版本组合最省心的是回到 JDK 8 加 Tomcat 9。如果机器上 JDK 版本改不了就在 IDEA 里给项目单独配一个 SDKProject Structure 里把 Project SDK 和 Module SDK 都指到 JDK 8不一定非要改系统环境变量。5.4 刷新页面重复提交用 PRG 而不是在 JSP 里刷一次现象提交请假单后按 F5 刷新页面弹提示“重新提交表单”数据库里出现多条相同的请假记录。原因表单是 POST 请求Servlet 处理完如果用的是forward转发到列表页浏览器地址栏里还是 POST 地址刷新自然把 POST 又执行了一遍。网上有人建议让 JSP 页面加载完后刷新一次来清掉请求那是治标不治本——请求已经执行了。解决数据变更的操作Servlet 里一律用sendRedirect跳转后浏览器发起的是全新 GET 请求F5 刷新只是重新查一次列表不会新增数据。这是 PRGPost-Redirect-Get模式的简化版也是“写操作重定向读操作转发”这条规则的实际意义。5.5 找不到驱动类jar 放错位置与 8.x 驱动类名变更现象Tomcat 启动时或第一次访问数据库时报ClassNotFoundException: com.mysql.jdbc.Driver。原因两种情况。一是mysql-connector-java.jar没放进WEB-INF/lib而是放在了 IDE 的某个外部目录运行时 Tomcat 根本看不见二是代码里写的是旧驱动类名com.mysql.jdbc.Driver但 jar 是 8.x8.x 的类名已经变成com.mysql.cj.jdbc.Driver。解决先确认 jar 位置——IDEA 里把它复制到web/WEB-INF/lib且确认 Artifact 输出里有这个 jar。再看代码里的Class.forName字符串MySQL 8 用com.mysql.cj.jdbc.Driver5.x 用com.mysql.jdbc.Driver。如果两个都确认了还报错删除 Tomcat 的work目录再重启清一下缓存。6. 给课设加分一个统一过滤器、一条演示路径和一晚答辩预案先说一个能立刻提升代码质量的改动把 4.3 的登录 Filter 扩展成“编码 登录”二合一在chain.doFilter之前加两行request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)这样所有 Servlet 里不再需要重复设置编码代码瞬间瘦身。这个 Filter 在答辩时特别好讲——从“一个请求进来先过 Filter 再进 Servlet”切入能自然带出过滤器链、拦截范围、白名单设计三个考点。第二个加分点是把连接池换进去。课设阶段用DriverManager没问题但如果你想展现「我知道生产环境不能这么写」可以加一个 HikariCP 的配置文件把dataSource.getConnection()换成从连接池取连接只改DBUtil一个类其他 DAO 全都不用动。答辩被问“你觉得这个系统哪里可以优化”这就是现成答案。第三个是演示路径。我自己带课设时要求每个项目准备固定的演示顺序先用学生账号登录展示请假列表页的权限隔离提交一条请假申请当场在数据库里SELECT出这条记录证明数据落库然后切换辅导员账号审批通过这条申请最后切回学生账号刷新列表页状态从“待审批”变成“已通过”。这套流程走一遍Servlet、JDBC、状态机、Session 全部覆盖比临时乱点有说服力。数据库里记得提前造好数据两个学生账号、一个辅导员账号、几条不同状态的请假记录别等到演示现场才插入。最后一个教训是很多人会犯的遇到SQLException直接空捕获或者把e.printStackTrace()注释掉假装没发生。课设代码里打印可能有失优雅但生产环境排查问题靠的就是异常堆栈。我会在项目里保留最简方案控制台打印只要它在演示时不炸答辩时老师问你哪里最容易出错你能打开控制台指着报错讲出原因这本身就是加分。这个习惯我保留到现在也希望帮到你。本文还有配套的精品资源点击获取