
简介这是一套面向Java Web初学者与课程设计需求的完整学生宿舍管理系统源码基于JSPServletMySQL技术栈实现适合作为毕业设计、课程大作业或Java Web入门练手项目。系统区分管理员与学生两种角色管理员可对学生信息增删查改、登记与查看卫生评比、维护综合宿舍信息并处理维修状况学生则可查看修改个人信息、提交报修、查看卫生评比结果。压缩包共137个文件约3.39MB包含44个java源文件与44个class编译文件、26个jsp页面、3个css样式、1个sql建库脚本及若干gif、js、jar等资源并附E-R图与说明文档目录结构清晰便于对照学习Servlet跳转与数据库操作逻辑。目前已有3001人学习下载经多次测试运行无误可直接部署调试帮助读者快速理解JavaWeb分层开发流程与常见排错思路。1. 从一张宿舍床位表说起这套系统到底在解决什么问题很多做课程设计或毕设的同学第一次拿到“Web 学生宿舍管理系统”这个题目时脑子里蹦出来的往往是“不就是几张表的增删改查吗”。真动手才发现床位分配、入住退宿、楼栋房间层级、学生与床位的绑定关系随便一个环节没设计好后面就是连锁翻车。这套基于 JavaJSPMySQL 的实现核心要解决的是把“楼栋—楼层—房间—床位—学生”这条链路用关系模型固定下来再通过 Web 界面完成日常管理动作。它适合两类人一类是需要一套能跑通、能讲清 E-R 关系、能交出 SQL 脚本的课程设计者另一类是想借这个场景练手 JSP 前后端交互和 JDBC 数据访问的初学者。E-R 图和 SQL 语句不是附属品它们决定了系统能不能扩展、查询会不会慢、数据会不会脏。2. 先把数据模型立住E-R 图怎么画才不会被返工2.1 实体识别与关系拆解做这套系统第一步不是打开 IDE而是拿纸把实体列清楚。常见实体有学生、宿舍楼、房间、床位、管理员、入住记录。这里最容易出问题的是“床位”和“学生”的关系。有人图省事直接在学生表里加一个“床位号”字段结果一个床位换人、一个学生换床位历史记录全丢。正确做法是把“入住”抽成独立的关系实体记录学生、床位、入住时间、退宿时间、状态。这样床位是资源学生是使用者入住记录是绑定关系三者分离。E-R 图里宿舍楼与房间是一对多房间与床位是一对多学生与入住记录是一对多床位与入住记录也是一对多。管理员可以单独成表也可以只做登录校验。画图时注意标注主键和外键尤其是房间表里的“楼栋 ID”和床位表里的“房间 ID”这两个外键不写清楚后面写 SQL 关联查询时会非常痛苦。2.2 从 E-R 图到关系模式的转换规则E-R 图转关系模式有固定套路每个实体一张表一对多关系把“一”的主键放到“多”的表里做外键多对多关系单独建表。入住记录本质上是一个带属性的关系所以它单独成表里面同时持有学生 ID 和床位 ID 作为外键。下面这张表可以作为字段设计的参考实际写 SQL 时按这个结构落地。表名关键字段说明studentid, name, student_no, gender, class_name, phone学生基本信息student_no 唯一buildingid, building_name, floors, manager楼栋信息roomid, building_id, room_no, capacity, occupiedbuilding_id 外键occupied 为已住人数bedid, room_id, bed_no, statusroom_id 外键status 标记空闲/占用checkinid, student_id, bed_id, checkin_time, checkout_time, status入住记录status 标记在住/已退提示capacity 和 occupied 这类冗余字段可以保留但必须在入住和退宿操作里同步更新否则会出现“房间显示满员但实际有空床”的玄学问题。2.3 建表 SQL 与索引设置字段定完后直接写建表语句。注意字符集用 utf8mb4外键约束按需开启。如果只是课程设计外键可以不加靠程序保证一致性但如果想练数据库建议加上。-- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender CHAR(2), class_name VARCHAR(50), phone VARCHAR(20) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 楼栋表 CREATE TABLE building ( id INT PRIMARY KEY AUTO_INCREMENT, building_name VARCHAR(50) NOT NULL, floors INT DEFAULT 1, manager VARCHAR(50) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 房间表 CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL, room_no VARCHAR(20) NOT NULL, capacity INT DEFAULT 4, occupied INT DEFAULT 0, FOREIGN KEY (building_id) REFERENCES building(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 床位表 CREATE TABLE bed ( id INT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, bed_no VARCHAR(10) NOT NULL, status TINYINT DEFAULT 0, -- 0空闲 1占用 FOREIGN KEY (room_id) REFERENCES room(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 入住记录表 CREATE TABLE checkin ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, bed_id INT NOT NULL, checkin_time DATETIME DEFAULT CURRENT_TIMESTAMP, checkout_time DATETIME, status TINYINT DEFAULT 1, -- 1在住 0已退 FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (bed_id) REFERENCES bed(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里student_no 加了唯一约束防止重复录入bed 表的 status 和 room 表的 occupied 是联动字段入住时都要改。索引方面student_no、room_no、bed_no 这些经常用来查询的字段即使不加唯一约束也建议加普通索引。外键在 InnoDB 下会自动创建索引但如果你用的是 MyISAM就得手动加。3. 用 JSPServletJDBC 把增删改查跑通3.1 项目分层与目录结构JSP 容易写成“页面里塞满 Java 代码”后期没法维护。我一般会按 Servlet 做控制器、JSP 做视图、DAO 做数据访问来分。目录结构大致是src/dao、src/servlet、src/model、WebContent/jsp、WebContent/WEB-INF/lib。MySQL 驱动 jar 放 lib 下不要用 build path 乱引。模型类用最简单的 JavaBean字段和表一一对应。DAO 里写 JDBC 连接和 SQL 执行Servlet 负责接收参数、调用 DAO、跳转 JSP。这样分层后改数据库只动 DAO改界面只动 JSP不会牵一发动全身。3.2 数据库连接与 DAO 基类JDBC 连接建议抽一个工具类避免每个 DAO 都写一遍 DriverManager。连接参数放配置文件或常量里注意 MySQL 8 的驱动类名和 URL 参数。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/dorm?useSSLfalseserverTimezoneUTCcharacterEncodingutf8; 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(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (Exception e) {} try { if (ps ! null) ps.close(); } catch (Exception e) {} try { if (conn ! null) conn.close(); } catch (Exception e) {} } }URL 里的useSSLfalse和serverTimezoneUTC是 MySQL 8 常见坑不写可能报时区错误或 SSL 警告。驱动类名是com.mysql.cj.jdbc.Driver不是老版本的com.mysql.jdbc.Driver。连接关闭顺序是先 ResultSet 再 PreparedStatement 再 Connection反了可能报错。3.3 入住与退宿的核心事务入住操作要同时做三件事往 checkin 插一条记录、把 bed 的 status 改成 1、把 room 的 occupied 加 1。这三步必须在一个事务里否则中途失败就会出现床位被占但记录没写的情况。public boolean checkIn(int studentId, int bedId) { Connection conn null; PreparedStatement ps1 null, ps2 null, ps3 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 插入入住记录 ps1 conn.prepareStatement(INSERT INTO checkin(student_id, bed_id, status) VALUES(?,?,1)); ps1.setInt(1, studentId); ps1.setInt(2, bedId); ps1.executeUpdate(); // 2. 更新床位状态 ps2 conn.prepareStatement(UPDATE bed SET status1 WHERE id? AND status0); ps2.setInt(1, bedId); int bedRows ps2.executeUpdate(); // 3. 更新房间已住人数 ps3 conn.prepareStatement(UPDATE room SET occupiedoccupied1 WHERE id(SELECT room_id FROM bed WHERE id?)); ps3.setInt(1, bedId); ps3.executeUpdate(); if (bedRows 0) { conn.rollback(); return false; } conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (Exception ex) {} e.printStackTrace(); return false; } finally { DBUtil.close(conn, ps1, null); } }这里UPDATE bed ... WHERE status0是关键如果床位已经被占更新影响行数为 0直接回滚避免重复入住。退宿逻辑反过来把 checkin 的 status 改成 0、checkout_time 填当前时间、bed 的 status 改回 0、room 的 occupied 减 1。同样要放在事务里。3.4 JSP 页面与表单提交JSP 只负责展示和提交不要在页面里写数据库逻辑。列表页用 JSTL 循环输出表单提交到对应的 Servlet。下面是一个简化的入住表单片段。form actionCheckInServlet methodpost 学生学号input typetext namestudentNo requiredbr 床位编号input typetext namebedNo requiredbr input typesubmit value办理入住 /formServlet 里先根据学号和床位编号查出对应的 id再调用 DAO 的 checkIn 方法。注意中文乱码问题request.setCharacterEncoding(UTF-8)要放在取参数之前。JSP 页面顶部也要% page contentTypetext/html;charsetUTF-8 %。4. 避坑与排查这套系统最容易翻车的五个地方4.1 中文乱码从请求到数据库一路飘红现象是表单提交后数据库里存的是问号或者页面显示乱码。原因通常有三层JSP 页面编码、请求编码、数据库连接编码。解决方法是三处都设 UTF-8JSP 的 page 指令、Servlet 的 setCharacterEncoding、JDBC URL 的 characterEncodingutf8。数据库和表的字符集也要确认是 utf8mb4用SHOW CREATE TABLE student;看一眼就知道。4.2 床位状态和入住记录不一致现象是学生退宿了但床位还显示占用或者房间已住人数对不上。原因多半是退宿操作没有放在事务里或者只改了 checkin 表忘了改 bed 和 room。解决方法是把入住和退宿都写成事务并且在退宿时用UPDATE bed SET status0 WHERE id?和UPDATE room SET occupiedoccupied-1 WHERE id?同步更新。如果已经出现脏数据写一条修复 SQL 按 checkin 表的在住记录重算 bed 和 room 的状态。4.3 MySQL 8 驱动和时区报错现象是启动时报No suitable driver found或The server time zone value is unrecognized。原因是驱动类名写错或 URL 没带时区参数。解决方法是确认 lib 下是 mysql-connector-java 8.x 的 jar驱动类用com.mysql.cj.jdbc.DriverURL 加上serverTimezoneUTC。如果还是不行检查端口和数据库名是否写对。4.4 JSP 里写业务逻辑导致后期改不动现象是页面里大段 Java 代码改一个查询要翻好几个文件。原因是初期图快把 DAO 逻辑直接写在 JSP 的% %里。解决方法是强制自己分层JSP 只做展示Servlet 做控制DAO 做数据。已经写乱的可以先把数据库操作抽到 DAO再把 Servlet 里的跳转逻辑理清逐步替换。4.5 并发入住同一床位现象是两个管理员同时给同一个床位办入住结果两条记录都插进去了。原因是查询和更新之间有间隙没有锁。解决方法是在事务里用UPDATE bed SET status1 WHERE id? AND status0的判断影响行数或者在查询床位时加FOR UPDATE。课程设计场景并发不高但养成这个习惯没坏处。5. 让这套系统更耐用的几个进阶技巧5.1 用视图简化多表关联查询宿舍管理里最常用的查询是“某个楼栋下每个房间住了哪些学生”。如果每次都写四张表关联SQL 会很长。可以建一个视图把常用关联固定下来。CREATE VIEW v_room_student AS SELECT b.building_name, r.room_no, bed.bed_no, s.name, s.student_no, c.checkin_time FROM checkin c JOIN student s ON c.student_id s.id JOIN bed ON c.bed_id bed.id JOIN room r ON bed.room_id r.id JOIN building b ON r.building_id b.id WHERE c.status 1;视图的好处是查询简单坏处是更新受限。这里只用来做展示查询不通过视图改数据所以没问题。查询时直接SELECT * FROM v_room_student WHERE building_name某楼就行。5.2 分页查询避免全表加载学生和入住记录多了以后列表页一次性查全部会慢。MySQL 用LIMIT做分页配合COUNT(*)算总页数。SELECT * FROM student ORDER BY id LIMIT 0, 10; SELECT COUNT(*) FROM student;JSP 里根据总记录数和每页条数算出总页数再渲染上一页下一页链接。注意LIMIT的偏移量从 0 开始页码从 1 开始时记得减一。5.3 用导出 SQL 脚本做数据备份课程设计交作业时通常要附上数据库脚本。用mysqldump导出结构和数据比手动写 INSERT 靠谱。mysqldump -u root -p dorm dorm_backup.sql导出的文件里包含建表语句和 INSERT 语句恢复时mysql -u root -p dorm dorm_backup.sql即可。注意导出前确认字符集避免中文变乱码。5.4 一个我常犯的教训早期做这类系统我总想着先把页面画漂亮结果数据库字段改来改去页面跟着返工。后来学乖了先把 E-R 图和建表 SQL 定死跑几条测试数据确认关联查询没问题再动手写 JSP。这个顺序反过来省下的时间够你多调两轮分页。希望帮到你。本文还有配套的精品资源点击获取