news 2026/9/23 19:36:05

JavaWeb新生报到系统实战:JSP+Servlet+SQL从设计到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb新生报到系统实战:JSP+Servlet+SQL从设计到部署

简介:基于Java+JSP+SQL技术栈的新生报到系统毕业设计项目源码,面向计算机相关专业需完成Web开发课题的学生,覆盖新生信息录入、报到确认、宿舍分配、课程安排等典型业务流程,可用于课程设计或毕业设计参考。压缩包共161个文件,大小2.36MB,包含29个JSP页面、6个Java源文件及对应class编译文件、8个JS与4个CSS前端脚本,58个GIF和18个JPG图形素材用于界面展示,另含4个db及mdf/ldf数据库文件,便于导入SQL数据库查看表结构与初始数据。已有158人学习下载,适合需要完整可运行代码、快速理解Java Web分层开发与数据库交互的读者。系统通过Servlet接收请求并控制页面跳转,JSP负责动态渲染,JavaBean封装学生、课程等业务对象,JDBC完成数据持久化,并配有基础防御措施。通过阅读源码可了解MVC分层、会话管理、文件上传等核心实现,也可在此基础上修改扩展为实验预约、学籍管理等相似系统。

1. 新生报到系统为什么值钱:一个迎新现场的混乱场景与这套技术栈的本质

每到九月初,学校迎新现场最忙的不是搬行李的志愿者,而是守着几台电脑录入报到信息的老师。新生排成长队,学号、姓名、专业、宿舍楼层全靠口头确认,Excel 表格开着三份,一不小心就把两个同名同姓的新生报到记录写串了。这时候你会发现,一个基于 Java+JSP+SQL 的新生报到系统,解决的不是技术问题,而是把“报到”这件事从纸质记录变成一条能查询、能统计、能打印报到单的固定流程。这套东西放到别的业务里叫进销存,放到迎新现场就叫报到系统。做毕业设计选它的价值在于:业务够具体,功能边界清晰,又能把 JSP、Servlet、SQL 这几门课的知识点全部串起来,从建表到发布的过程也就是一次完整的 JavaWeb 练兵。

2. 业务拆解与数据库设计:先把新生报到系统的表结构立住

很多同学拿到这类题第一反应是写页面,界面弄得花里胡哨,最后卡在数据存不进去。我一般建议反过来,先把业务拆清楚,把表建好,再动 JSP。因为这个系统的核心不是漂亮页面,而是“报到状态”这一条主线的流转。一台电脑录入学号,判断这个学生是哪个学院哪个班的,是否已经报到,宿舍还剩几个床位,打印报到单,这些动作背后全是对数据库的增删改查。表结构设计对了,后面写代码就是顺水推舟;表结构错了,后面每加一个功能都要改表,返工成本极高。

2.1 系统角色与功能边界:管理员、辅导员、新生各干什么

先明确角色,报到系统不是 OA,不需要几十个菜单。常见的切法是三类用户:

系统管理员负责基础数据维护,包括学院、专业、班级、宿舍楼和账号管理,还要能看全校的报到进度。院系用户也就是辅导员,负责录入本学院新生报到信息、修改报到状态、分配宿舍、打印报到单。新生本人一般不直接登录系统,而是到现场由工作人员代录入。

功能边界要刻意收住。报到系统最核心的状态流就是:未报到 → 已报到(同时记录报到时间)→ 已分配宿舍 → 已领取物资。缴费可以做成一个字段或者一个状态,但不要往财务系统方向做,否则你还要做退款、作废、对账,工作量直接翻倍。把下面这几张表的字段设计好,系统的骨架就立住了。

2.2 核心数据表设计:学生表、报到表、宿舍表的关系与字段

学生表是主表,一个学生一条记录。学号建议单独建一个student_no字段并加唯一索引,不要拿自增主键当学号,因为学号有业务含义,比如年份加学院编号加流水号。身份证号用VARCHAR(18)存,不需要计算就永远不要用数值类型。报到状态statusTINYINT,0 表示未报到,1 表示已报到,2 表示缓报到。用数字比用字符串好排序、省空间,但一定要在代码里或者注释里写清楚含义,不然半年后你自己都看不懂。

