JSP+Servlet+JDBC学生请假管理系统:从工程导入到部署避坑指南 简介这是一份基于 jspservletjdbc 的学生请假信息管理系统课程设计主要面向计算机相关专业大学生用于毕业设计或课程设计参考。资源实现了学生、辅导员、宿管员三类角色的核心功能学生可填写请假单、查看个人晚归记录与审核状态辅导员可审核请假单、管理学生和班级信息并可导出晚归记录前端采用经典蓝色 UI 的 JSP 页面后端以 ServletJDBC 完成数据处理数据库脚本内置于压缩包中。压缩包共117个文件包含78个 JSP 页面、5个 Java 源文件及对应 class 文件、2个 SQL 脚本、2个 CSS 样式文件以及图片、配置文件和 jar 依赖包等整体大小约2.25MB结构清晰导入 IDE 即可运行调试。目前已有2404人学习/下载代码逻辑清晰附带学生、辅导员、宿管员三组默认测试账号适合初学者理解 JSPServletJDBC 三层交互也可作为课程设计答辩与二次开发的基础。1. 如果你拿到的是 jspservletjdbc 学生请假管理系统先别急着改代码如果你现在手头正压着一个 jspservletjdbc 学生请假信息管理系统课程设计最要紧的不是研究什么高深框架而是先让它在本地跑起来。这套系统覆盖了学生提交请假单、辅导员审核、导出晚归记录、宿管员维护基础信息这几条完整业务线正好对应三个登录角色答辩时既能讲业务又能演示功能是那种“看着朴素但能撑住场面”的典型课设。它适合两类人一是马上面临验收、需要一个能跑能演示的代码库的同学二是没完整写过 Servlet 项目、想借一份真实工程理解分层和 JDBC 的初学者。下面我会按工程结构、数据库、Servlet、坑位、导出优化这几个顺序把这份资源拆给你看。2. 从 .classpath 到 pintuer.css动手前先把 Eclipse 工程结构读透2.1 .classpath 与 org.eclipse.wst.common.component这两类元数据文件丢了项目就跑不起来大多数课设包下载下来都是 Eclipse 工程。你会在根目录看到 .classpath、.project、.settings 这类隐藏文件还有 org.eclipse.wst.common.component 这样的配置。如果你用 Eclipse 的 Import Existing Projects into Workspace 导入时发现项目没变成“地球图标”而是普通文件夹问题基本出在 .project 丢失或失效。常见做法是手动补齐工程描述文件或者直接用 New Dynamic Web Project 重建一个空壳再把源码导进来。org.eclipse.wst.common.component 这个 XML 文件要特别留意因为里面记录了 context-root访问路径前缀和部署组装方式比如?xml version1.0 encodingUTF-8? project-modules ... wb-module deploy-nameLeaveSystem wb-resource deploy-path/ source-path/WebContent / property namecontext-root valueLeaveSystem/ /wb-module /project-modulescontext-root 改成你自己习惯的访问名比如 leave启动后访问地址就是 http://localhost:8080/leave/。很多同学把项目名改了之后发现 404多半就是这里没同步或者部署到 Tomcat 时项目名与 context-root 不一致。Eclipse 里还可以通过 Project Properties Web Project Settings 修改 Context Root改完会自动维护该文件。.settings/org.eclipse.wst.jsdt.ui.superType.container 也是容易忽略的一个文件它决定了 JS 代码补全和类型推断的容器常见内容是 org.eclipse.wst.jsdt.launching.baseBrowserLibrary。它和运行无关但如果整个 .settings 目录都丢了Eclipse 对 JSP 里的 JS 报一堆红叉非常烦人。2.2 CodeUtil、PageBean、Excel、CodeServlet、CheckCode一眼看出这套系统的五个关键类打开 src 目录后像 CodeUtil.class、PageBean.class、Excel.class、CodeServlet.class、CheckCode.class 这类类名基本可以断定包的结构。如果是 .class 说明下载的是编译产物需要有源码才能继续改。如果只有 class 文件没有源码建议先找同名的 java 文件实在没有的话就反编译——不过在课设阶段我一般宁可自己重写一个同名类也不去反编译因为反编译出来的代码结构乱答辩时根本说不清。这些类的分工从上往下数类名负责内容对应功能CodeUtil验证码生成辅助逻辑随机字符串、颜色、干扰线登录页验证码图片CodeServlet验证码图片输出把答案写入 session登录页img的请求入口CheckCode校验前端输入与 session 中验证码是否一致登录校验PageBean分页数据模型晚归记录、学生列表的分页Excel基于 POI 的导出封装辅导员导出晚归记录看类名就能明确一件事这套课设的验证码模块是完整的分页也是独立封装的不是把 SQL 拼在 JSP 里。写课设说明书时你完全可以拿“PageBean 对查询结果做分页封装、Excel 负责数据导出”作为亮点描述。2.3 pintuer.css 与 admin.css前端资源路径别乱动这套系统前端用的是 Pintuer 框架对应的样式文件admin.css 是后台管理布局的补充样式。pintuer.css 是一套响应式框架类似于把常用组件、栅格系统、弹层都打包好的 CSSJS 前端库。课设里用它最大的好处是不用自己写 CSS页面看起来像“正经系统”。代价是 js 和 css 的引用必须用绝对或相对正确路径否则页面花成一片。建议拿到包后先按下面步骤把前端资源关系理一遍看 WebContent 下是否有 css、js、fonts、image 目录pintuer.css 是否在 css 目录内。打开 login.jsp看link标签的 href 是相对路径还是${pageContext.request.contextPath}拼接的绝对路径。用浏览器直接访问登录页时把地址栏路径与 css 的实际路径逐级对一遍。常见问题是 JSP 放在子目录后hrefcss/pintuer.css 解析到了子目录样式全丢。这个坑在第 5 章会展开讲。2.4 拿到课设包后的半小时操作顺序为了避免“不知道先干什么”的迷茫我一般按下面顺序快速过一遍先确认 MySQL 版本和密码把 lib 下的 mysql-connector-java 找到再用 Eclipse 导入工程导入时如果项目没被识别就补 .project 文件然后打开项目属性确认 Java Compiler 版本在 1.8 左右Targeted Runtimes 里勾上本机 Tomcat最后把 web.xml 或注解里 /login、/code 这些路径记录下来后面排查 404 时直接对照。这套顺序能过滤掉八成环境问题。3. 数据库设计五张核心表与 JDBC 连接的完整写法3.1 数据模型用户、班级、请假单、晚归记录、课程怎么关联先达成一个共识这类课设的重点也是数据的归属。学生属于班级班级挂课程学生提交请假单辅导员审核宿管员记录晚归。默认登录账号 1/1、2/2、3/3 分别对应学生、辅导员、宿管员说明 user 表里有一列 role角色初始数据就是这三行。我一般会给出这样的建表 SQL单人单机课设完全够用你也可以直接导入CREATE DATABASE IF NOT EXISTS leave_db DEFAULT CHARACTER SET utf8mb4; USE leave_db; CREATE TABLE t_school_class ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 班级ID, class_name VARCHAR(50) NOT NULL COMMENT 班级名称, grade_year INT COMMENT 年级 ) COMMENT 班级信息表; CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程ID, course_name VARCHAR(50) NOT NULL COMMENT 课程名 ) COMMENT 课程表; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(20) NOT NULL COMMENT 登录名, password VARCHAR(50) NOT NULL COMMENT 密码, real_name VARCHAR(20) COMMENT 姓名, role INT NOT NULL COMMENT 角色:1学生 2辅导员 3宿管员, class_id INT COMMENT 学生所属班级 ) COMMENT 用户表; CREATE TABLE t_leave_apply ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 请假ID, stu_id INT NOT NULL COMMENT 学生用户ID, start_time DATETIME NOT NULL COMMENT 开始时间, end_time DATETIME NOT NULL COMMENT 结束时间, reason VARCHAR(255) COMMENT 请假事由, status INT DEFAULT 0 COMMENT 0待审 1通过 2驳回, audit_comment VARCHAR(255) COMMENT 审核意见, audit_time DATETIME COMMENT 审核时间, create_time DATETIME NOT NULL COMMENT 提交时间 ) COMMENT 请假申请表; CREATE TABLE t_late_record ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 记录ID, stu_id INT NOT NULL COMMENT 学生用户ID, late_time DATETIME NOT NULL COMMENT 晚归时间, recorder VARCHAR(20) COMMENT 记录人 ) COMMENT 晚归记录表;几个容易理解错的地方说明一下我用了 t_ 开头是为了避免和 MySQL 关键字冲突比如 course、class 单独使用有风险加前缀更稳。班级、课程这两张表是最简单的字典表。如果课程和班级多对多就建一张班级课程关联表字段就两个班级 ID、课程 ID。很多课设直接把 class_id 塞进课程表里也行答辩时能说清一对多关系就够。逻辑关联 物理外键。课设阶段不要在每张表上写 FOREIGN KEY原因是以后要加数据、改数据物理外键限制会导致增删改烦琐而且答辩时经常被问到“为什么订单删不掉”——多半就是外键没处理干净。你用 JOIN 查询完全可以把逻辑关系讲明白。3.2 JDBC 连接封装DBUtil 类与 URL 参数的含义servlet 项目最朴素的写法是一个 DBUtil 工具类。无论学号登录、请假的增删改查还是导出晚归记录都绕不开 Connection。下面是完整可用的版本package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/leave_db ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; 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(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { // 资源释放失败不影响主流程 } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { } } } }URL 里每个参数都有用途useUnicodetrue 和 characterEncodingutf8 解决中文写入乱码useSSLfalse 避免 MySQL 8 连接时的 SSL 警告serverTimezone 指定时区。如果本机数据库是 5.7驱动类要改成 com.mysql.jdbc.Driver如果是 8.0则是 com.mysql.cj.jdbc.Driver两者写反会在 Tomcat 启动时就报 ClassNotFoundException。密码改成你本地 MySQL 的密码全项目就这一处需要动。3.3 审核流程的状态机从待审到通过/驳回的 SQL 边界请假单的审核本质是一个状态流转0 待审 → 1 通过0 待审 → 2 驳回。辅导员在审核 JSP 里拿到请假单 ID 和审核意见后后端一般这样更新String sql UPDATE t_leave_apply SET status ?, audit_comment ?, audit_time NOW() WHERE id ? AND status 0; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, status); // 1 通过2 驳回 ps.setString(2, comment); // 审核意见 ps.setInt(3, leaveId); // 请假单ID int rows ps.executeUpdate(); if (rows 0) { // 说明这张单已经审核过页面提示 } }注意我在 WHERE 里加了一个 AND status 0这是比较容易被忽略的小技巧防止辅导员重复审核。两个页面同时打开一条待审单据一个点通过另一个人再点驳回如果没这个条件后执行的会把状态覆盖掉。rows 0 就说明有人抢先改了这时候页面提示“该请假单状态已变化请刷新”。这是能在答辩时讲出来的业务细节。如果项目里审核通过后还要给某个统计表加数据两步操作就要放到事务里。JDBC 事务比较简单Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 更新请假状态 // 2. 向统计表插入一条数据 conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.close(); }事务的边界就看一件事中间任何一步失败业务数据是否仍然自洽。像审核加统计必须一起成功只更新一张表那就老老实实单条 update不要为了显得高级硬套事务。4. Servlet 控制层验证码、登录分发与分页的落地写法4.1 CodeServlet 与 CheckCode验证码不是摆设要能连成完整链路登录页输入的验证码看起来是小功能实际上串起了三个类。CodeUtil 负责随机生成四位字符和干扰噪点CodeServlet 把字符画成 PNG 输出同时存到 sessionCheckCode 在登录提交时把用户输入和 session 里的值做比较。生成图片的核心代码大概长这样WebServlet(/code) public class CodeServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width 100, height 35; BufferedImage image new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g image.getGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); String code CodeUtil.randomCode(4); request.getSession().setAttribute(checkCode, code); // 逐字符绘制并加简单干扰线 for (int i 0; i code.length(); i) { g.setColor(new Color( (int) (Math.random() * 120) 30, (int) (Math.random() * 120) 30, (int) (Math.random() * 120) 30)); g.drawString(String.valueOf(code.charAt(i)), 15 i * 20, 25); } g.dispose(); response.setContentType(image/png); ImageIO.write(image, png, response.getOutputStream()); } }需要补充两点。第一验证码字符串和图片要同步生成不要把生成代码写两遍否则 session 里的值和图片不一致导致明明看着是 abcd输入 abcd 却说验证码错误。第二登录 Servlet 里校验的 session 钥匙要和这里一致比如都用 checkCode而不是有的地方存 check_code。这两个问题让新手排查时能折腾一晚上实际就是钥匙没对上。4.2 登录分发三个角色三种首页的跳转逻辑登录账号 1/1、2/2、3/3 对应三个角色后端只有拿到用户和角色才知道往哪儿跳。登录 Servlet 的流程我写成一版常规写法WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String inputPwd request.getParameter(password); String inputCode request.getParameter(code); String sessionCode (String) request.getSession().getAttribute(checkCode); if (sessionCode null || !sessionCode.equalsIgnoreCase(inputCode)) { request.setAttribute(msg, 验证码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } User user userService.login(username, inputPwd); if (user null) { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } request.getSession().setAttribute(loginUser, user); String ctx request.getContextPath(); if (user.getRole() 1) { response.sendRedirect(ctx /student/index.jsp); } else if (user.getRole() 2) { response.sendRedirect(ctx /teacher/index.jsp); } else { response.sendRedirect(ctx /admin/index.jsp); } } }这里有一个课设里反复出现的安全问题大多数 JSP 页面没有做登录拦截直接用地址栏输 /teacher/index.jsp 也能打开。正确做法是在 filter 包里写一个 LoginFilterWebFilter(/student/*) public class StudentFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpSession session request.getSession(false); User user (User) (session null ? null : session.getAttribute(loginUser)); if (user null || user.getRole() ! 1) { ((HttpServletResponse) resp).sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }student、teacher、admin 三个路径各配一个过滤器或者用一个拦截所有 jsp 的过滤器再按 role 放行。加了它才敢在答辩演示时回答“我是怎么保证只有辅导员能审核的”。4.3 PageBean把 LIMIT 和 total 封装成可复用模型列表页面用到的数据无非两类当前页数据和总条数。PageBean 就是把这两样塞进同一个对象里JSP 拿到后循环输出当前页再显示“上一页/下一页”。数据层常见的查法是// 先查总数 String countSql SELECT COUNT(*) FROM t_late_record WHERE stu_id ?; int total ...; // 执行 countSql 得到 total // 再查当前页数据 String listSql SELECT * FROM t_late_record WHERE stu_id ? ORDER BY late_time DESC LIMIT ?, ?; ListLateRecord list ...; // 从 start 开始取 pageSize 条PageBean 的起始偏移不能直接用前端传来的页码必须服务端算一遍public int getStart() { if (pageNum 1) { pageNum 1; } return (pageNum - 1) * pageSize; }防 SQL 注入的角度LIMIT 后的问号同样用 PreparedStatement 的 setInt 绑定不要直接拼字符串。分页参数 pageNum 从 request 拿到后最好强制转 int 并且设默认值 1否则第一个访问进来的人不传 pageNumInteger.parseInt(null) 直接 500。分页条在 JSP 里渲染时还要注意“上一页”和“下一页”的链接别在边界页失效第一页时禁用上一页最后一页时禁用下一页这个细节页面上要写。5. 运行避坑数据库导入、乱码、验证码与前端的常见故障排查拿课设包跑不起来是常态能一次跑通的反而少见。下面是我拆这类 JSP 课设时最常碰到的五类问题按现象、原因、解决三步写给你。5.1 现象SQL 文件导入报 Unknown database原因包里的 SQL 文件通常以 USE leave_db; 开头但你的 MySQL 里还没有 leave_db 这个库所以 Navicat 或命令行执行到 USE 语句直接报错。 解决先手动建库再导文件。在命令行或 Navicat 查询窗口执行CREATE DATABASE IF NOT EXISTS leave_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后右键该数据库执行 SQL 文件。如果 SQL 文件里已经写了 USE第二次执行时就不会再报错。注意选择数据库后再导入避免把表导进默认库。5.2 现象Tomcat 启动后访问项目 404原因最常见的是项目根本没被发布到 Tomcat Server。Eclipse 中虽然写了代码但 Servers 视图里 Tomcat 上没有部署该项目。 解决在 Servers 视图双击 Tomcat打开 Server Locations确保 Use Tomcat installation 已勾选回到 Server 右键 Add and Remove把项目 Add 进去。还要检查 org.eclipse.wst.common.component 里的 context-root与访问地址 http://localhost:8080/xxx 保持完全一致。改完项目名后 404九成是这个根路径没同步。5.3 现象页面、数据库、导出文件里的中文全是问号或乱码原因页面编码、请求编码、数据库连接编码三层不一致。JSP 页面设了 UTF-8但 MySQL 库建库时是 utf8mb4 或 latin1连接 URL 又没带 characterEncodingutf8中文必然乱。 解决三层都固定成 UTF-8。JSP 页面顶部写% page contentTypetext/html;charsetUTF-8 %Servlet 在 doPost 入口调用 request.setCharacterEncoding(UTF-8)DBUtil 的 URL 加上 useUnicodetruecharacterEncodingutf8。如果已经乱掉的数据重新插入或 UPDATE 回正确值别一次改一半。5.4 现象登录页面验证码图片裂开或空白原因img 标签的 src 路径没有带 contextPath像 srccode 这类写法在项目部署名不是 ROOT 时必然 404或者 web.xml 里的 servlet-mapping 和注解映射冲突导致访问不到 CodeServlet。 解决把验证码图片地址写成${pageContext.request.contextPath}/code如果是注解 WebServlet(/code)。如果项目用 web.xml 配置保持url-pattern/code/url-pattern与 JSP 里的地址一致不要同时用注解和 web.xml 重复映射同一路径——那是纯给自己添乱。5.5 现象样式和 JS 全部丢失页面像纯文本原因登录页在根目录时 hrefcss/pintuer.css 可能没问题但进入 student/index.jsp 后浏览器解析相对路径是基于当前目录于是一层子目录就导致 css 路径错位。 解决全部改用绝对路径JSP 头部声明变量% String basePath request.getScheme() :// request.getServerName() : request.getServerPort() request.getContextPath() /; %然后每个 css、js 链接都写成%basePath%css/pintuer.css。或者在 EL 表达式里用${pageContext.request.contextPath}/css/pintuer.css拼效果一样。这种问题 F12 看 Network 标签最直接哪个 css 红了改哪个。5.6 启动前十分钟自检清单这几个坑排完项目基本就能站在你面前了。第一次跑通后把 MySQL 账号密码、context-root、导入路径这三样记下来。启动前再自查四件事WEB-INF/lib 下有没有 mysql-connector jarServers 视图里项目是否已经 Add登录页直接访问 login.jsp 是否白屏filter 是否放行了 login.jsp 和验证码路径。这四项没问题系统基本不会在演示当场翻车。6. Excel 导出把晚归记录导出做成答辩里的加分项最后聊一个很多人忽略的优化点导出晚归记录。项目里的 Excel 类基于 POI 封装但默认导出往往会碰到两个问题一是文件名中文乱码二是数据量大时内存被撑爆。导出动作的本质是“把查询结果写成 Excel 文件流通过 HttpServletResponse 输出给浏览器”关键代码这样组织// 设置响应头必须放在写数据之前 response.setContentType(application/vnd.ms-excel;charsetUTF-8); String fileName URLEncoder.encode(晚归记录.xls, UTF-8) .replace(, %20); response.setHeader(Content-Disposition, attachment;filename fileName); try (Workbook wb new HSSFWorkbook(); OutputStream out response.getOutputStream()) { Sheet sheet wb.createSheet(晚归记录); String[] header {学号, 姓名, 晚归时间, 记录人}; Row headRow sheet.createRow(0); for (int i 0; i header.length; i) { headRow.createCell(i).setCellValue(header[i]); } // 循环查询出的 List逐行写入数据 wb.write(out); } catch (IOException e) { log.error(导出失败, e); }三点使用习惯直接抄HSSFWorkbook 对应 xls课设数据量绝对够用如果非要导 xlsx改用 XSSFWorkbook响应头对应改成 spreadsheetml.sheet文件名后缀同步换。文件名必须 URLEncoder.encode否则浏览器拿到中文文件名会乱码replace(, %20) 是为了把空格还原。凡是产生流的地方都用 try-with-resources 或 finally 里关闭Workbook 和 OutputStream 哪个不关Tomcat 在 Windows 上就会一直锁文件导致下一次导出报文件占用。从那次以后我拿到任何课设包第一件事就是先打开 WEB-INF 下的 lib 确认驱动在不在再把数据库导入、Tomcat 发布、登录、导出完整走一遍最后才动业务代码。这套学生请假系统虽然不是我自己写的但按这条路线把环境、登录、审核、导出全部核了一遍踩坑顺序熟悉到闭着眼都能背。希望这个顺序也帮你在答辩前少折腾几个晚上希望帮到你。本文还有配套的精品资源点击获取