简介:面向JavaWeb初学者与毕业设计学生的学生宿舍管理系统完整源码项目,整合登录鉴权、学生信息管理、宿舍信息维护、水电费管理等常见功能模块,可用于课程设计、期末大作业或毕业设计参考,难度适中,适合作为JavaWeb分层开发的入门与进阶练习。包内共133个文件,以Java源码、JSP页面、JavaScript脚本为主,辅以Servlet控制器、XML配置、CSS样式及SQL数据库脚本,压缩包仅3.3MB,目录结构清晰,导入IDE后即可对照运行,便于逐模块阅读,省去从零搭建环境的麻烦。项目已经本地编译验证通过,评审分达到98分,整体功能完整、交互逻辑直观,涵盖查询、新增、删除、修改、登录校验、Excel导入等业务场景,从数据访问对象DAO到控制层Servlet再到前端JSP形成完整闭环。已有143人学习使用,配合SQL脚本与工程配置文件,可快速理解数据库表设计、JDBC操作、Web层请求分发等核心知识点,对毕业设计答辩和期末验收都有直接帮助。
1. 学生宿舍管理系统,为什么JavaWeb课程设计总拿它当高分模板
每年课程设计答辩季,总有一批人对着“学生宿舍管理系统”这个需求无从下手:功能看着不多,可真要做完,登录、宿舍分配、调宿、来访登记、水电费统计全得落地,数据库至少五张表起步。这个题目恰好踩中了JavaWeb教学案例里最核心的考点——JSP/Servlet、JDBC连接池、三层架构、MySQL增删改查,一套做下来,课程设计里的评分点基本覆盖全了。这篇笔记不点评某个特定源码包的水准,而是把这个方向拆开:拿到一份“javaweb学生宿舍管理系统源码+数据库”之后,怎么在最短时间内看懂骨架、把库跑起来、把功能改到自己能讲清楚的程度。适合正在做JavaWeb或数据库课程设计的人,也适合想拿现成项目当起点、再往上面加自己想法的人。
2. 拆开高分源码的骨架:Servlet、DAO与JSP各管什么
拿到一份JavaWeb源码,第一件事不是急着启动,而是先把目录结构摸清楚。高分项目和低分项目之间最大的分水岭,不是功能多少,而是代码有没有分层。评委老师问的第一个问题往往是:“你的请求是怎么从前端走到数据库的?”答不清楚,功能再全也拿不到高分。
2.1 先看web.xml:一个请求到底怎么走到处理代码的
常见做法是标准Eclipse或IDEA里的JavaWeb工程结构。先把源码导入IDEA,正常识别为Web项目后,优先打开web/WEB-INF/web.xml。这个文件是整个项目的路由总表,读懂了它,你就知道每个URL对应哪个Servlet,从而反推整个系统有哪些功能模块。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>StudentDormitorySystem</display-name> <!-- 登录控制:负责账号密码校验 --> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.dorm.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <!-- 宿舍管理:负责列表展示和查询 --> <servlet> <servlet-name>DormServlet</servlet-name> <servlet-class>com.dorm.servlet.DormServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DormServlet</servlet-name> <url-pattern>/dorm</url-pattern> </servlet-mapping> <!-- 默认首页 --> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app>逻辑说明:servlet-class指向编译后的类全限定名,url-pattern是浏览器访问的路径。比如用户访问http://localhost:8080/dorm/login,Tomcat根据url-pattern匹配到LoginServlet,调用它的doPost方法。路径映射有精确匹配和通配符匹配两种,课设项目里基本都是精确匹配,一个Servlet对应一个功能。
参数说明:web-app的version决定了Servlet规范的版本,对应Tomcat 9用4.0,Tomcat 8.5用3.1。如果版本写高了,旧Tomcat启动时报错;写低了,新Tomcat也能跑。这一点在多人合作或换电脑运行时最容易翻车,后面避坑章节里细说。
2.2 控制层与DAO层:登录和宿舍列表的最小可运行代码
分层的好处在登录功能上体现得最明显。一个成熟的课设源码,控制层只做三件事:拿参数、调服务、转发页面。具体的SQL操作被封装在DAO类里。下面这段代码是登录控制层的典型写法。
package com.dorm.servlet; import com.dorm.dao.UserDao; import com.dorm.entity.User; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 第一步:设置编码,防止中文用户名乱码 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); // 第二步:调DAO层校验 UserDao userDao = new UserDao(); User user = userDao.findByUsernameAndPassword(username, password); // 第三步:跳转或转发 if (user != null) { HttpSession session = request.getSession(); session.setAttribute("currentUser", user); response.sendRedirect(request.getContextPath() + "/dorm"); } else { request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } }逻辑说明:先取请求参数,再调DAO层方法,最后根据结果决定是重定向到主页面还是转发回登录页并携带错误提示。这里有个容易混淆的细节——sendRedirect是浏览器重新发一次请求,地址栏会变,会丢request域数据;forward是服务器内部跳转,地址栏不变,request域数据还在。所以登录失败用forward能把errorMsg传回页面,登录成功用redirect避免刷新后重复提交表单。
参数说明:request.getParameter("username")拿的是表单里name="username"的输入框值,前后端字段名必须一致,这是初学阶段最高频的404和空指针来源。setCharacterEncoding("UTF-8")只对POST请求的请求体生效,如果项目用了GET请求传中文,还需要在Tomcat的server.xml里配URIEncoding,这就是另一个坑了。
再看DAO层,这是SQL语句聚集的地方。一个学生宿舍管理系统的DAO层通常围绕student、dorm、user三张核心表转,查询宿舍列表是必写的功能。
package com.dorm.dao; import com.dorm.entity.Dorm; import com.dorm.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class DormDao { public List<Dorm> findAllDorms() { String sql = "SELECT d.id, d.building, d.room_number, d.capacity, " + "COUNT(s.id) AS current_count " + "FROM dorm d LEFT JOIN student s ON d.id = s.dorm_id " + "GROUP BY d.id, d.building, d.room_number, d.capacity"; List<Dorm> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Dorm dorm = new Dorm(); dorm.setId(rs.getInt("id")); dorm.setBuilding(rs.getString("building")); dorm.setRoomNumber(rs.getString("room_number")); dorm.setCapacity(rs.getInt("capacity")); dorm.setCurrentCount(rs.getInt("current_count")); list.add(dorm); } } catch (SQLException e) { e.printStackTrace(); } return list; } }逻辑说明:这条SQL用了LEFT JOIN加GROUP BY,查出来的是每个宿舍的编号、楼栋、床位容量、已住人数。关联表并统计是宿舍管理系统里最高频的查询模式,也是评委最喜欢追问的SQL知识点。使用PreparedStatement而不是Statement,是为了防止SQL注入,这个也是答辩常问题之一。
参数说明:DBUtil.getConnection()是自定义工具类,内部封装了JDBC驱动的加载和连接获取,常见实现方式有DBCP、C3P0连接池和直接用DriverManager.getConnection()。连接池的好处是复用连接,避免每次请求都新建数据库连接而拖慢响应。课设阶段用C3P0或HikariCP的都有,源码里若用的是裸DriverManager也别急着否定——小型系统扛得住,但答辩时要把“为什么没用连接池”这个问题准备好。
2.3 视图层的取舍:JSP里到底放多少逻辑
很多课设源码翻车就翻在JSP页面里塞了大量Java脚本片段(<% ... %>)。这能跑,但代码一多,页面文件就变成黑匣子,连原作者自己都很难维护。高分的源码在视图层通常遵循一个原则:JSP只负责显示,逻辑全部收口在Servlet和DAO里。
正确姿势是使用EL表达式和JSTL标签库遍历数据。比如展示宿舍列表页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>宿舍列表</title> </head> <body> <table border="1"> <tr> <th>楼栋</th> <th>房间号</th> <th>容量</th> <th>已住人数</th> <th>操作</th> </tr> <c:forEach items="${dormList}" var="dorm"> <tr> <td>${dorm.building}</td> <td>${dorm.roomNumber}</td> <td>${dorm.capacity}</td> <td>${dorm.currentCount}</td> <td> <a href="dorm?action=detail&id=${dorm.id}">查看</a> <a href="dorm?action=edit&id=${dorm.id}">编辑</a> </td> </tr> </c:forEach> </table> </body> </html>逻辑说明:${dormList}是Servlet在转发前用request.setAttribute("dormList", list)放进request域的数据,c:forEach遍历输出。页面里没有任何Java代码,万一页面报错,问题要么出在数据没传进来,要么出在JSTL标签库没引入,排查范围小很多。
参数说明:items="${dormList}"里的dormList变量名必须和Servlet里setAttribute时用的key完全一致,这是EL表达式最常见的取值失败原因。另外c前缀是taglib指令里uri决定的,如果项目用的Tomcat版本较新,可能还需要额外下载jstl-api和jstl-impl两个jar包,很多404和500错误就是缺这个引起的。
3. MySQL数据库脚本先行:建表、初始化数据与宿舍分配的SQL逻辑
标题里的“数据库”三个字分量不轻。宿舍管理系统的核心不在于页面多好看,而在于表设计是否合理。看源码的第一步是看SQL脚本,脚本通读一遍,系统的完整功能图景就出来了。常见的宿舍管理系统最少需要三张表:管理员表(或用户表)、学生表、宿舍表,再加上调宿记录、来访登记等扩展表。评分老师看的是表间关系是否清晰、字段类型是否合理、有没有冗余设计。
3.1 三张核心表的关系与建表SQL
表关系其实很直观:一个宿舍住多个学生,所以宿舍表和学生表是一对多关系,学生表通过外键dorm_id关联宿舍表。管理员表和前两者没有直接关系,它独立存在,只负责登录认证。下面是标准的建库建表脚本。
-- 建库 CREATE DATABASE IF NOT EXISTS dormitory_db DEFAULT CHARACTER SET utf8mb4; USE dormitory_db; -- 1. 管理员表 CREATE TABLE t_admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, real_name VARCHAR(20) DEFAULT NULL ); -- 2. 宿舍表 CREATE TABLE t_dorm ( id INT PRIMARY KEY AUTO_INCREMENT, building VARCHAR(20) NOT NULL COMMENT '楼栋名,如 学一楼', room_number VARCHAR(10) NOT NULL COMMENT '房间号,如 301', capacity INT NOT NULL DEFAULT 4 COMMENT '额定入住人数', bed_number INT DEFAULT 0 COMMENT '空床位数,冗余字段', UNIQUE KEY uk_building_room (building, room_number) ); -- 3. 学生表 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号', name VARCHAR(20) NOT NULL, gender VARCHAR(4) DEFAULT '男', phone VARCHAR(11), dorm_id INT, bed_index INT COMMENT '床位编号 1-4', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (dorm_id) REFERENCES t_dorm(id) );逻辑说明:t_dorm表里的bed_number是反范式设计的冗余字段,目的就是查空床位时不用每次COUNT统计。这种冗余在课设里很常见,答辩被问到“为什么冗余”时能解释清楚就是加分项。t_student表里的bed_index配合dorm_id使用,用来标识某个学生在某宿舍的具体床位。
参数说明:VARCHAR(20)对宿舍房间号和学号长度足够;int类型的capacity和bed_index注意取值范围,宿舍容量上限通常不会超过8。字符集统一用utf8mb4而不是utf8,因为utf8在MySQL里最多存3字节,遇到生僻字或特殊符号会报错,这属于典型的改脚本时容易被忽略的细节。外键在这里是物理外键,所以插入学生数据时dorm_id必须保证在宿舍表里存在,否则直接报错。有些课设源码为了图省事会去掉外键,靠应用层保证数据一致,但课程设计里必须带物理外键,这是评分硬指标之一。
3.2 宿舍分配与入住率统计:两个必问的SQL写法
宿舍分配的逻辑是:学生入住时,先查目标宿舍当前已住人数是否小于容量,小于则床位编号+1并可入住。这个操作在源码里通常对应一个assignDorm的DAO方法,SQL写成事务。入住率统计则是典型的GROUP BY+JOIN组合,答辩必问。下面两条SQL基本覆盖了这两个核心场景。
-- 查询所有宿舍的入住率,按入住人数降序排列 SELECT d.building, d.room_number, d.capacity, COUNT(s.id) AS current_count, (d.capacity - COUNT(s.id)) AS empty_beds, CONCAT(ROUND(COUNT(s.id) * 100.0 / d.capacity, 1), '%') AS occupancy_rate FROM t_dorm d LEFT JOIN t_student s ON d.id = s.dorm_id GROUP BY d.id, d.building, d.room_number, d.capacity ORDER BY empty_beds ASC; -- 查询所有未入住的学生,即没有分配宿舍的学生,用于办理入住功能 SELECT student_no, name, gender, phone FROM t_student WHERE dorm_id IS NULL;逻辑说明:第一条SQL用LEFT JOIN保证没住人的宿舍也出现在结果里,GROUP BY后面带着d.id防止不同宿舍同房间号被合并处理。ROUND函数保留一位小数输出入住率,这种带百分比格式的输出在页面表格上直接展示,效果很直观。第二条SQL查dorm_id IS NULL的学生,是入住登记页面的数据基础。
参数说明:COUNT(s.id)统计的是关联上的学生行数,如果学生表里dorm_id为空的记录会被LEFT JOIN正常关联,但因为条件在GROUP BY里已经限制,不会误统计。ROUND(COUNT(s.id) * 100.0 / d.capacity, 1)里写出100.0而不是100是为了让结果转成浮点数,避免整数除法得到0。这是写SQL时极容易踩的坑,整数相除在MySQL里默认取整,加一个小数点就能绕开。
3.3 测试数据怎么造:手动insert与批量生成的平衡
源码包里通常附带一个init.sql,里面是十几条手写的插入语句。这种数据量对演示够用,但如果你要测试“宿舍满员时禁止分配”的功能,就必须自己造一批能触发边界条件的数据。手写几十条insert不是不能干,只是效率太低,而且容易写错外键。
批量造测试数据有两条路。一条是写存储过程,用WHILE循环生成;另一条是在Java代码里用for循环调DAO的插入方法。课设阶段用存储过程更直接,因为脚本一执行就完事,不用跑工程。下面是生成100个学生分配到10个宿舍的存储过程:
DROP PROCEDURE IF EXISTS generate_student_data; DELIMITER $$ CREATE PROCEDURE generate_student_data() BEGIN DECLARE i INT DEFAULT 0; WHILE i < 100 DO SET i = i + 1; INSERT INTO t_student (student_no, name, gender, dorm_id, bed_index) VALUES ( CONCAT('2024', LPAD(i, 4, '0')), CONCAT('测试学生', i), IF(i MOD 2 = 0, '男', '女'), MOD(i, 10) + 1, MOD(i, 4) + 1 ); END WHILE; END$$ DELIMITER ; CALL generate_student_data(); -- 更新每间宿舍的空床位数量 UPDATE t_dorm d SET bed_number = d.capacity - (SELECT COUNT(*) FROM t_student s WHERE s.dorm_id = d.id);逻辑说明:LPAD(i, 4, '0')把1转成0001,拼接出稳定的学号序列。MOD(i, 10) + 1让100个学生平均分布在10间宿舍里,MOD(i, 4) + 1则让床位编号在1到4之间循环。最后一条UPDATE把每间宿舍的空床位数同步成最新值,保证冗余字段和实际数据一致。
参数说明:DELIMITER $$是MySQL命令行客户端专用的语法,如果你用Navicat或IDEA的数据库工具执行,可以省略这行。存储过程执行时如果遇到报错,第一步检查前面有没有DROP PROCEDURE IF EXISTS,否则重复执行时会报“already exists”错误。
4. 从零跑通源码的避坑清单:Tomcat版本、编码与404排查
拿源码、配环境、启动、看报错,这是每个课设人必经的四部曲。这章把JavaWeb项目落地时最高频的坑按“现象 → 原因 → 解决”三段式列出来。这些坑我基本都踩过一遍,有些是帮别人排查时见到的,每条背后的原理都值得记下来,答辩时讲出来反而是加分项。
4.1 现象:Tomcat能启动,但访问首页报404
原因:web.xml里welcome-file配置的是login.jsp,但实际文件放在web目录的根路径下,文件名叫login.html或不在根目录。另一种情况是导入项目后IDEA没有把web目录标记为Web资源目录,导致编译后没有生成对应的文件。
解决:第一步打开Project Structure → Facets,确认Web模块存在,并且Web Resource Directory指向了web目录。第二步看web.xml里welcome-file的路径是否和磁盘路径完全一致,包括大小写和文件名后缀。login.jsp和Login.jsp在Windows上可能不报错,在Linux服务器上必然404,尽早统一成小写最省事。
4.2 现象:页面和数据库里的中文全部变成问号
原因:三层编码不一致。JSP文件本身的编码、Servlet里request.setCharacterEncoding、数据库连接串的characterEncoding参数,任意一层断了都会出乱码。最常见的翻车点是连接串里只写了useSSL=false,忘了加characterEncoding=UTF-8。
解决:第一,JSP文件头统一加<%@ page contentType="text/html;charset=UTF-8" %>;第二,所有Servlet的doPost方法第一行写request.setCharacterEncoding("UTF-8");第三,数据库连接串完整写成下面这种格式:
String url = "jdbc:mysql://localhost:3306/dormitory_db" + "?useUnicode=true&characterEncoding=UTF-8" + "&useSSL=false&serverTimezone=Asia/Shanghai";逻辑说明:useUnicode=true和characterEncoding=UTF-8告诉MySQL驱动连接建立时就用UTF-8编码交互。serverTimezone=Asia/Shanghai是MySQL 8.x驱动必须加的,否则报时区错误,这是新版驱动和旧版本之间的典型兼容性问题,源码里不同版本驱动混搭最容易在这个位置暴露。
参数说明:如果你的驱动是com.mysql.jdbc.Driver,对应MySQL 5.x老版本连接串;驱动类名是com.mysql.cj.jdbc.Driver,对应MySQL 8.x,连接串里必须有serverTimezone且不能有旧的useUnicode写法。这两个驱动类名差一个cj,报“ClassNotFoundException”时先看是不是这里搞混了。
4.3 现象:IDEA运行时提示“源服务器未能找到目标资源的表示”或直接报ClassNotFoundException
原因:新版本的IDEA和Tomcat集成后,默认的编译输出路径和项目实际路径不一致。Tomcat运行时会去找out/artifacts/目录,而你改的代码编译到了target/classes,导致旧class文件被反复加载,改了代码不生效。
解决:在File → Project Structure → Artifacts里确认输出目录的路径,建议直接改成项目名对应的out/artifacts/exploded结构。改完后做一次Build → Rebuild Project,把旧的临时文件清干净再跑。这个过程网上常说的“IDEA运行javaweb项目配置”主要就是指这一步,配置不对,整个项目的源码就像没改过一样,非常容易让人误以为代码有Bug。
4.4 现象:启动时报“NoClassDefFoundError”或“ClassNotFoundException”与数据库相关
原因:JDBC驱动jar包没放对位置。很多新手把mysql-connector-java的jar包放在lib目录下,但JavaWeb项目的Web资源目录是WEB-INF/lib,Tomcat只加载这个目录下的jar包。
解决:把jar包拷贝到web/WEB-INF/lib目录下,然后在IDEA的Project Structure → Libraries里引入这个目录。注意两个动作要同时做,光放文件不引入,编译器不认;光引入不放文件,Tomcat运行时不认。这个配好之后,数据库连接才不会是黑匣子式的“报错但不知道去哪看”。第一次运行起来后,建议在DBUtil的getConnection方法后面加一行System.out.println(conn),把连接对象打印出来,看到com.mysql.cj.jdbc.ConnectionImpl字样,就说明这一步配通了。
4.5 现象:修改了数据库表结构,但页面查询结果没变化
原因:大概率是连的数据库不对。开发环境里可能同时存在多个MySQL实例,或者一个实例里有多个同构但数据不同的库。源码里DBUtil类的连接URL写死了库名,你往dormitory_db_2里插数据,但程序连的是dormitory_db。
解决:连接串里的库名改成你初始化数据时实际写入的库名。用一个MySQL客户端看下SHOW DATABASES列表,确认你引用的库名和程序里写的一致。顺手在DBUtil里把连接参数打出来,分不清时打印一遍最直接。数据库连接这种问题,排查方法大于记忆,知道怎么验证远比记住某条报错更值钱。
5. 从跑通到能答辩:给系统加两个功能并验证稳定性
跑通源码只是起点,答辩能不能过,拼的是你对项目的理解深度和现场应变能力。这里给两个低成本高收益的改造方向,一个偏功能,一个偏稳定性,改完之后整个系统的完成度会明显上一个台阶。
先列一张改造点清单,用表格规划时间和涉及文件:
| 改造点 | 具体做法 | 涉及文件 |
|---|---|---|
| 宿舍状态自动标记 | 页面表格里根据empty_beds字段值为0时显示“已满”红字 | dormList.jsp |
| 重复学号提示 | 新增学生时先查学号是否存在,存在则返回友好提示 | AddStudentServlet |
| 宿舍分配前校验 | 入住时检查目标宿舍空床位数,满员则禁止分配 | AssignDormServlet |
| 密码加密存储 | 注册或新增管理员时用MD5加密后再入库 | AdminDao.java |
加一个“宿舍已满不允许再分配”的功能,是改动量小但最能体现业务逻辑的做法。在AssignDormServlet里加一个前置校验:
// 查询目标宿舍当前已住人数 String checkSql = "SELECT capacity, " + "(SELECT COUNT(*) FROM t_student WHERE dorm_id = ?) AS used " + "FROM t_dorm WHERE id = ?"; PreparedStatement ps = conn.prepareStatement(checkSql); ps.setInt(1, dormId); ps.setInt(2, dormId); ResultSet rs = ps.executeQuery(); if (rs.next()) { int capacity = rs.getInt("capacity"); int used = rs.getInt("used"); if (used >= capacity) { request.setAttribute("errorMsg", "该宿舍已满,请选择其他宿舍"); request.getRequestDispatcher("/dorm?action=assign").forward(request, response); return; } }逻辑说明:这段代码在真正执行插入之前先查目标宿舍的已住人数和容量,满了就转发回分配页面并提示错误。注意这里不是简单的if判断,而是利用子查询一次性拿到两个关键数据,比先查宿舍再查学生数少跑一次数据库。
参数说明:setInt(1, dormId)和setInt(2, dormId)分别对应SQL里两个?问号,顺序不能换。如果源码里已经有bed_number冗余字段,也可以改成查这个字段,效果一样但SQL更短。我一般喜欢用实时COUNT的方式,因为冗余字段可能在别处被改漏,实时统计更可靠。
验证方法也很简单:先通过测试数据把某个宿舍填满,然后用管理员账号登录系统,对该宿舍执行分配入住操作,观察系统是否弹出了预期的错误提示。再验证一个边界:宿舍当前入住3人、容量4人的情况下分配第4人应该成功,第5人必须失败。这个边界测试直接对应答辩时老师最爱问的“你如何保证数据不超员”。
最后说一个我自己的习惯:做完任何改动后,重启Tomcat前先看一眼控制台尾巴有没有Exception,不要只看页面效果。“页面正常”和“后台没报错”完全是两回事,打印日志比用眼睛检查可靠得多。这套流程跑下来,项目里每个表、每条核心SQL你都能讲得清来龙去脉,答辩想翻车都难。希望帮到你。
本文还有配套的精品资源,点击获取