宿舍表和班级表分别是学生表的“从属”信息。宿舍表里要存楼号、房间号、总床位数、已用床位数、性别限制。这里有个老手习惯:床位余量不要每次现算,而是在分配宿舍时对used_count字段做加一操作,查询宿舍列表时直接比较used_count < bed_count,效率高很多。班级表里存学院和班级名,后面统计报到率就靠它做分组。

下面用表格把最常用的几张表列出来,字段只列核心项,不是完整设计稿,但要覆盖主要业务。

表名关键字段用途
student_infoid, student_no, name, gender, id_card, class_id, dorm_id, status, report_time学生基础信息 + 报到状态
class_infoid, dept_name, class_name学院/班级,用于分班与统计
dormitoryid, building, room_no, bed_count, used_count, gender宿舍床位管理
report_logid, student_id, action, operator_id, create_time报到操作流水
sys_userid, username, password, role管理员/辅导员账号

report_log这张表容易被忽视,但它是答辩时的亮点。每次报到、改专业、换宿舍都往里面写一条日志,老师问“你怎么追溯数据变更”的时候,直接拿这张表回答。只有一张学生表的话,改完数据就找不回现场了。

2.3 初始化 SQL 脚本:从业务表到可运行数据库的落地写法

表结构设计好之后,按下面的脚本建库建表。这里选 MySQL 做演示,原因是毕业设计里遇到最多的是 MySQL,如果你学校机房统一用 SQL Server,注意把AUTO_INCREMENT换成IDENTITY(1,1),把反引号去掉,其余差别不大。

CREATE DATABASE IF NOT EXISTS enrollment DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE enrollment; CREATE TABLE student_info ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号,唯一', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT NOT NULL DEFAULT 1 COMMENT '1男 2女', id_card VARCHAR(18) DEFAULT NULL COMMENT '身份证号', class_id INT DEFAULT NULL COMMENT '班级ID', dorm_id INT DEFAULT NULL COMMENT '宿舍ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '报到状态 0未报到 1已报到 2缓报到', report_time DATETIME DEFAULT NULL COMMENT '报到时间', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB COMMENT='新生报到信息表'; CREATE TABLE class_info ( id INT AUTO_INCREMENT PRIMARY KEY, dept_name VARCHAR(50) NOT NULL COMMENT '学院名称', class_name VARCHAR(50) NOT NULL COMMENT '班级名称' ) ENGINE=InnoDB COMMENT='班级表'; CREATE TABLE dormitory ( id INT AUTO_INCREMENT PRIMARY KEY, building VARCHAR(50) NOT NULL COMMENT '楼号', room_no VARCHAR(20) NOT NULL COMMENT '房间号', bed_count INT NOT NULL COMMENT '总床位数', used_count INT NOT NULL DEFAULT 0 COMMENT '已用床位数', gender TINYINT NOT NULL DEFAULT 1 COMMENT '1男 2女' ) ENGINE=InnoDB COMMENT='宿舍表';

建表时字符集统一用utf8mb4,不要用utf8utf8在 MySQL 里最多存 3 字节,遇到生僻字或者表情符号会报错或变成问号。学号加UNIQUE约束是防止重复报到最底层的一道防线,这个索引在后面并发场景里能救命。

表建完后,还要往里塞基础数据。班级、宿舍、管理员账号都需要初始化。管理员密码字段强烈建议存哈希,不要存明文。由于这个阶段还没写代码,可以用下面这种安全哈希写法的变通形式,只作演示定位。

提示:下面的密码字段用占位字符串演示。正式项目里密码必须做加盐哈希,常见的做法是用 BCrypt,不要用 MD5 裸存。

