多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于JSP+Servlet+JDBC的学生请假管理系统课程设计与实现

基于JSP+Servlet+JDBC的学生请假管理系统课程设计与实现 简介基于JSPServletJDBC技术栈的学生请假信息管理系统课程设计源码包面向计算机专业大学生课程设计或毕业设计场景覆盖学生请假、辅导员审批、宿管员角色三条业务主线。系统采用经典蓝色UI已内置学生、辅导员、宿管员三类登录账号支持请假单填写与审核、个人及全院晚归记录查询与导出、学生/班级/课程信息管理等完整功能可按SQL脚本导入数据库后直接运行。资源共117个文件以78个JSP页面为主体配套Java源码、SQL建库脚本、jar依赖库、CSS/JS前端资源及Eclipse工程配置文件压缩包仅2.25MB结构清晰便于二次开发。已有2404人学习适合需要快速获取可运行课设项目并理解ServletJDBC分层开发的初学者。1. 基于 JSPServletJDBC 的学生请假信息管理系统课程设计从选题到答辩的全链路拆解如果你正在为 JavaWeb 课程设计选题发愁或者已经选了请假管理系统但不知道从哪下手这篇文章能帮你少走两周弯路。基于 JSPServletJDBC 实现学生请假信息管理系统是 JavaWeb 课程设计里最经典、也最适合练手的选题之一——业务不复杂但五脏俱全覆盖了前端页面、后端控制、数据库交互的完整闭环。它既能满足课程设计“必须用上 JSP、Servlet、JDBC”的硬性要求又不会像商城系统那样动辄几十张表一个人在一周内完全可以做完并写清楚设计文档。这个系统要解决的核心问题是学生在线提交请假申请辅导员或系主任审批过程中涉及的角色、状态流转、时间冲突判断都是真实业务里最常见的信息管理场景。适合正在做 JavaWeb 课程设计的在校学生也适合想快速回顾 JSPServletJDBC 技术栈的从业者。下面我按自己带课程设计的实际经验把从技术选型到答辩演示的每一步都拆给你看。2. 技术底座为什么这套系统的骨架必须是 JSPServletJDBC 三者协同2.1 三层架构在请假系统中的职责切分JSPServletJDBC 的组合不是凭空规定的它对应着 Web 开发里最经典的 Model1/Model2 演进结果。在请假系统里Servlet 承担控制器角色负责接收浏览器发来的请求、解析参数、调用业务逻辑、决定跳转到哪个 JSP 页面JSP 承担视图角色只负责展示数据和接收用户输入JDBC 承担模型角色负责和 MySQL 数据库打交道执行插入请假记录、更新审批状态、查询学生列表等操作。用一个最常见的“学生提交请假申请”请求举例。学生在apply.jsp表单里填写请假类型、开始时间、结束时间、请假原因点击提交后表单数据通过 POST 请求发送到ApplyServlet。Servlet 里先用request.getParameter()取出表单字段做基础校验比如结束时间不能早于开始时间然后调用ApplicationDao中的insert方法。ApplicationDao内部通过 JDBC 的PreparedStatement执行 INSERT SQL把数据写入t_application表。写入成功后 Servlet 跳转到success.jsp或者重定向到查询列表的ListServlet。整个过程数据流清晰哪一层出了问题都能快速定位这就是课程设计评委最想看到的“结构清晰”。2.2 为什么不用 SSM 或 Spring Boot很多同学会问现在企业都在用 Spring Boot课程设计能不能直接用能用但你要先搞清楚这门课的目标。课程设计的题目明确写着“基于 JSPServletJDBC”这就是在强制你使用原生 JavaWeb 技术栈目的是让你理解 HTTP 请求从浏览器到服务器再到数据库的完整流转过程而不是被 Spring 的自动装配和注解魔法包住。用 Ssm 或用 Spring Boot 做一个请假系统你会被 IoC 容器、Mapper 代理、自动配置这些东西分散注意力反而说不清楚“一个请求到底经历了什么”。我见过 A 同学用 Spring Boot 做了个非常漂亮的请假系统答辩时被老师问“Servlet 生命周期是怎样的”“Filter 和 Interceptor 的区别是什么”他答不上来——因为他的项目里根本没有手写 Servlet。课程设计本质上是一次技术摸底用原生 JSPServletJDBC 能把底层原理展示得明明白白这是加分项不是减分项。2.3 JDBC 连接 MySQL 的最小可用代码既然是 JDBC 驱动持久层第一步就是把数据库连接这件事做对。通常我会让 A 同学建一个DBUtil.java工具类用静态代码块加载驱动用ThreadLocal或简单静态方法提供连接获取和关闭。下面是课程设计里我建议的最小可用版本import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/leave_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception e) { e.printStackTrace(); } } } } }这段代码的逻辑分三层静态代码块在类加载时执行一次驱动注册Class.forName(com.mysql.cj.jdbc.Driver)是让 JDBC 驱动管理器识别 MySQL 驱动getConnection()每次调用都创建一个物理连接close()用可变参数接收 Connection、Statement、ResultSet统一关闭避免泄漏。三个参数里最容易踩坑的是serverTimezoneAsia/ShanghaiMySQL 8.x 版本不设置时区会报The server time zone value Öйú is unrecognized错误。字符集编码characterEncodingutf8必须和 JSP 页面pageEncodingUTF-8保持一致否则中文乱码问题会纠缠你整个开发周期。useSSLfalse是关闭 MySQL 8.x 默认的 SSL 警告。3. 需求建模与数据库设计把“请假”这个动作拆成四张表3.1 角色权限与用例梳理动手写代码前必须先把需求弄清楚。一个完整的学生请假系统至少涉及三种角色学生、辅导员、系主任管理员。学生能提交请假申请、查看自己的请假记录、撤销未审批的申请辅导员能查看所带班级学生的请假申请、审批或驳回系主任或管理员能查看全系统所有请假记录、按状态统计、导出报表。在课程设计的答辩环节评委最常问的问题就是“你的权限是怎么控制的”。所以设计阶段就要决定用 Filter 拦截还是每个 Servlet 里手动判断。我的建议是两层配合——Filter 统一做登录态检查Servlet 内根据 session 里的role字段做具体操作授权。比如ApproveServlet在执行审批前判断session.getAttribute(role).equals(teacher)不是辅导员直接跳转到错误页。这种粗粒度加细粒度的组合既简单又能答上老师的提问。3.2 四张核心表的字段设计与 SQL 建表数据库设计是整个系统的地基。请假系统核心表我认为四张就够了学生表、教师表、请假申请表、审批记录表。学生表和教师表可以合并成用户表加角色字段但课程设计为了讲解清晰分开建模反而更容易展示表关系。请假申请表是核心中的核心至少包含以下字段申请编号、学生编号、请假类型病假/事假/公假、开始时间、结束时间、请假天数、请假原因、状态字段、提交时间。审批记录表记录每一次审批动作的审批人、审批意见、审批时间和审批结果状态。下面是可以直接执行的建表 SQLMySQL 8.x 和 5.7 都兼容CREATE DATABASE IF NOT EXISTS leave_db DEFAULT CHARACTER SET utf8mb4; USE leave_db; CREATE TABLE t_student ( student_id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL COMMENT 学号, student_name VARCHAR(30) NOT NULL, class_name VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL ) ENGINEInnoDB; CREATE TABLE t_teacher ( teacher_id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) UNIQUE NOT NULL, teacher_name VARCHAR(30) NOT NULL, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT teacher ) ENGINEInnoDB; CREATE TABLE t_application ( app_id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, leave_type VARCHAR(20) NOT NULL COMMENT 病假/事假/公假, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, leave_days DECIMAL(5,1) NOT NULL, reason VARCHAR(500) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待审批 1已批准 2已驳回 3已撤销, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_app_student FOREIGN KEY (student_id) REFERENCES t_student(student_id) ) ENGINEInnoDB; CREATE TABLE t_approval ( approval_id INT PRIMARY KEY AUTO_INCREMENT, app_id INT NOT NULL, teacher_id INT NOT NULL, approval_comment VARCHAR(300), approval_status TINYINT NOT NULL, approval_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_app_id FOREIGN KEY (app_id) REFERENCES t_application(app_id), CONSTRAINT fk_app_teacher FOREIGN KEY (teacher_id) REFERENCES t_teacher(teacher_id) ) ENGINEInnoDB;这段 DDL 有四个设计要点。第一t_application表用statusTINYINT 字段而不是字符串节省空间且在 Java 里用int判断状态更直观。第二leave_days用 DECIMAL(5,1) 而不是 INT因为请假天数可能是 1.5 天这种半天的假。第三外键约束fk_app_student一定要加课程设计答辩时老师会检查你有没有建立表关系。第四create_time用 DATETIME DEFAULT CURRENT_TIMESTAMP 让数据库自动填充少写一行 Java 代码。另外注意所有表名都加了t_前缀避免和 MySQL 保留字冲突。3.3 请假天数自动计算的两种方案计算请假天数是一个表格里没体现但业务里绕不开的逻辑。方案一是 Java 代码里手动计算通过java.time.temporal.ChronoUnit计算两个日期之间的天数差。方案二是直接用 SQL 的DATEDIFF函数在 INSERT 时算好再落库。我建议用方案一因为课程设计评分看重代码逻辑的展示Java 里写日期计算比 SQL 里一行函数更显工作量也方便在 Service 层做边界判断。import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; public class LeaveDaysCalculator { public static double calculate(LocalDateTime start, LocalDateTime end) { if (end.isBefore(start)) { throw new IllegalArgumentException(结束时间不能早于开始时间); } long days ChronoUnit.DAYS.between(start.toLocalDate(), end.toLocalDate()); long hours ChronoUnit.HOURS.between(start, end) % 24; if (hours 0) { return days 0.5; } return days; } }这段计算逻辑的关键在于处理“半天假”的场景。ChronoUnit.DAYS.between只算两个日期的自然日差值早上 8 点到晚上 6 点是 0 天但实际是 1 天假所以用ChronoUnit.HOURS.between % 24取出不足一天的小时数大于 0 就加 0.5 天。这个工具类建议单独放一个包后续审批、统计功能都可能复用。4. 编码实现全流程JSP 页面、Servlet 控制与 DAO 持久层的完整串联4.1 后端分层结构从包名到类的职责规划写代码之前先建好包结构。我会让 A 同学按照com.xxx.leave的父包组织servlet包存放所有 Servlet 控制器dao包存放 JDBC 数据访问类entity包存放学生、请假申请等实体类util包存放 DBUtil 这样公共工具类filter包存放编码过滤器。实体类用纯 POJO字段和数据库表一一对应必须实现Serializable接口课程设计里用到 Session 传递对象时会用到。需要注意的是JSP 文件建议统一放在WebContent目录下按角色分子文件夹比如student/、teacher/、admin/不能放任都在根目录乱放。Servlet 的WebServlet注解映射路径要有规律比如student/apply、student/list、teacher/approve这样在 JSP 页面的表单 action 里写路径也不容易出错。JSP 页面建议放在WEB-INF目录外因为课程设计一般不用复杂的视图转发控制直接让 Servletresponse.sendRedirect(student/list.jsp)就好如果你用了WEB-INF下的视图每个跳转都得走RequestDispatcher路径一多自己先乱。4.2 学生提交请假申请从 JSP 表单到 DAO 插入先看前端 JSP 表单这是学生用户直接接触的界面字段要和t_application表对应% page contentTypetext/html;charsetUTF-8 languagejava % html head title提交请假申请/title /head body h2学生请假申请/h2 form action${pageContext.request.contextPath}/student/apply methodpost table tr td请假类型/td td select nameleaveType option value病假病假/option option value事假事假/option option value公假公假/option /select /td /tr tr td开始时间/td tdinput typedatetime-local namestartTime required/td /tr tr td结束时间/td tdinput typedatetime-local nameendTime required/td /tr tr td请假原因/td tdtextarea namereason rows4 cols30 required/textarea/td /tr tr td colspan2button typesubmit提交申请/button/td /tr /table /form /body /htmldatetime-local是 HTML5 的日期时间选择器提交的值形如2025-05-20T08:00这个 T 需要处理。${pageContext.request.contextPath}是拿到当前应用的根路径防止部署时上下文路径变化导致请求 404。对应的 Servlet 代码如下package com.xxx.leave.servlet; import com.xxx.leave.dao.ApplicationDao; import com.xxx.leave.entity.Student; import com.xxx.leave.util.LeaveDaysCalculator; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; WebServlet(/student/apply) public class ApplyServlet extends HttpServlet { private ApplicationDao applicationDao new ApplicationDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); HttpSession session request.getSession(); Student student (Student) session.getAttribute(loginUser); if (student null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } String leaveType request.getParameter(leaveType); String startTimeStr request.getParameter(startTime).replace(T, ); String endTimeStr request.getParameter(endTime).replace(T, ); String reason request.getParameter(reason); DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm); LocalDateTime startTime LocalDateTime.parse(startTimeStr, formatter); LocalDateTime endTime LocalDateTime.parse(endTimeStr, formatter); double leaveDays LeaveDaysCalculator.calculate(startTime, endTime); boolean success applicationDao.insert(student.getStudentId(), leaveType, startTimeStr, endTimeStr, leaveDays, reason); if (success) { response.sendRedirect(request.getContextPath() /student/list); } else { request.setAttribute(errorMsg, 提交失败请重试); request.getRequestDispatcher(/student/apply.jsp).forward(request, response); } } }这段 Servlet 里有两个关键点。一是request.setCharacterEncoding(UTF-8)必须放在getParameter()之前否则 POST 请求的中文参数会乱码前提是 JSP 页面也是 UTF-8 编码且数据库连接串带了characterEncodingutf8。二是replace(T, )的处理HTML 的datetime-local提交格式是 ISO 格式带 T而 MySQL 的 DATETIME 字段需要空格分隔所以入库前要转换。DateTimeFormatter的解析必须和转换后的字符串格式完全匹配否则抛DateTimeParseException。异常处理这里偷懒没写 try-catch实际上线要捕获并跳转到统一错误页课程设计可以口头说明。DAO 层ApplicationDao的核心插入方法package com.xxx.leave.dao; import com.xxx.leave.util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class ApplicationDao { public boolean insert(int studentId, String leaveType, String startTime, String endTime, double leaveDays, String reason) { String sql INSERT INTO t_application (student_id, leave_type, start_time, end_time, leave_days, reason, status) VALUES (?, ?, ?, ?, ?, ?, 0); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); ps.setString(2, leaveType); ps.setString(3, startTime); ps.setString(4, endTime); ps.setDouble(5, leaveDays); ps.setString(6, reason); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } }这段代码是最标准的手写 JDBC 模板。try-with-resources 语法是 Java 7 引入的Connection 和 PreparedStatement 实现了 AutoCloseable 接口可以自动关闭不需要在 finally 里手工释放。占位符?必须用setXXX方法按顺序赋值第一个?对应 student_id。status字段直接写死在 SQL 里为 0表示新提交的申请是待审批状态比在 Java 代码里再 set 一个参数更直观。返回值用executeUpdate() 0判断插入是否影响了一行以上的记录。4.3 辅导员审批功能事务处理的必要性与实现审批是整个系统里业务价值最高、也最容易体现技术水平的模块。辅导员打开待审批列表看到一条申请点击同意或驳回系统要同时干两件事更新t_application表的 status 字段往t_approval审批记录表里插入一条审批历史。这两步必须在一个事务里完成——如果只更新状态但没写审批记录或者写了审批记录但状态没更新数据就乱套了。错误的写法是分别在 DAO 里调两个 update 和 insert 方法这种方式每一步自动提交中途任何一步失败都会造成脏数据。正确做法是在 DAO 里写一个approve方法手动关闭自动提交用同一连接执行两条 SQLpublic boolean approve(int appId, int teacherId, int status, String comment) { String updateSql UPDATE t_application SET status ? WHERE app_id ?; String insertSql INSERT INTO t_approval (app_id, teacher_id, approval_comment, approval_status) VALUES (?, ?, ?, ?); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(updateSql)) { ps1.setInt(1, status); ps1.setInt(2, appId); ps1.executeUpdate(); } try (PreparedStatement ps2 conn.prepareStatement(insertSql)) { ps2.setInt(1, appId); ps2.setInt(2, teacherId); ps2.setString(3, comment); ps2.setInt(4, status); ps2.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }事务操作的套路要记牢setAutoCommit(false)打开事务两条 SQL 执行成功后commit()提交任何一个executeUpdate抛异常就rollback()回滚。finally 里关闭连接前要把setAutoCommit(true)恢复默认值这是连接池场景下必备的习惯虽然你这里用的是DriverManager每次新建连接但好习惯从课程设计就开始养成。这个approve方法中 status 参数既用于更新申请状态又记录审批结果其实更规范的做法是审批记录里存独立的状态枚举课程设计里保持和主表一致也能说得通。4.4 查询列表用 List 集合传参到 JSP 展示学生查看自己的请假记录是最基本的功能也是会把数据从后端带到页面的典型场景。Servlet 里先通过session里的学生 ID 调 DAO 查出 List再用request.setAttribute把 List 放进去最后forward到 JSP 页面。这里区别于重定向的原因是 forward 能在同一次请求内携带属性而 sendRedirect 会丢失。看代码WebServlet(/student/list) public class ListApplicationServlet extends HttpServlet { private ApplicationDao applicationDao new ApplicationDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } Student student (Student) session.getAttribute(loginUser); ListApplication list applicationDao.findByStudentId(student.getStudentId()); request.setAttribute(appList, list); request.getRequestDispatcher(/student/list.jsp).forward(request, response); } }DAO 层的findByStudentId方法用WHERE student_id ?查询并用ORDER BY create_time DESC排序。JSP 接收端把request.getAttribute(appList)通过 JSTL 的c:forEach循环输出。如果你没用 JSTL也可以直接Scriptlet写 Java 循环但不推荐——页面上 Java 代码多了答辩时老师会扣分。记住一个原则JSP 里不允许出现import和业务逻辑只允许 JSTL 标签和 EL 表达式。5. 避坑指南JavaWeb 请假系统里最常见的 5 个翻车现场5.1 现象数据库中文全部变成问号页面展示乱码这是我见过课程设计里最高频的翻车点。A 同学写完第一个插入功能发现数据库里中文全是???控制台还报java.sql.SQLException: Incorrect string value。原因不只有一个JSP 页面本身不是 UTF-8 编码、Servlet 里没调setCharacterEncoding、数据库连接串缺characterEncodingutf8、MySQL 表字段不是 utf8mb4。解决方法是四处一起改。JSP 文件第一行写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 的doPost和doGet方法开头加request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)JDBC 连接串加?characterEncodingutf8useUnicodetrue建表统一用ENGINEInnoDB DEFAULT CHARSETutf8mb4。此外可以写一个全局编码 Filter把所有请求响应统一设置编码一劳永逸。5.2 现象页面报错 404URL 路径怎么看都不对404 的排查路径最考验耐心。Eclipse 或 IDEA 里部署到 Tomcat 后访问地址通常是http://localhost:8080/leave_system/student/apply这里的leave_system是你 Web 项目的上下文路径。如果你在 JSP 的 form action 里写死了/student/apply而不是用request.getContextPath()部署后就会 404。另一个坑是写WebServlet(/student/apply)时如果你项目里有多个 Module 或者改过部署名上下文路径会和你本地不一致。最稳妥的写法是用${pageContext.request.contextPath}拼接所有路径包括 form action、sendRedirect和forward。还有一个小坑Servlet 注解不能少写前导斜杠WebServlet(student/apply)会导致 Tomcat 启动直接报错。5.3 现象Session 里取不到登录用户页面一直跳回登录页这个问题的表现是页面每次跳转都被拦截到 login.jsp登录成功后也无济于事。原因多半是你登录成功后在 Session 里存的 key 和后续 Servlet 里取用的 key 不一致。比如登录 Servlet 里session.setAttribute(student, student)后面查询列表却写session.getAttribute(loginUser)那当然取不到。另一个原因是重定向和 forward 混用。查列表用了response.sendRedirect()而sendRedirect是两次请求如果你登录成功用了 redirect 到列表页列表页的 Servlet 在新的请求里重新读 session此时 session 还在所以没问题但如果你在登录 Servlet 里用的是 forward且 forward 到的 JSP 里写 Java 代码取 session那么表单重复提交时会丢失部分数据。统一约定登录信息统一用loginUser作为 key所有 Servlet 从 session 取角色判断时写session.getAttribute(loginUser)注意 登录成功用 redirect 防止表单重复提交。5.4 现象用 PreparedStatement 反而报 SQL 语法错误有些同学图省事把参数直接拼成字符串再传给Statement执行这首先有 SQL 注入风险但反过来用PreparedStatement也偶尔踩坑。最常见的是把setString(3, startTime)传入带 T 的字符串MySQL 解析不了。另一种是leave_days用setDouble传 1.5但数据库字段是 DECIMAL如果 sql_mode 设置了严格模式精度对不上会报Data truncation。解决方法日期时间字符串在进入 DAO 之前统一格式化为yyyy-MM-dd HH:mm请假天数用setObject或者setBigDecimal。还有一点MySQL 8.x 对LIMIT ?之类的参数使用setInt没问题但如果你在ORDER BY create_time DESC LIMIT ?里用了setString会报Incorrect arguments to mysqld_stmt_execute。5.5 现象Tomcat 配置数据源失败连接直接拒绝课程设计如果要求用 JNDI 数据源而不是DriverManager坑更多。最常见的是javax.naming.NameNotFoundException或java.sql.SQLException: Cannot load JDBC driver class com.mysql.jdbc.Driver。在 Tomcat 的context.xml里配了Resource标签但 web.xml 的resource-ref没声明或者驱动 jar 没放到 Tomcat 的 lib 目录。如果你是跟着网上配置文档走优先确认 MySQL 驱动 jar 的版本和 MySQL 服务器版本匹配MySQL 8.x 必须用mysql-connector-java-8.0.x.jar驱动类名是com.mysql.cj.jdbc.Driver而不是 5.x 的com.mysql.jdbc.Driver。这门课我一般不用 JNDI直接用DBUtil更符合课程设计的演示节奏能少踩两个坑。6. 进阶优化与答辩加分技巧从“能跑”到“好看”如果你做完上述功能还有余力或者想让项目在答辩时明显比同龄人的高一个档次有四个方向可以投入。第一个是请假时间冲突检测。目前的版本中一个学生同时段可以提交多份申请这在真实业务里是不允许的。你可以在 DAO 层写一个findOverlapApplications方法用WHERE student_id ? AND status IN (0,1) AND start_time ? AND end_time ?判断时间段重叠如果有重叠就拦截。方法签名设计为public boolean hasOverlap(int studentId, String startTime, String endTime)SQL 里的参数顺序必须和请假时间区间交叉匹配这条 SQL 值得反复验证。第二个是审批超时自动提醒。可以在t_application表加一个submit_time字段学生提交 48 小时后自动跳过审批环节或者定时通知辅导员。这个可以用java.util.Timer的定时任务或者给审批 Servlet 每次都检查一遍超期的记录。加这个功能不仅业务增量还能在答辩时自然说出“考虑过期审批”。第三个是分页功能。课程设计往往没有分页但如果你的测试数据超过了 100 条查询列表就会变得很慢而且不好看。分页参数核心是两个pageNum和pageSizeSQL 计算偏移量LIMIT ?,?。在 MySQL 里LIMIT的两个参数第一个从 0 开始就是(pageNum - 1) * pageSizeDAO 记录总数用SELECT COUNT(*) FROM t_application。JSP 页面里两个隐藏字段传页码翻页。第三个是统计图表展示。我用 ECharts 或者 Canvas 画一张简单的柱状图来展示各班请假次数需要写一个统计按班级分组的 DAO 方法返回 ListMapString, Object再在 Servlet 里把 List 转为 JSON 字符串传回页面。ECharts 和 Canvas 相对独立答辩环节展示效果极佳。市面上的课程设计往往停步在增删改查你加一个简单的可视化图表就足以覆盖“核心亮点”。第四个是答辩前必做的验证清单。部署到 Tomcat 后一定要把系统角色操作完整演练一遍重点验证四个场景学生提交申请后登录教师端能看到待审批记录审批驳回后学生端的列表状态变为已驳回同一个学生的跨月请假时间段计算正确中文姓名和中文请假原因在列表页和审批详情页显示不乱码。这四条拿下来演示就不会中途翻车。最后说一个我用无数次血泪换来的习惯。课程设计验收最喜欢看的就是“新的请求会不会互相影响”这类问题。所以你每次写完一个功能都要把 Tomcat 彻底重启一次清空 session用全新浏览器无痕模式跑一遍主流程。如果发现修改过的代码没有生效或者页面还在显示旧数据先mvn clean或者删掉服务器里的work目录清理缓存再检查是否有重复的 Servlet 映射类名。讲真从选题到答辩这个系统最多两周就能全部搞定。关键是别一头扎进代码里先花半天把表结构和 Servlet 路径理清楚。哪怕你中间翻车几次也值得在这上面投入精力——因为它是你整个 JavaWeb 学习生涯里最后一次能把底层原理讲透的机会。希望帮到你。本文还有配套的精品资源点击获取
返回列表