简介Java学生考勤管理系统源码与配套文档包面向高校学生、Java开发者及课程设计使用者针对教育机构日常签到签退、考勤统计与异常处理等需求提供一套基于Servlet/JSP的B/S架构完整方案。资源共355个文件约40.82MB主要包含96个JSP页面、6个Java源文件、14个JAR依赖库、1个SQL数据库脚本以及演示视频、论文和题审批表等材料覆盖系统代码、数据库、部署配置、设计说明与答辩文档多个维度。目前已有127人学习查看适合用于课程设计、毕业设计或Java Web入门实战。配套演示视频可直观了解登录、考勤记录、报告生成等功能流程SQL脚本和完整工程目录结构支持快速导入数据库并在IDE中运行论文与审批表详细阐述了系统设计思路、技术选型及项目审批过程有助于参考撰写文档或理解考勤管理业务逻辑适合需要从零搭建同类系统的学习者。1. 考勤系统的矛盾点代码简短但工程设计不简单学生考勤管理系统在课程设计里出现频率极高表面需求就三句话登录、签到签退、出报表。但真正拿到一份完整源码包时你会发现写业务逻辑只是工作量的一部分更大的精力消耗在数据库表设计、跨角色权限控制、日期状态判定和部署环境兼容上。这个项目包带了源码、数据库脚本、论文、审批表和演示视频说明它不是临时拼凑的demo而是按一次正规课程流程走完的产物。源码里出现DataFormat.class、ISOtoGb2312.class、dbconn.class这类命名可以判断这是典型的 JSP Servlet JDBC MySQL 老架构项目没有引入 Spring 全家桶。这种结构的优点是逻辑直白、部署轻量缺点是连接管理粗糙、SQL 拼接普遍恰好是理解 Java Web 底层原理的极佳样本也适合拿去对照面试题里关于 Servlet 生命周期、JDBC 事务、Session 机制的讨论。2. 业务实体拆解与关系型数据库设计2.1 考勤系统的实体关系边界学生在做数据库课程设计时最容易犯的错误是一上来就建表边写代码边加字段最后表结构混乱关联查询写出笛卡尔积。拿到源码包后第一件事不是打开 Eclipse 看代码而是先看数据库脚本里到底建了几张表。一个合格的考勤系统核心实体只有四个学生student、教师teacher、考勤记录attendance、课程/班级course/class。角色实体不能混在同一张表里虽然学生和教师都有姓名、账号、密码但业务权限和关联关系完全不同合并成一张 user 表会污染外键约束。推荐的设计是拆分三张身份表再用一个独立的管理员账号或角色字段区分权限层级。2.2 表结构的主键策略与状态字段约定以下是根据这套系统常见数据库脚本整理出的五张核心表字段命名风格保持与源码中的 JDBC 查询逻辑一致CREATE TABLE student ( stu_id VARCHAR(20) PRIMARY KEY, stu_name VARCHAR(50) NOT NULL, class_name VARCHAR(50), password VARCHAR(64) NOT NULL, status TINYINT DEFAULT 1 ); CREATE TABLE teacher ( teach_id VARCHAR(20) PRIMARY KEY, teach_name VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL ); CREATE TABLE course ( course_id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(100) NOT NULL, teach_id VARCHAR(20), FOREIGN KEY (teach_id) REFERENCES teacher(teach_id) ); CREATE TABLE attendance ( att_id INT AUTO_INCREMENT PRIMARY KEY, stu_id VARCHAR(20) NOT NULL, course_id INT NOT NULL, att_date DATE NOT NULL, sign_in_time DATETIME, sign_out_time DATETIME, status TINYINT DEFAULT 0, UNIQUE KEY uk_stu_course_date (stu_id, course_id, att_date), FOREIGN KEY (stu_id) REFERENCES student(stu_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );这段 DDL 里有几个容易被忽略的设计点。stu_id使用 VARCHAR 而不是 INT是因为学号通常有前导零或字母前缀用 INT 会在插入时自动去除前导零导致数据与教务系统对不上。UNIQUE KEY uk_stu_course_date是在数据库层面兜底重复签到等于给 Java 代码里的判断加了一道保险。att_date与sign_in_time分开存储是因为“某天的签到记录”和“具体几点几分签到”查询频率完全不同拆开日期字段WHERE att_date CURDATE()可以用上索引而不必对 DATETIME 做DATE()函数包裹导致全表扫描。2.3 状态码设计不要用字符串描述状态考勤状态建议使用 TINYINT 数字码在 Java 侧定义常量类。下面是这套系统里常用的状态约定status含义触发条件0正常签到时间在课程开始前或迟到阈值内且签退时间正常1迟到签到时间晚于上课时间但早于迟到上限2早退签退时间早于下课时间但晚于早退下限3缺勤全天无签到记录或签到时间晚于迟到上限4异常无签退记录、重复签到、时间冲突等用数字枚举而不是late、absent这类字符串核心原因有两个。第一数据库排序和分组时数字码天然有序做月度汇总只需GROUP BY status加COUNT(*)不用CASE WHEN写一堆字符串匹配。第二Java 侧后续扩展状态只需要在常量类里加一行不需要动数据库表结构。源码里DataFormat.class这个类名说明项目里有一个专门处理日期格式化与状态转换的工具类面试时可以重点讲这个类的职责边界。2.4 表关系与查询路线的选择依据学生与考勤记录是一对多课程与考勤记录是一对多教师与课程是一对多。实际查询时两条主路线一条是学生视角student → attendance → course用于个人考勤明细和个人汇总另一条是管理员视角course → attendance → student用于按班级或课程维度查缺席名单。两条路线在 SQL 里对应不同的 JOIN 方向源码里的dbconn.class封装了基本的getConnection()和executeQuery()真正复杂的关联查询一般在 JSP 页面或 Servlet 的 Service 层写班级汇总是典型的聚合查询可以单独抽出工具方法。3. 登录认证与 JDBC 连接层实现分析3.1 从 dbconn.class 反推连接管理方式项目源码里的dbconn.class是拆解这个系统的入口。一个典型的实现是加载 MySQL 驱动并返回连接对象配合 properties 文件里的数据库地址、账号、密码。老项目的标准写法如下import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class dbconn { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/attendance_db?useUnicodetruecharacterEncodingutf-8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这串代码里的useUnicodetruecharacterEncodingutf-8是中文不乱码的关键参数。MySQL 5.x 时代连接 URL 不显式声明字符集JDBC 驱动会使用服务器默认的 latin1导致 JSP 页面传来的中文用户名和数据库表中的中文班级名全部变成问号。源码包里的ISOtoGb2312.class就是老项目应对编码问题的补丁常见逻辑是把 ISO-8859-1 编码的字符串转成 UTF-8 或 GB2312。现在用 JDBC 8.x 驱动时URL 参数需要改成serverTimezoneAsia/ShanghaicharacterEncodingutf-8同时驱动类变为com.mysql.cj.jdbc.Driver这一点在部署到新版 Tomcat 时最容易踩坑。3.2 登录流程中的 SQL 注入隐患与拦截时机学生的登录验证一般是SELECT * FROM student WHERE stu_id? AND password?但老项目往往会直接用字符串拼接String sql SELECT * FROM student WHERE stu_id username AND password password ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); if (rs.next()) { session.setAttribute(studentId, rs.getString(stu_id)); response.sendRedirect(student_home.jsp); }这种写法在课程设计中很常见但拿去面试会被追问 SQL 注入案例。比如用户名传入 OR 11拼接后的查询条件恒为真直接绕过密码验证。正确做法是使用PreparedStatement预编译占位符让数据库引擎先完成 SQL 模板解析再绑定参数值从语法层面隔离用户输入与 SQL 结构。下面是改造后的写法String sql SELECT stu_id, stu_name FROM student WHERE stu_id ? AND password ? AND status 1; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username.trim()); ps.setString(2, encryptPassword(password)); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { HttpSession session request.getSession(); session.setAttribute(role, student); session.setAttribute(userId, rs.getString(stu_id)); session.setAttribute(userName, rs.getString(stu_name)); response.sendRedirect(student_dashboard.jsp); } else { request.setAttribute(loginError, 账号或密码错误或账号已被禁用); request.getRequestDispatcher(login.jsp).forward(request, response); } } }session.setAttribute这一步非常关键后续所有考勤操作都需要先核对session.getAttribute(role)防止学生直接拼 URL 访问教师管理页面。encryptPassword()是额外建议老项目多数是明文存储但如果答辩时被问到安全性你需要能答出至少用 MD5 加盐或 BCrypt 做哈希不能把数据库泄露的后果留给明文密码。3.3 跨页面中文乱码的三层处理策略源码里有ISOtoGb2312.class说明原开发者已经被编码问题折磨过。这类老项目乱码通常会出现在三个位置浏览器请求参数、JSP 页面输出、数据库存取。浏览器发出 POST 请求 - Servlet 里 request.setCharacterEncoding(UTF-8) 处理请求体 - JSP 页面顶部 % page contentTypetext/html;charsetUTF-8 % - 数据库连接 URL 带 characterEncodingutf-8 - MySQL 表本身是 utf8 字符集四层只要有一层断掉就会出现中文乱码。Tomcat 8 以上默认 URI 编码已经是 UTF-8而 Tomcat 7 及以下需要修改server.xml里的Connector URIEncodingUTF-8 /。这个项目带演示视频如果视频里展示的是 Eclipse 内置浏览器访问那可能是老版本 Tomcat 6 或 7部署到新环境时必须把连接器编码显式声明。调试顺序建议先把 JSP 页面的静态中文打出来看是否正常再测数据库传入传出最后查请求参数层。3.4 连接关闭与事务边界的正确习惯老项目通病是不关连接或者最后只关了ResultSet忘记关Statement和Connection。虽然程序结束后 JDBC 驱动会释放资源但在 Tomcat 长驻进程里每次请求都泄漏一个连接最终导致Too many connections。推荐的资源关闭写法用 try-with-resourcesJava 7 以上直接支持try (Connection conn dbconn.getConnection(); PreparedStatement ps conn.prepareStatement(UPDATE attendance SET sign_out_time ? WHERE att_id ?)) { ps.setTimestamp(1, new Timestamp(System.currentTimeMillis())); ps.setInt(2, attId); int rows ps.executeUpdate(); if (rows 1) { conn.commit(); // 如果是手动事务 } }注意setTimestamp与 MySQL 的DATETIME类型对应不要用setString拼日期字符串否则数据库端会因为格式解析失败或隐式转换导致索引失效。该项目的DataFormat.class很可能就是封装了SimpleDateFormat的日期转换逻辑比如把前端传来的yyyy-MM-dd HH:mm转成java.sql.Timestamp这些细节在答辩时比业务代码更容易展示功底。4. 考勤业务规则的代码实现与边界处理4.1 签到判定迟到与异常的时间阈值推算考勤系统真正的核心逻辑不在增删改查而在状态判定。一个学生签到系统需要对比当前时间与课程表里的上课时间、迟到上限。源码中通常会在course表增加上课时间字段或在attendance逻辑里硬编码阈值。更推荐在课程表维护start_time、end_time、late_threshold_minutes三个字段把业务规则数据化而不是写死在 Java 常量里。public int checkSignInStatus(Course course, Date now) { long diff now.getTime() - course.getStartTime().getTime(); long lateLimit course.getLateThresholdMinutes() * 60 * 1000L; if (diff 0) { return AttendanceStatus.NORMAL; } else if (diff lateLimit) { return AttendanceStatus.LATE; } else { return AttendanceStatus.ABSENT; } }这个方法的逻辑很短但边界条件很考验细节。diff 0表示提前到或踩着点正常diff超过迟到阈值则直接标记缺勤而不是迟到因为超过阈值一般默认这节课不来了。course.getStartTime()返回的是java.util.Date而数据库里取出来的是java.sql.Timestamp两者可以直接比较毫秒值不需要额外转换。还有一点容易被忽视跨天课程。有些选修课晚上上课跨过午夜签退时间小于签到时间这类场景需要额外处理课程日期偏移最简单的方案是在course表增加days_offset字段签退判定时给课程结束时间加一天再比较。4.2 签退与早退判定注意时间的动态基准签退比签到更易出错。原因在于签到基准时间固定签退判定基准在数据库里取的是课程结束时间但学生实际签退可能是当天也可能跨天。下面的代码展示了一套完整的签退校验逻辑public int processSignOut(Connection conn, int attId, Date signOutTime) throws SQLException, AttException { String sql SELECT c.end_time, c.early_leave_threshold_minutes, a.sign_in_time, a.att_date FROM attendance a JOIN course c ON a.course_id c.course_id WHERE a.att_id ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, attId); try (ResultSet rs ps.executeQuery()) { if (!rs.next()) { throw new AttException(考勤记录不存在); } Timestamp endTime rs.getTimestamp(end_time); Timestamp signInTime rs.getTimestamp(sign_in_time); if (signInTime null) { throw new AttException(未签到无法签退); } if (signOutTime.before(signInTime)) { throw new AttException(签退时间早于签到时间请检查系统时钟); } int earlyLeaveMinutes rs.getInt(early_leave_threshold_minutes); long earlyLimit earlyLeaveMinutes * 60 * 1000L; boolean earlyLeave (endTime.getTime() - signOutTime.getTime()) earlyLimit; int newStatus earlyLeave ? AttendanceStatus.EARLY_LEAVE : AttendanceStatus.NORMAL; String updateSql UPDATE attendance SET sign_out_time ?, status ? WHERE att_id ?; try (PreparedStatement psUpdate conn.prepareStatement(updateSql)) { psUpdate.setTimestamp(1, signOutTime); psUpdate.setInt(2, newStatus); psUpdate.setInt(3, attId); psUpdate.executeUpdate(); } return newStatus; } } }这里的核心判定用endTime.getTime() - signOutTime.getTime()得到提前离开的毫秒数。早退阈值通常设置为 10 到 15 分钟也就是提前超过阈值才算早退如果只早走两分钟大部分考勤系统仍按正常处理。AttException是自定义异常用来区分业务异常与系统异常这种设计在课程设计论文里是一个加分项答辩时可以专门讲为什么要自定义异常而不是直接抛RuntimeException。4.3 重复签到与数据一致性应用层与数据库层双保险老项目常见的重复签到问题有两种来源一是学生手抖连续点两次签到按钮二是前端未禁用按钮导致重复请求。应用层判断方案是查询当天该课程是否已有记录public boolean hasSignedToday(Connection conn, String stuId, int courseId, Date today) { String sql SELECT att_id FROM attendance WHERE stu_id ? AND course_id ? AND att_date ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stuId); ps.setInt(2, courseId); ps.setDate(3, new java.sql.Date(today.getTime())); try (ResultSet rs ps.executeQuery()) { return rs.next(); } } }但应用层判断存在并发窗口两个请求同时进来都查到无记录然后同时插入最终出现两条重复数据。这时依赖的就是 2.2 节里 DDL 中的UNIQUE KEY uk_stu_course_date (stu_id, course_id, att_date)。数据库层约束是最终防线当第二条 INSERT 命中唯一索引时抛出DuplicateKeyException业务层捕获后返回“今天已签到”而不是让异常直接变成 500 错误页。这种双保险思路在 java 面试八股文里常被考到本质上是一个经典的并发控制问题可以用乐观锁、唯一索引或分布式锁解决考勤系统里唯一索引最简单也最有效。4.4 缺勤补录与异常处理流程数据库中缺勤状态不一定来自签到判定也可能是课程结束后没有被签到过的记录自然沉淀为缺勤。推荐的做法是每天课程结束后定时任务或管理员手动触发一次批量更新UPDATE attendance SET status 3 WHERE course_id ? AND att_date ? AND status 0 AND sign_in_time IS NULL;这条 SQL 的前提是attendance表在每门课程开始前已经初始化了所有学生的记录行而不是等学生签到才插入。两种设计区别很大前者是“预生成记录 状态流转”后者是“按需插入记录”前者做月度汇总和缺勤名单统计效率更高后者省存储但查缺勤需要先 JOIN 出谁没签到。这套系统源码采用的是哪种可以通过dbconn.class里的 SQL 判断SELECT ... WHERE sign_in_time IS NULL出现的频率足够说明问题。管理员补录功能本质就是对指定学生的某条attendance记录做 UPDATE包括修改签到时间、签退时间和状态码。补录操作必须有审计日志至少记录操作人、被修改的学生、修改前状态、修改后状态、操作时间。论文的“系统测试”章节里通常会包含这类操作的截图展示说明这个功能是评委关注的内容点。4.5 月度汇总报告的 SQL 写法与优化报告生成功能是管理员的刚需核心 SQL 一般是按学生和月份分组统计各状态次数SELECT stu.stu_id, stu.stu_name, SUM(CASE WHEN a.status 0 THEN 1 ELSE 0 END) AS normal_count, SUM(CASE WHEN a.status 1 THEN 1 ELSE 0 END) AS late_count, SUM(CASE WHEN a.status 2 THEN 1 ELSE 0 END) AS early_leave_count, SUM(CASE WHEN a.status 3 THEN 1 ELSE 0 END) AS absent_count, COUNT(*) AS total_count FROM student stu LEFT JOIN attendance a ON stu.stu_id a.stu_id AND a.att_date BETWEEN ? AND ? WHERE stu.class_name ? GROUP BY stu.stu_id, stu.stu_name;LEFT JOIN而非INNER JOIN的原因是如果某个学生全月没有任何考勤记录比如休学但未停用账号INNER JOIN会把他从汇总里丢掉而管理员更希望看到“这个学生全月无记录”这个事实。BETWEEN查询的日期参数在 MyBatis 出现前通常用SimpleDateFormat转字符串拼接这就是DataFormat.class存在的价值。这里值得留意的是SUM(CASE WHEN ... )替代多次COUNT子查询一次扫描完成全部统计性能远优于多次访问表。当年这套系统跑在教室局域网 MySQL 上可能没感觉但如果数据量到十万级这种写法在索引优化后仍然能保持毫秒级响应。5. 从零部署Eclipse Tomcat MySQL 的完整复现过程5.1 开发环境版本选型与兼容性说明看到.classpath和org.eclipse.wst.common.component这两个文件可以确认项目是 Eclipse 动态 Web 项目。这类老源码包的部署环境必须严格匹配否则跑不起来。经过大量类似项目的复盘推荐按以下组合安装组件推荐版本说明JDK1.7 或 1.8如果源码用了try-with-resources必须 JDK 7EclipseKepler 或 Luna版本过新可能导致 WTP 组件识别异常Tomcat7.0.x与 JDK 1.7 配套默认 URI 编码为 ISO-8859-1MySQL5.5 或 5.6使用com.mysql.jdbc.Driver老驱动MySQL Connector/J5.1.49放入WEB-INF/lib目录最高频的启动失败原因有两个。第一个是驱动类找不到Class.forName(com.mysql.jdbc.Driver)抛ClassNotFoundException通常因为 jar 包没有真正发布到 Tomcat 的WEB-INF/lib只在 Eclipse 的 Build Path 里添加了Eclipse 发布项目时需要勾选 “Publish module contents to Tomcat”。第二个是 JDK 与 Tomcat 版本不匹配Tomcat 7 配 JDK 8 问题不大但 Tomcat 9 配 JDK 7 直接启动失败UnsupportedClassVersionError是最直接的报错。5.2 数据库导入与账号配置的完整步骤用 Navicat 或命令行执行数据库脚本的步骤如下mysql -u root -p attendance_db.sql如果脚本里已经包含CREATE DATABASE attendance_db DEFAULT CHARACTER SET utf8;导入后不需要额外建库。导入完毕后必须检查三件事student表里是否有测试账号、attendance表里是否有历史数据、数据库字符集是否是utf8。USE attendance_db; SHOW TABLES; SELECT * FROM student LIMIT 5; SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME attendance_db;第三步的输出必须是utf8如果是latin1执行ALTER DATABASE attendance_db DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;然后再对每张表执行ALTER TABLE ... CONVERT TO CHARACTER SET utf8;。注意CONVERT TO CHARACTER SET会改动表的字符集但不会自动修复已存储的乱码数据如果导入前脚本里的中文就已经是乱码需要先灌入新的正确数据。源码中的dbconn.class里写死的数据库密码如果和本地 MySQL 不一致需要修改源码再重新编译或者在WEB-INF/classes下新建一个高优先级的配置文件覆盖默认值。老项目经常把配置写进常量类没有外置 properties 文件这一步改造工作量不大但能大幅提升可维护性。5.3 编译发布与演示视频对照的关键步骤Eclipse 部署的常见路径是右键项目 - Run As - Run on Server。如果项目里没有.settings文件夹导入后 WTP 的 web 模块配置可能丢失表现为打开eclipse的 Servers 视图添加 Tomcat 时找不到项目上下文。解决办法是右键项目Properties - Project Facets - Convert to faceted form勾选 Dynamic Web Module 2.5 和 Java 1.7。.classpath文件已经定义好了源码目录src和输出目录build/classes不需要手动调整但要确认 Build Path 里有 JDK 库和 MySQL 驱动 jar。发布成功后访问地址一般是http://localhost:8080/项目名/login.jsp。演示视频里如果展示的是localhost:8080/AttendanceSystem/这样的 URL项目名大小写敏感Linux 服务器上尤其严格。Tomcat 默认大小写不敏感的是路径部分但上下文名称区分大小写Windows 下大小写差异会被忽略Linux 下会导致 404。5.4 启动失败时按日志顺序排查Tomcat 启动失败时第一处要看的地方是logs/catalina.out或 Eclipse Console 窗口。常见报错与处理方式对照如下报错信息根因处理方式ClassNotFoundException: com.mysql.jdbc.Driver驱动 jar 未发布检查WEB-INF/libAccess denied for user rootlocalhost密码不匹配改dbconn.class里的密码或改 MySQL 账号Unknown database attendance_db数据库未导入执行数据库脚本The server time zone value Öйú±ê׼ʱ¼äMySQL 时区未识别连接 URL 加serverTimezoneAsia/Shanghaijava.sql.SQLException: Column status not found表结构不一致对比脚本与实体类字段HTTP 404: /AttendanceSystem/WEB-INF/classes/xyz访问了 WEB-INF 内部路径确认所有 JSP 都在 WebContent 下第五种时区报错是国人的经典痛点MySQL 5.5 和 Connector/J 5.1 的组合通常会规避这个问题但换了新驱动就会冒出The server time zone value Öйú±ê׼ʱ¼ä is unrecognized or represents more than one time zone这是一串乱码形式的“中国标准时间”本质上不是时区配置坏掉而是驱动的默认解析不支持。在连接 URL 上附加serverTimezoneAsia/Shanghai后问题消失。如果不想改源码重新编译可以在 MySQL 命令行执行SET GLOBAL time_zone 08:00;同样能解决。5.5 数据库连接数异常与 Tomcat 内存配置老项目一次登录查询就getConnection()一次一个页面如果有三个查询单次访问就创建三个连接。在没有连接池的情况下Tomcat 默认最大线程数是 200极端情况下 MySQL 连接数会被打满。MySQ L 默认的最大连接数是 151超过就报Too many connections。临时的处理是调大 MySQL 上限SET GLOBAL max_connections 500;但临时方案不解决根本问题。给这个课程设计做优化提升时连接池是最值得做的一项改造常见做法是引入dbcp或c3p0或者更轻量的 HikariCP。以下是一个直接用 Apache DBCP 的BasicDataSource改造示例import org.apache.commons.dbcp2.BasicDataSource; public class DataSourceManager { private static BasicDataSource dataSource; static { dataSource new BasicDataSource(); dataSource.setDriverClassName(com.mysql.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/attendance_db?characterEncodingutf-8); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxTotal(20); dataSource.setMaxIdle(10); dataSource.setMinIdle(3); dataSource.setMaxWaitMillis(6000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }setMaxTotal(20)表示连接池最多建立 20 个物理连接setMaxWaitMillis(6000)表示连接不够用时最多等 6 秒超过直接抛异常而不是无限阻塞。这个改造对原项目的侵入非常小只需把dbconn.getConnection()替换为DataSourceManager.getConnection()其余 DAO 代码完全不用动。把这段写进论文的“系统优化”章节答辩分数通常会高一个档次因为它证明了作者理解连接池的基本原理以及为什么要控制空闲连接数。6. 把课程设计变成面试谈资的三个技巧6.1 从表结构反推业务规则面试中的“项目真实性”考察面试官最常问的一句话是 “这个项目是你自己做的吗”。考勤管理系统听起来常见但如果你能随口说出表结构设计时为什么用UNIQUE KEY防止重复签到、为什么状态字段用 TINYINT、为什么汇总报表用SUM(CASE WHEN)而不是COUNT(IF())面试官会认为这是真实的思考积累。Java 基础问题可以围绕这个项目正面回答Servlet 是单例多线程多个请求共享同一个 service 方法所以 Servlet 里的成员变量要谨慎使用只读常量没问题但可修改状态字段会引发并发读写问题。6.2 从 JSP 旧架构到 Spring Boot 的迁移成本评估这套旧架构迁移到 Spring Boot 的工作量并不大。数据库层把dbconn.class换成 Spring Data JPA 或 MyBatis。业务层把 Servlet 里的逻辑拆成 Service Controller。表现层JSP 换成 Thymeleaf 或直接做前后端分离。关键点在于考勤判定逻辑是可以完全复用的checkSignInStatus()和processSignOut()这两个方法的核心算法与框架无关迁移时原封不动拷贝进 Service 层这就是分层设计的价值。回答 “你项目里最复杂的逻辑是什么” 这个问题时把日期阈值判定和跨天签退场景讲清楚比背八股文有说服力得多。6.3 报表导出为 Excel 的一种轻量实现课程设计里如果没有表格导出功能演示时总感觉少点什么。在不引入 Apache POI 这种重量级工具的前提下最简单的实现是生成 CSV 文件核心代码只有二十行StringBuilder sb new StringBuilder(); sb.append(学号,姓名,出勤天数,迟到次数,早退次数,缺勤次数\n); for (AttendanceReportItem item : reportList) { sb.append(item.getStuId()).append(,) .append(item.getStuName()).append(,) .append(item.getNormalCount()).append(,) .append(item.getLateCount()).append(,) .append(item.getEarlyLeaveCount()).append(,) .append(item.getAbsentCount()).append(\n); } response.setContentType(text/csv;charsetUTF-8); response.setHeader(Content-Disposition, attachment;filenameattendance_report.csv); response.getWriter().write(\uFEFF sb.toString());注意写入前先输出\uFEFF也就是 UTF-8 BOM否则用 Excel 直接打开这个 CSV 文件时中文列名会乱码。这个细节很小但在实际导出功能里几乎必考属于做过的项目才会知道的坑。把 CSV 升级成真正的 xlsx 思路其实就是把字符串拼接换成Workbook对象填充但业务数据的组织方式和这里的reportList完全一致迁移时不用重新设计数据装配逻辑。6.4 给学习者的最终建议先跑通再重构先弄脏再优化这套系统的源码价值在于它的“旧”结构足够简单你能看清 JSP、Servlet、JDBC 每一层职责但正因为旧它保留了连接泄漏、SQL 拼接、编码混乱这些真实生产问题。建议把源码跑起来后按照数据库层加固、连接池改造、PreparedStatement 替换、事务管理完善、演示视频录制的顺序做一轮属于自己的重构。用考勤系统去练手防御性编程比如输入校验、异常捕获、日志记录比刷一百道面试题更能建立工程直觉。把重构后的项目重新写一份论文附上每次提交的 git 记录这份经历在简历上的分量远超对某个框架版本的熟练度。本文还有配套的精品资源点击获取