INSERT INTO class_info (dept_name, class_name) VALUES ('计算机学院', '计算机2301班'); INSERT INTO class_info (dept_name, class_name) VALUES ('计算机学院', '软件2301班'); INSERT INTO class_info (dept_name, class_name) VALUES ('外国语学院', '英语2301班'); INSERT INTO dormitory (building, room_no, bed_count, used_count, gender) VALUES ('1号楼', '101', 6, 0, 1); INSERT INTO dormitory (building, room_no, bed_count, used_count, gender) VALUES ('2号楼', '201', 4, 0, 2); INSERT INTO sys_user (username, password, role) VALUES ('admin', '这里存哈希值', 'ADMIN');

这段脚本的作用是把项目从“零表”状态变成可运行状态。gender字段在宿舍分配时很关键:男生不能分到女生楼。这个校验可以在代码里做,但更稳妥的做法是查询宿舍时直接把gender条件拼进 SQL,让数据库把不可能的数据直接过滤掉。

3. 环境搭建与项目骨架:JDK+Tomcat+MySQL 组合的版本玄学

JSP 项目跑不起来,一半以上不是代码问题,是环境问题。尤其是版本搭配,充满了玄学。很多教程是五六年前写的,照着做 JDK 17 + Tomcat 10,结果 Tomcat 把javax.servlet换成了jakarta.servlet,老代码全部编译报错。你说代码错了吗?代码没问题,是教材没跟上版本。

3.1 JDK+Tomcat+MySQL 版本搭配:为什么卡在 JDK8 最不容易翻车

如果你去问教学班的老师,十个有九个会告诉你“用 JDK 8 就行”。这不是守旧,是 JSP 生态的老项目确实在 JDK 8 上最稳定。Tomcat 8.5 和 Tomcat 9 都支持 JDK 8,而且 API 还是javax.servlet这一套,网上随便搜出来的代码片段基本都能直接用。

MySQL 这边,5.7 和 8.0 都行。用 8.0 的话注意两点:JDBC 驱动要用 8.x,驱动类名是com.mysql.cj.jdbc.Driver;连接串里要加serverTimezone,否则报时区错误。用 5.7 也有它的好处,占用内存小,机房老机器跑得动。

组合是否推荐理由
JDK 8 + Tomcat 8.5 + MySQL 5.7推荐教程范例最多,javax API,踩坑成本最低
JDK 8 + Tomcat 9 + MySQL 8.0推荐Tomcat 9 比 8.5 新一些,驱动换 8.x 即可
JDK 17 + Tomcat 10 + MySQL 8.0谨慎servlet API 包名由 javax 变 jakarta,老代码要改导入

这个表格建议直接截图存着。遇到“代码没错但启动就报 NoClassDefFoundError”这类问题时,第一反应不是去查代码,而是看 Tomcat 版本和代码里 import 的包名一不一致。

3.2 最小可运行步骤:在 IDEA 里建 Web 项目并跑通第一个 JSP 页面

环境确认后,先不要急着写报到功能,用五分钟跑通一个最小页面,确认 Servlet 能转发、JSP 能渲染、数据库能连上。这一步通过的标志是浏览器里能看到当前时间,说明三层通了,后面写业务才有底。

新建一个 Maven 项目,打包方式改成 war。pom.xml里只需要三个依赖,多了反而容易冲突。

<dependencies> <!-- Servlet API,编译时需要,部署时 Tomcat 自带 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP API --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies>

scope设置成provided意味着打包时不会把 Servlet 和 JSP 的 jar 塞进 war 包里,因为这些 jar 在 Tomcat 的 lib 目录里已经有了。如果写compile,带上也没大问题,但可能和 Tomcat 自带的版本冲突,出现两个 Servlet API 的诡异报错,所以这里用provided是习惯操作。

然后写一个最简单的 Servlet,专门用来验证请求能不能转发到 JSP。

import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/index") public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("serverTime", new java.util.Date()); request.getRequestDispatcher("/index.jsp").forward(request, response); } }

