JSP+MySQL宿舍管理系统实战:从建表到避坑的完整指南

📅 发布时间:2026/10/10 14:34:12
JSP+MySQL宿舍管理系统实战:从建表到避坑的完整指南
简介这份实训作业资源面向计算机相关专业学生与Java Web初学者提供一套基于JSP与MySQL的学生宿舍管理系统完整实现可用于课程设计、毕业实训或自学练手。系统围绕学生信息、宿舍登记、住宿分配与调整、费用管理、在线报修、统计报表及用户权限等模块展开帮助读者理解动态网页与关系型数据库的协作方式。压缩包共67个文件约5.55MB包含17个java源文件与17个class编译文件、10个jsp页面、8个xml配置、7个jar依赖以及sql建库脚本、properties配置和docx设计文档覆盖从源码、数据库到说明文档的完整链路。目前已有69人学习下载。读者可据此掌握Servlet与JavaBean分层写法、web.xml与pom.xml配置、数据库表结构设计及项目部署流程并借助文档快速完成环境搭建与功能验证是一份结构清晰、便于二次开发的实训参考方案。1. 从一份实训作业说起JSPMySQL 宿舍管理系统到底能跑多远很多同学拿到“基于JSPMySQL的学生宿舍管理系统”这个实训题目时第一反应是去网上找一份现成源码改改界面、换个数据库名就交差。但真正做过的人都知道这类系统最值钱的地方不是界面有多花哨而是数据表设计是否经得起推敲、增删改查逻辑是否闭环、权限边界是否清晰。我带过几届学生的课程设计见过太多“能跑但一碰就碎”的版本宿舍分配冲突、退宿后床位状态不更新、管理员和宿管员权限混在一起。这篇笔记就围绕这个标题把一套可复现的 JSPMySQL 宿舍管理系统从建库、搭环境、写核心模块到避坑完整拆一遍。适合正在做实训作业的在校生也适合想用 JSP 快速搭一个内部管理系统的初级开发者。读完你能自己动手把最小版本跑起来并且知道哪些地方最容易翻车。2. 环境与数据库先落地别急着写 JSP先把这四张表定死2.1 为什么选 JSPMySQL 而不是 Spring Boot实训作业场景下JSPMySQL 的优势很直接不需要额外学框架、Tomcat 一配就能跑、代码逻辑全写在页面里调试时一眼能看到请求从哪来到哪去。对于只要求“功能完整、能演示、有文档”的课程设计这套组合的投入产出比最高。常见做法是用 JDBC 直连不引入连接池也能跑但我会建议至少加一个简单的 DBUtil 工具类否则每个 Servlet 里都写一遍驱动加载和关闭连接后期改数据库密码会改到崩溃。选型上要注意JDK 用 8 或 11 都行Tomcat 用 8.5 或 9MySQL 用 5.7 或 8.0。别用太新的版本JSP 里javax.servlet和jakarta.servlet的包名差异会让新手卡半天。我一般会固定 JDK 8 Tomcat 8.5 MySQL 5.7 这套组合资料最多报错最好搜。2.2 四张核心表学生、宿舍、床位、管理员宿舍管理系统的数据模型不复杂但字段类型和约束必须一次定对。下面是我常用的建表语句直接可执行-- 学生表一个学生对应一个床位床位号允许为空表示未分配 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, sname VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(2) DEFAULT 男, phone VARCHAR(20), bed_id INT DEFAULT NULL COMMENT 关联床位未分配为NULL, status TINYINT DEFAULT 1 COMMENT 1在校 0退宿 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 宿舍表记录楼号、房间号、总床位 CREATE TABLE dorm ( id INT PRIMARY KEY AUTO_INCREMENT, building VARCHAR(20) NOT NULL COMMENT 楼号, room_no VARCHAR(10) NOT NULL, total_beds INT DEFAULT 4, UNIQUE KEY uk_room (building, room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 床位表每张床一条记录被占用时 student_id 不为空 CREATE TABLE bed ( id INT PRIMARY KEY AUTO_INCREMENT, dorm_id INT NOT NULL, bed_no INT NOT NULL COMMENT 床位序号1-4, student_id INT DEFAULT NULL, UNIQUE KEY uk_bed (dorm_id, bed_no), FOREIGN KEY (dorm_id) REFERENCES dorm(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 管理员表role 区分超级管理员和宿管员 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT DEFAULT 2 COMMENT 1超管 2宿管 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明学生和床位是一对一关系用student.bed_id和bed.student_id双向记录查询时不用反复 JOIN。宿舍和床位是一对多床位表里存dorm_id。管理员表用role字段做权限分级超管能管宿舍楼宿管只能管自己楼的学生。参数说明utf8mb4是为了支持中文姓名和特殊符号UNIQUE KEY防止同一房间出现两个相同床位号外键约束保证不会出现孤儿床位。注意 MySQL 5.7 默认引擎是 InnoDB外键才生效如果建表时没写ENGINEInnoDB后面删宿舍会留下垃圾数据。2.3 初始化数据与连接配置建完表先插几条测试数据否则登录都进不去INSERT INTO admin (username, password, role) VALUES (admin, 123456, 1); INSERT INTO dorm (building, room_no, total_beds) VALUES (A栋, 101, 4), (A栋, 102, 4); INSERT INTO bed (dorm_id, bed_no) VALUES (1,1),(1,2),(1,3),(1,4),(2,1),(2,2),(2,3),(2,4);密码明文存储只适合实训演示真实项目必须加盐哈希。连接配置写在db.properties里drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/dorm_db?useUnicodetruecharacterEncodingutf8useSSLfalse usernameroot password你的密码然后在 Java 里用Properties加载不要硬编码。这样换环境只改一个文件。3. 核心功能怎么串登录、分配、退宿、查询四步走3.1 登录与权限拦截一个 Filter 挡住所有未登录请求JSP 项目最容易出的安全问题是用户直接敲 URL 就能进后台页面。解决办法是写一个LoginFilter拦截/admin/*路径public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(); // 放行登录页和登录接口 String uri request.getRequestURI(); if (uri.contains(login.jsp) || uri.contains(LoginServlet)) { chain.doFilter(req, resp); return; } if (session.getAttribute(admin) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }逻辑说明先判断当前请求是不是登录相关是就放行否则检查 session 里有没有管理员对象没有就跳回登录页。参数说明request.getContextPath()保证项目部署在非根路径时也能正确跳转。注意 Filter 要在web.xml里配置映射到/admin/*或者用注解WebFilter(/admin/*)。3.2 宿舍分配事务里同时更新两张表分配床位是核心业务必须保证student.bed_id和bed.student_id同时更新否则会出现“学生显示已分配但床位还空着”的玄学问题。用 JDBC 事务Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 检查床位是否已被占用 String checkSql SELECT student_id FROM bed WHERE id? FOR UPDATE; PreparedStatement ps1 conn.prepareStatement(checkSql); ps1.setInt(1, bedId); ResultSet rs ps1.executeQuery(); if (rs.next() rs.getObject(student_id) ! null) { throw new RuntimeException(该床位已被占用); } // 2. 更新床位表 String bedSql UPDATE bed SET student_id? WHERE id?; PreparedStatement ps2 conn.prepareStatement(bedSql); ps2.setInt(1, studentId); ps2.setInt(2, bedId); ps2.executeUpdate(); // 3. 更新学生表 String stuSql UPDATE student SET bed_id? WHERE id?; PreparedStatement ps3 conn.prepareStatement(stuSql); ps3.setInt(1, bedId); ps3.setInt(2, studentId); ps3.executeUpdate(); conn.commit(); } catch (Exception e) { if (conn ! null) conn.rollback(); e.printStackTrace(); } finally { DBUtil.close(conn); }逻辑说明FOR UPDATE锁住床位行防止两个管理员同时分配同一张床。先检查再更新最后提交。参数说明setAutoCommit(false)开启事务rollback()保证任何一步失败都回滚。注意 MySQL 默认隔离级别是 REPEATABLE READFOR UPDATE在 InnoDB 下才有效MyISAM 不支持事务建表时别选错引擎。3.3 退宿与床位释放别忘了把状态改回去退宿操作要同时做三件事学生状态改为 0、学生表bed_id置空、床位表student_id置空。顺序不能乱否则外键或唯一约束会报错。常见做法是写一个CheckoutServlet接收studentId先查出bed_id再依次更新String sql1 UPDATE student SET status0, bed_idNULL WHERE id?; String sql2 UPDATE bed SET student_idNULL WHERE id?;如果学生表有外键指向床位直接置 NULL 可能被拒绝所以建表时bed_id不要加外键约束用程序逻辑保证一致性。这是血泪经验加了外键后退宿时删床位记录会级联失败调试半天找不到原因。3.4 多条件查询用动态 SQL 拼条件宿舍查询通常要按楼号、房间号、是否满员筛选。JSP 里用 JDBC 拼 SQL 要注意防注入全部用PreparedStatementStringBuilder sql new StringBuilder(SELECT d.*, COUNT(b.student_id) AS used FROM dorm d LEFT JOIN bed b ON d.idb.dorm_id WHERE 11); ListObject params new ArrayList(); if (building ! null !building.isEmpty()) { sql.append( AND d.building?); params.add(building); } if (roomNo ! null !roomNo.isEmpty()) { sql.append( AND d.room_no LIKE ?); params.add(% roomNo %); } sql.append( GROUP BY d.id);逻辑说明WHERE 11是占位技巧后面每个条件都加AND不会语法出错。LEFT JOIN保证空宿舍也能查出来。参数说明LIKE配合%做模糊查询但数据量大时前置%会导致索引失效实训数据量小无所谓真实项目要换成全文索引或搜索引擎。4. 避坑与排查这五个问题几乎每个人都会遇到4.1 中文乱码从数据库到页面要统一三处编码现象学生姓名显示成问号或乱码。原因数据库、连接串、JSP 页面三处编码不一致。解决建库时CHARACTER SET utf8mb4连接串加useUnicodetruecharacterEncodingutf8JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 %并且request.setCharacterEncoding(UTF-8)要放在读取参数之前。三处缺一处都会翻车。4.2 空指针异常session 过期后直接取属性现象登录后放一会儿再点操作页面报 500。原因session 超时后session.getAttribute(admin)返回 null后面直接调用方法就空指针。解决在 Filter 里已经拦截了未登录但 Filter 只拦/admin/*如果某个 Servlet 路径没配进去就会漏。检查web.xml的 Filter 映射或者在每个 Servlet 开头再判一次 null。4.3 床位重复分配并发下检查失效现象两个宿管同时给不同学生分配同一张床结果都成功了。原因先查后改之间没有锁。解决用SELECT ... FOR UPDATE锁行或者给bed.student_id加唯一索引允许 NULL 但非 NULL 唯一数据库层面直接拒绝重复。后者更简单但要注意 MySQL 唯一索引允许多个 NULL。4.4 删除宿舍报外键错误现象删宿舍时提示Cannot delete or update a parent row。原因床位表有外键指向宿舍。解决先删该宿舍下所有床位再删宿舍或者建表时外键加ON DELETE CASCADE。但级联删除有风险实训项目建议手动按顺序删逻辑更清晰。4.5 Tomcat 启动报 404项目路径和 web.xml 版本不匹配现象部署后访问http://localhost:8080/项目名/显示 404。原因web.xml的web-app版本声明和 Tomcat 版本不兼容或者url-pattern写错。解决Tomcat 8.5 用web-app 3.1Tomcat 9 用4.0。检查web.xml里 Servlet 映射的url-pattern是否和表单action一致大小写敏感。5. 进阶技巧把实训作业做成能写进简历的项目5.1 用连接池替换直连性能提升立竿见影JDBC 直连每次请求都新建连接并发一高就崩。换成 Druid 或 HikariCP配置一个DataSourceDBUtil从池里拿连接。以 Druid 为例urljdbc:mysql://localhost:3306/dorm_db?useUnicodetruecharacterEncodingutf8 usernameroot password你的密码 initialSize5 maxActive20public class DBUtil { private static DataSource ds; static { try { Properties p new Properties(); p.load(DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)); ds DruidDataSourceFactory.createDataSource(p); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }逻辑说明静态代码块只加载一次配置后续所有请求复用连接池。参数说明initialSize是初始连接数maxActive是最大连接数实训项目设 20 足够。注意关闭连接时用conn.close()实际是归还池不是真关闭。5.2 加一层简单的分页查询不再一次性拉全表宿舍和学生列表数据一多页面会卡。用 MySQL 的LIMIT做分页SELECT * FROM student WHERE status1 ORDER BY id DESC LIMIT ?, ?第一个参数是偏移量(pageNum-1)*pageSize第二个是每页条数。JSP 里传pageNumServlet 里计算偏移。注意LIMIT在数据量大时深分页会慢可以用WHERE id 上一页最大id优化但实训项目用LIMIT就够了。5.3 导出 Excel用 POI 把查询结果写出去宿管经常要导出学生名单用 Apache POI 几行代码就能实现XSSFWorkbook wb new XSSFWorkbook(); XSSFSheet sheet wb.createSheet(学生名单); XSSFRow header sheet.createRow(0); header.createCell(0).setCellValue(学号); header.createCell(1).setCellValue(姓名); // 遍历结果集填充数据 int rowIdx 1; for (Student s : list) { XSSFRow row sheet.createRow(rowIdx); row.createCell(0).setCellValue(s.getSno()); row.createCell(1).setCellValue(s.getSname()); } response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filenamestudents.xlsx); wb.write(response.getOutputStream()); wb.close();逻辑说明创建工作簿、表头、数据行最后通过响应流输出。参数说明Content-Disposition的attachment让浏览器下载而不是直接打开。注意 POI 的XSSFWorkbook占内存较大数据量超过几万行要换SXSSFWorkbook。5.4 验证方法用 Postman 或浏览器直接测接口写完一个 Servlet不要只靠点页面验证。用 Postman 发 POST 请求到/LoginServlet带上username和password看返回的 session 是否生效。或者浏览器 F12 看 Network 里请求参数和响应状态码。我一般会先测登录再测分配最后测退宿每一步都确认数据库里两张表的数据同步变了。这个习惯能帮你提前发现 80% 的逻辑漏洞。最后说个我自己的教训做这类系统先把数据库表结构和字段约束定死再动手写页面。我早期图快边写 JSP 边改表结果改一个字段名要翻十几个文件后悔药都没得吃。现在我的习惯是建表语句先跑通插测试数据用 SQL 把增删改查全验证一遍再开始写 Java。希望帮到你。本文还有配套的精品资源点击获取