@WebServlet("/index")注解配置映射,效果等同于在 web.xml 里写一大段<servlet>配置。课设阶段用注解能少写很多 XML,但如果你们学校要求看传统配置,建议还是保留 web.xml。

index.jsp页面里只做一件事,把 Servlet 传过来的时间显示出来。

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>新生报到系统 - 环境验证</title></head> <body> <h2>当前服务器时间:</h2> <p><%= request.getAttribute("serverTime") %></p> </body> </html>

这里用<%= %>输出属性,是 JSP 最基础的表达式语法。跑通之后,在 IDEA 里配置 Tomcat,Deployment 添加当前项目的 war exploded 模式,Application context 填/enroll。启动后访问http://localhost:8080/enroll/index,能看到时间就说明环境链路完整。接着再写一个 JDBC 测试类连接刚建好的库,能查出版本号,环境就算彻底通了。

注意:Application context 的值决定访问路径,如果它被配置成/,那就是直接访问http://localhost:8080/index。这个值建议保持和项目名一致,减少后面部署时的路径混乱。

4. 核心模块实现:报到登记、宿舍分配与统计的落地思路

环境通了,数据库表也建好了,接下来就是写业务。新生报到系统最核心的两段代码:一是报到登记入口,接收页面提交的数据,经过校验后写库;二是统计报表,把全校报到进度用一条 SQL 拉出来。这两段我分开讲,因为它们一写一读,代表了 JavaWeb 里最典型的两个方向。

4.1 基于 MVC 分层:用 Servlet 接收报到表单并写库

报到登记页面提交的字段大概是:学号、姓名、性别、班级、宿舍ID。Servlet 收到这些参数后,不能直接操作数据库,而是调 Service 层,由 Service 层管理事务。这样做的原因是“报到”不是一次单表插入,它至少要同时更新两张表:学生表写入报到状态和宿舍ID,宿舍表把used_count加一。两步操作必须在一个事务里,要么都成功,要么都回滚,否则就出现“学生显示已报到,但宿舍床位没扣”的脏数据。

@WebServlet("/report/add") public class ReportServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String studentNo = request.getParameter("studentNo"); int dormId = Integer.parseInt(request.getParameter("dormId")); int operatorId = Integer.parseInt(request.getParameter("operatorId")); ReportService service = new ReportService(); try { service.report(studentNo, dormId, operatorId); request.setAttribute("msg", "报到成功"); } catch (Exception e) { request.setAttribute("msg", "报到失败:" + e.getMessage()); } request.getRequestDispatcher("/report/result.jsp").forward(request, response); } }

request.setCharacterEncoding("UTF-8")这一行必须放在读取任何参数之前。它的作用是告诉 Tomcat 用 UTF-8 解码请求体,解决 POST 提交的中文乱码。Integer.parseInt在这里没有做异常处理,真实项目里要加 try-catch,否则非法参数会让页面直接 500。

Service 层里的事务控制是这段代码的精华,写出来大概长这样:

public void report(String studentNo, int dormId, int operatorId) throws Exception { Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 检查学生是否存在且未报到 StudentDao studentDao = new StudentDao(); int studentId = studentDao.checkUnreported(conn, studentNo); if (studentId == -1) { throw new RuntimeException("学号不存在或已完成报到"); } // 2. 更新学生表和宿舍表 studentDao.updateReportStatus(conn, studentId, dormId); // 3. 写入操作日志 ReportLogDao logDao = new ReportLogDao(); logDao.insert(conn, studentId, "报到", operatorId); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { DBUtil.close(conn); } }

setAutoCommit(false)是事务的开始,commit()rollback()分别对应成功与失败。关键点是同一个Connection要在三个 DAO 方法之间传递,保证它们用的是同一个数据库连接,事务才有效。很多新手在每个 DAO 里各自getConnection(),结果事务控制形同虚设。

宿舍分配的核心 SQL 同样放在这里说。分配时不是先查再改,而是用一条 UPDATE 语句原子地完成“占用一个床位”:

UPDATE dormitory SET used_count = used_count + 1 WHERE id = ? AND used_count < bed_count

这个 WHERE 条件里带上used_count < bed_count,数据库会在更新时自动判断。如果影响行数为 0,说明床位已满或宿舍不存在,代码里直接抛异常触发回滚。这就是你说的防并发、防超卖,比先 SELECT 再 UPDATE 靠谱得多。

4.2 报到单打印与按学院统计:JSP 页面与报表 SQL

报到登记完成后要打印一张报到单,上面有学生基本信息、报到时间、分配的宿舍。很多毕设源码喜欢把这个逻辑全塞在 JSP 里,页面里套一堆<% %>,能跑但没法维护。常见做法是 Servlet 里查出数据放到 request 域,JSP 里只负责展示。

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>新生报到单</title></head> <body> <h3>新生报到单</h3> <p>学号:${student.studentNo}</p> <p>姓名:${student.name}</p> <p>报到时间:<%= new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()) %></p> <p>宿舍:${student.building} - ${student.roomNo}</p> </body> </html>

页面里直接调用new java.util.Date()虽然不推荐,但在打印报到单这种一次性页面上能接受。如果你想稳妥一点,就在 Servlet 里把report_time格式化好再放进 request,JSP 就纯粹做显示。这套系统的核心追求是能跑通、能答辩,不是要做一个企业级框架。

统计报表这里给出一条万能 SQL,效果是把每个学院的报到人数、报到率一次性算出来:

SELECT c.dept_name AS 学院, COUNT(*) AS 新生人数, SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END) AS 已报到人数, ROUND(SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS 报到率 FROM student_info s JOIN class_info c ON s.class_id = c.id GROUP BY c.dept_name ORDER BY 报到率 DESC;

SUM(CASE WHEN ... THEN 1 ELSE 0 END)是条件计数的经典写法,把“统计已报到人数”这个需求在一条 SQL 里完成,不需要在 Java 里循环查库。GROUP BY按学院分组后,COUNT(*)是每个学院的总人数,比值再乘 100 就是报到率。

这段 SQL 建议单独放在StatsDao里,页面用 JSTL 的<c:forEach>遍历结果集就可以生成报表表格。如果导师要求报表导出,可以在 Servlet 里用 POI 生成 Excel,但这属于加分项,排期不够就先把页面里的表格做漂亮。

5. JSP+SQL 开发避坑:5 个常见问题与一个固定排查顺序

JSP+SQL 这套老技术栈的坑非常固定,几乎每个人都会踩一遍。我把这几年最常见的五类问题按现象、原因、解决的格式整理出来,每个都是血泪经验。你照着项目对号入座,能省下一整周的排查时间。

5.1 中文乱码:JSP 页面、Servlet 和数据库三层都要管

现象:页面上输入中文姓名,提交后数据库里变成问号;或者数据库里正常,页面上显示乱码。原因:编码链路断了一环。JSP 页面用 UTF-8,到了 Servlet 读取参数时没设置请求编码;数据库连接串里没指定字符集;建表时字符集用了默认的 latin1,这三处有一处不对就乱码。解决:统一做三件事。第一,JSP 文件头部写pageEncoding="UTF-8";第二,Servlet 里在读取第一个参数之前调用request.setCharacterEncoding("UTF-8");第三,JDBC 连接串带上字符集参数。

jdbc:mysql://localhost:3306/enrollment?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

排查时有个固定顺序:先看页面源码里 charset 是什么,再在 Servlet 里打日志看参数是否正常,最后直接打开 Navicat 看表数据。这三步从前往后走,基本十分钟定位。最常被漏掉的是连接串里没加characterEncoding,页面和 Servlet 都对,数据一进库就变问号。

5.2 SQL 注入与字符串拼接:不要在登录功能里放松警惕

现象:在用户名框输入' or '1'='1,能直接登录后台。原因:登录代码里把用户输入拼进了 SQL 字符串,比如SELECT * FROM user WHERE username='"+name+"'。输入内容被当成了 SQL 代码执行。解决:所有 SQL 一律用 PreparedStatement 参数占位符,禁止拼字符串。

String sql = "SELECT id FROM sys_user WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery();

setString会把输入内容真正当成字符串处理,引号和分号都不会再参与 SQL 解析。这条规则不只针对登录,所有接收用户输入的地方——搜索框、报到备注、班级名称——都要用参数化查询。任何"SELECT * FROM xxx WHERE name = '" + name + "'"的写法都是给自己埋雷。

5.3 驱动版本与连接串不匹配:Maven 依赖里的隐形翻车

现象:启动 Tomcat 后访问数据库相关功能,控制台报ClassNotFoundException: com.mysql.jdbc.Driver,或者报Communications link failure原因:MySQL 8.x 的 JDBC 驱动把驱动类从com.mysql.jdbc.Driver迁移到了com.mysql.cj.jdbc.Driver,同时新增了serverTimezone参数要求。老教程里的旧驱动类在部分新版本中不适用,连接串少参数直接失败。解决:pom.xml 里显式写清楚驱动版本,连接串一次写全。

com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/enrollment?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

useSSL=false是明确不使用 SSL 加密连接,本机开发环境不需要证书。allowPublicKeyRetrieval=true解决 MySQL 8.x 连接时出现的公钥检索报错。这两个参数不加,即使驱动类写对了也会连不上。遇到连接问题,先查驱动 jar 是否在WEB-INF/lib下,再查连接串参数,最后查 MySQL 服务是否启动,按这个顺序排查。

5.4 上传文件路径写死:重启 Tomcat 后照片消失

现象:新生证件照上传后能正常显示,但重启 Tomcat 或者重新部署项目,照片全没了。原因:上传文件保存到了 Tomcat 的 webapps 部署目录里,比如webapps/enroll/upload。Tomcat 重新部署时会清理工作目录,文件自然被删掉。更隐蔽的是本地 IDEA 和服务器路径不一致,本地能查到图服务器找到不。解决:文件保存到项目外部的固定磁盘目录,数据库里只存相对路径,再用 Tomcat 虚拟路径映射出去。配置方式是在 server.xml 的 Host 节点下加一行 Context,或者用 Spring 系的资源映射注解。常见做法是本地放在D:/enroll_upload,生产环境换成一个独立挂载盘。

数据库里存储的应该是2025/09/01/20250101.jpg这种相对路径,页面展示时拼上访问前缀。这样项目不管部署到哪台机器,只要外部目录跟着搬走,数据就不会丢。以后接云存储,也只需要改这个前缀。

5.5 重复提交与并发报到:小白最容易忽略的数据层防线

现象:迎新当天两个老师在同一台或两台电脑上对同一个学号连续点了两次“报到”,数据库里出现两条报到记录,或者宿舍床位被重复扣减。原因:前端没有防重复提交,后端业务代码也没有做并发校验。两个请求同时通过查询检查,都能查到“未报到”,然后各自执行插入,最终产生两条记录。解决:数据库和业务两层同时控制。第一层,student_info.student_no字段建 UNIQUE 唯一索引,重复插入直接报 DuplicateKeyException,这是最终防线;第二层,后端更新报到状态用带条件的 UPDATE,影响行数为 0 就说明已被别人处理:

UPDATE student_info SET status = 1, report_time = NOW() WHERE student_no = ? AND status = 0

前端也不是无事可做,提交后立刻把按钮设成disabled,配合后端的原子更新。宿舍扣床位同样用前面那种used_count < bed_count的条件更新。这两个条件更新写完,并发问题基本绝迹。

6. 收尾技巧:用一条 SQL 校验报到数据,给答辩留好演示场景

系统写完后,验收不能只靠肉眼点页面。我习惯在最后阶段把数据库当成“唯一事实来源”,用报表 SQL 验证录入的数据是否完整一致。下面的 SQL 在校验的同时就是答辩时最有力的演示素材,打开页面跑一次,全校报到率一览无余。

SELECT c.dept_name AS 学院, COUNT(*) AS 新生人数, SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END) AS 已报到, ROUND(SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS 报到率 FROM student_info s INNER JOIN class_info c ON s.class_id = c.id GROUP BY c.dept_name;

数据校验建议对照下面这个表格逐项过一遍。每项检查对应一个常见的交付事故,顺序不要颠倒,否则现场演示时容易翻车。

检查项操作通过标准
中文数据新建一条含中文的报到记录并刷新页面、数据库均无乱码
宿舍余量连续分配满一个房间的床位房间满员后不能再被分配
数据持久化重启 MySQL 和 Tomcat 后重新查询数据不丢失
统计报表给三个学院各造 5 条演示数据报到率计算正确
换机部署换一台电脑重新部署运行不再依赖本机 IDEA 环境

我自己的习惯是,在把系统交给学院机房之前,一定先把 Tomcat 端口从默认的 8080 改成不太容易被占用的端口,再重启一次 MySQL、重启一次 Tomcat,按上面表格跑一遍检查流程。最后一步是跑一次这条统计 SQL,看到有数据并且数字合理,才算真正收工。这样做的不只是确保功能正常,更是给自己留一条“我验证过”的底气。希望帮到你。

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

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

3个坑点拆解若函数f(x)底层原理实战项目避坑指南

3个坑点拆解若函数f(x)底层原理实战项目避坑指南 官方文档翻了三遍还是云里雾里?别慌,我懂这种抓不住重点的崩溃感。很多刚接手 实战项目 的工程师,一看到 f(x) 这种抽象定义就头大,其实核心逻辑就藏在几个关键边界条件里。 一句话原理:函数映射的确定性本质…

作者头像 李华
网站建设 2026/9/23 19:35:16

珠宝行业前景源码解析:3个报错让你项目崩盘

珠宝行业前景源码解析:3个报错让你项目崩盘 上周接了个急活,给某线下珠宝连锁做会员系统重构。老板拍胸脯说“这行现在好,数据量不大”,结果一上线,后台日志刷得跟瀑布似的。 最要命的是那条 NullPointerException 。 看着堆栈信息(StackTrace)一行行往下跳,从…

作者头像 李华
网站建设 2026/9/23 19:35:02

全球十大创意广告完整示例:3步拆解底层逻辑

全球十大创意广告完整示例:3步拆解底层逻辑 别再去翻那堆几万字、排版还乱的官方文档了,真没时间也没耐心。想搞懂【全球十大创意广告】到底为啥能火,看这篇【完整示例】就够了。…

作者头像 李华
网站建设 2026/9/23 19:34:59

IV写真底层逻辑解析:3个高频面试题拆解官方文档痛点

IV写真底层逻辑解析:3个高频面试题拆解官方文档痛点 翻开官方文档,满屏的术语和晦涩的配置项,是不是让你瞬间头晕?很多人卡在 IV写真 这个概念上,不是代码写不出来,而是搞不懂它背后的运行机理。更扎心的是,每年招聘季, 高频面试题 里关于 IV写真…

作者头像 李华
网站建设 2026/9/23 19:34:52

室内定位RSS指纹法配KNN:MATLAB快速入门实战

简介&#xff1a;这份资源面向室内定位方向的初学者与工程实践者&#xff0c;提供RSS位置指纹法结合KNN算法的完整MATLAB实现&#xff0c;帮助读者在GPS信号难以覆盖的室内环境中理解并复现基于信号强度的定位流程。包内共2个文件&#xff0c;包含1个mat数据文件与1个m脚本文件…

作者头像 李华
网站建设 2026/9/23 19:34:48

288001报错栈解析:2026最新性能优化实战

288001报错栈解析:2026最新性能优化实战 盯着屏幕上一长串红色的Stack Trace,手指悬在键盘上半天敲不下去。这种“报错一堆看不懂”的焦虑,几乎每个刚接触后端或底层开发的学员都经历过。尤其是当你看到 288001…

作者头像 李华