
简介这套基于Java的学生宿舍管理系统源码包面向计算机专业毕业设计、课程设计及Java Web入门进阶学习者帮助读者快速搭建一个具备多角色权限的宿舍管理演示项目。系统前端采用Bootstrap构建管理界面后端基于SpringBoot框架与MySQL数据库划分管理员、宿管、学生三类角色管理员可维护学生、楼宇、宿舍、入住、宿管及管理员信息并支持各类数据的增删改查与Excel导入导出宿管侧重学生、宿舍与入住管理学生端提供学生及宿舍信息查看。包内共661个文件以400个js、46个css、35个java、若干class与xml配置为主另有sql脚本、html页面和说明文档压缩包约72.37MB并提供MySQL 5与MySQL 8两套可运行项目。目前已有469人学习下载。读者可据此理解SpringBoot分层结构、权限登录流程、Bootstrap后台页面组织及数据导入导出实现也可作为毕业设计原型进行二次开发与功能扩展。1. 宿舍管理系统一个能直接跑起来的 Java 毕设项目长什么样每年毕业季后台总有人问同一个问题有没有一个结构别太花哨、能在一周内跑起来、答辩时讲得清楚的 Java 项目。宿舍管理系统就是这类需求里命中率最高的选题之一而这份基于 Java 的学生宿舍管理系统源码恰好是那种「拉下来配好数据库就能登录」的工程。它解决的场景很具体学生入住、调宿、退宿管理员分配房间、统计床位、登记报修辅导员查在寝情况。技术栈走的是 Java 后端加关系型数据库的经典路线没有堆微服务也没上容器编排对新手友好对熟手也留了改造空间。这篇笔记就按「它是什么、目录长什么样、核心代码怎么转、怎么跑起来、哪里会翻车、二次开发往哪改」的顺序拆一遍尽量把血泪经验提前说省得你在环境上卡两天。2. 技术选型与目录结构从一次登录请求看整体骨架2.1 为什么是这套经典组合而不是时髦方案这类管理系统型项目的技术选型逻辑很简单数据关系明确学生、楼栋、房间、床位、报修单并发压力低读多写少核心诉求是增删改查跑通加权限隔离。所以常见的做法是 Servlet/JSP 或者 Spring Boot 二选一前者偏教学、后者偏工程。这份源码走的是 Java Web 主线持久层用 JDBC 或轻量 ORM前端是 JSP 加少量 JS数据库 MySQL构建工具 Maven部署到 Tomcat。选这套的理由得说清楚不然改起来会迷路分层清晰Controller/Service/DAO 三层各管一段改业务只动 Service改 SQL 只动 DAO适合二次开发时快速定位。依赖少没有注册中心、没有消息队列一个 war 包丢进 Tomcat 就能起本地调试链路短。可讲性答辩场景下面试官或老师问「你的请求怎么走的」一条链路能从浏览器讲到数据库再讲回来比讲微服务熔断好答得多。如果你接手的是 Spring Boot 版本结构会变成controller / service / mapper / entity差异主要在依赖注入和配置方式业务逻辑的组织方式是一致的。下面按通用 Java Web 结构讲遇到 Boot 版本自行对应。2.2 目录结构每个包负责什么一个合格的宿舍管理系统目录大致长这样先建立地图感后面看代码才不会觉得哪段都是孤立的dormitory-management/ ├── src/main/java/com/example/dorm/ │ ├── controller/ # 接收请求参数校验返回视图或 JSON │ │ ├── LoginController.java │ │ ├── StudentController.java │ │ ├── RoomController.java │ │ └── RepairController.java │ ├── service/ # 业务规则床位是否已满、能否调宿 │ │ ├── StudentService.java │ │ ├── RoomService.java │ │ └── RepairService.java │ ├── dao/ # SQL 与数据库交互一个表一个 DAO │ │ ├── StudentDao.java │ │ ├── RoomDao.java │ │ └── BaseDao.java # 连接获取、关闭、通用查询封装 │ ├── entity/ # 与表结构一一对应的 POJO │ │ ├── Student.java │ │ ├── Room.java │ │ └── Repair.java │ ├── filter/ # 登录态校验、编码过滤 │ │ └── AuthFilter.java │ └── util/ # 数据库连接、分页、日期工具 │ └── DBUtil.java ├── src/main/webapp/ │ ├── WEB-INF/views/ # JSP 页面按模块分子目录 │ ├── static/ # css、js、图片 │ └── WEB-INF/web.xml # Servlet 映射、过滤器配置 ├── src/main/resources/ │ └── db.properties # 数据库连接参数 ├── sql/ │ └── dorm.sql # 建库建表加初始数据 └── pom.xml # 依赖与打包配置这张图里有三个地方值得注意。BaseDao是所有 DAO 的父类连接管理和通用查询写在这里避免每个 DAO 重复写Connection那套样板代码。filter包承担登录拦截没登录访问任何管理页都会被弹回登录页这是权限隔离的第一道门。sql/dorm.sql是能不能跑起来的关键很多下载的源码跑不起来八成是这份脚本没执行或者执行了但字符集不对。提示拿到源码第一件事不是打开 IDE是先看sql目录和db.properties这两处决定了你后面 80% 的时间是花在业务上还是环境上。2.3 表结构设计五个核心表撑起全部业务关系型系统的表设计直接决定代码复杂度。这套系统的核心表不多但关系要绕清楚表名关键字段说明studentid, sno, name, gender, class_name, room_id, status学生基本信息room_id 关联房间status 标记在住/退宿roomid, building_id, room_no, capacity, occupied房间capacity 总床位occupied 已住人数buildingid, name, floors, manager楼栋宿管归属repairid, student_id, room_id, content, status, create_time报修单status 走待处理/处理中/已完成adminid, username, password, role管理员账号role 区分超级管理员和普通宿管设计上最容易出问题的是room.occupied这个冗余字段。它和student表里room_id的计数是重复的好处是查空床位不用每次count(*)坏处是调宿、退宿时必须同步更新漏一处就会出现「显示满员但实际有空床」的玄学。看到这个字段就要在脑子里给它挂个警示牌改业务时优先检查同步逻辑。3. 核心模块拆解一个入住请求从进入到写库的完整链路3.1 登录与权限拦截filter 里那道门先看入口。所有管理端页面都要经过AuthFilter它的职责单一判断 session 里有没有登录用户没有就重定向到登录页。这是最朴素也最有效的权限控制比在每个 Controller 里判断一遍优雅得多。public class AuthFilter implements Filter { // 白名单登录页、静态资源不需要登录态 private static final SetString WHITE_LIST Set.of(/login, /static, /css, /js); Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 白名单直接放行否则登录页自己也会被拦 for (String prefix : WHITE_LIST) { if (uri.startsWith(request.getContextPath() prefix)) { chain.doFilter(req, resp); return; } } Object user request.getSession().getAttribute(loginUser); if (user null) { // 未登录统一回登录页避免用户看到 404 或裸数据 response.sendRedirect(request.getContextPath() /login); return; } chain.doFilter(req, resp); } }这段代码的关键在WHITE_LIST和contextPath的处理。contextPath是应用部署路径如果项目不是部署在根路径下比如http://host/dorm/直接拿uri和/login比会永远不匹配导致登录页也被拦截形成死循环——这是新手最常踩的一个坑。另外loginUser这个 key 必须和登录成功时session.setAttribute的 key 完全一致大小写都别错。3.2 学生入住业务校验写在 Service 而不是 Controller入住是这个系统里最典型的写操作涉及三张表的联动更新学生room_id和status、房间occupied加一、可能还要写一条操作日志。这类逻辑必须收在 Service 层Controller 只负责取参数和返回结果。public class StudentService { private final StudentDao studentDao new StudentDao(); private final RoomDao roomDao new RoomDao(); /** * 学生入住 * param studentId 学生主键 * param roomId 目标房间主键 * return 操作结果提示语 */ public String checkIn(Long studentId, Long roomId) { Student student studentDao.findById(studentId); if (student null) { return 学生不存在; } // 已入住的不允许重复分配防止 occupied 被加两次 if (IN.equals(student.getStatus())) { return 该学生已在住请先办理退宿; } Room room roomDao.findById(roomId); if (room null) { return 房间不存在; } // 床位已满直接拒绝这里用 occupied 冗余字段快速判断 if (room.getOccupied() room.getCapacity()) { return 该房间床位已满; } // 两表更新放在同一事务里任一失败整体回滚 boolean ok roomDao.increaseOccupied(roomId) studentDao.bindRoom(studentId, roomId); if (!ok) { throw new RuntimeException(入住失败事务回滚); } return 入住成功; } }逐段说明findById先做存在性检查避免空指针status判断是防重复入住的闸门没有它occupied会被重复累加这是最隐蔽的数据错误床位判断用occupied capacity而不是等号防止历史脏数据导致occupied已经超过容量时仍然放行最后的短路结合事务保证两表一致。注意increaseOccupied内部如果是update room set occupied occupied 1就天然带并发保护如果写成「先查再 set」并发下会丢更新。看到这种写法要第一时间改掉。3.3 条件分页查询学生列表怎么不拖垮页面列表页是使用频率最高的功能学生一多就必须分页还要支持按楼栋、班级、状态组合筛选。实现上分两步先查总数再查当前页数据SQL 用limit控制偏移。public class StudentDao extends BaseDao { // 动态拼条件的思路用 StringBuilder 收集 where 片段用参数列表收集占位符值 public ListStudent queryPage(String buildingName, String className, String status, int page, int pageSize) { StringBuilder sql new StringBuilder( select s.*, r.room_no from student s left join room r on s.room_id r.id where 11); ListObject params new ArrayList(); if (buildingName ! null !buildingName.isEmpty()) { sql.append( and r.building_id (select id from building where name ?)); params.add(buildingName); } if (className ! null !className.isEmpty()) { sql.append( and s.class_name ?); params.add(className); } if (status ! null !status.isEmpty()) { sql.append( and s.status ?); params.add(status); } // limit 的两个参数是偏移量和条数不能直接拼 page 和 pageSize sql.append( order by s.id desc limit ?, ?); params.add((page - 1) * pageSize); params.add(pageSize); return queryList(sql.toString(), Student.class, params.toArray()); } }参数说明page从 1 开始计pageSize建议 1020(page - 1) * pageSize就是偏移量。where 11是拼接动态条件的惯用写法避免判断第一个条件要不要加and。left join room是为了在列表里直接显示房间号省一次查询。queryList用反射把结果集映射成实体模型类字段名必须和列别名一致select s.*出来的列名要和Student属性对得上对不上就返回 null表现为「列表有行但字段全空」——这种黑匣子问题调起来很烦记住先打印结果集元数据。3.4 报修单状态流转一个字段把业务串起来报修模块的代码量不大但状态流转是业务完整性的体现学生提交待处理→ 宿管接单处理中→ 维修完成已完成。状态更新要做合法性校验不能随意跳转。public class RepairService { // 合法流转表key 是当前状态value 是允许的下一状态 private static final MapString, SetString FLOW Map.of( PENDING, Set.of(PROCESSING), PROCESSING, Set.of(DONE), DONE, Set.of() ); public String updateStatus(Long repairId, String targetStatus) { Repair repair repairDao.findById(repairId); if (repair null) { return 报修单不存在; } SetString allowed FLOW.getOrDefault(repair.getStatus(), Set.of()); if (!allowed.contains(targetStatus)) { return 非法的状态变更 repair.getStatus() - targetStatus; } repairDao.updateStatus(repairId, targetStatus); return 状态已更新; } }这段代码的用意是把状态机显式化。很多同类项目直接update repair set status ?前端传什么就写什么结果出现「已完成」被改回「待处理」的脏数据。用一张FLOW表把规则钉死改需求时只动这一处业务代码不用散落判断。4. 跑起来环境准备、导库与启动排错4.1 环境清单与版本匹配跑这类项目版本对不上是最耗时的坑。下面这套组合是踩过几轮后比较稳的组件建议版本说明JDK8 或 11源码若用了var或List.of需要 11Maven3.6负责拉依赖注意配国内镜像MySQL5.7 或 8.08.0 注意驱动包和时区参数Tomcat8.5 或 9不要用 10包名从javax换成jakarta会直接报错IDEIntelliJ IDEA社区版即可配合 Smart Tomcat 插件调试MySQL 8.0 连接串必须带时区和 SSL 参数否则启动时报时区错误# db.properties jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/dorm?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.passwordyour_password参数逐个解释useUnicode和characterEncoding决定中文不乱码serverTimezone解决 8.0 驱动的时区异常useSSLfalse关掉本地不必要的加密握手allowPublicKeyRetrievaltrue避免 8.0 默认认证方式下的连接拒绝。四个少一个都可能起不来这套组合建议原样抄。4.2 导库与数据初始化拿到sql/dorm.sql后别在图形化工具里点开复制粘贴执行容易因为编码问题丢中文。走命令行更稳# 先建库并指定字符集再导入表结构和数据 mysql -uroot -p -e CREATE DATABASE dorm DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p dorm sql/dorm.sql # 导入后核对表和数据量确认没导空 mysql -uroot -p -e use dorm; show tables; select count(*) from student;第一句里的utf8mb4不要改成utf8后者在部分版本下只支持三字节名字里带生僻字或特殊符号会插入失败。第二句用重定向导入比交互式执行快且不出编码岔子。第三句是验证show tables应该看到前面说的五张核心表count(*)应该有初始数据如果两张表都空说明脚本里只有结构没有insert需要看脚本末尾有没有初始账号。4.3 启动与第一次登录IDEA 里导入 Maven 项目等依赖下载完配置 Tomcat 运行。这里有个高频操作顺序先确认pom.xml里的打包方式是 war再配置 Artifact 为war exploded用 exploded 模式改 JSP 不用重启调试效率高一截。启动成功后访问http://localhost:8080/dorm/login用sql脚本里的初始管理员账号登录。如果登录页能出但提示密码错误先去数据库里看admin表的password字段是明文还是密文明文就直接对照密文则要确认登录逻辑用的是 MD5 还是 BCrypt不匹配永远登不进去。这一步别猜直接看LoginController里的比对代码。5. 避坑排查五条真实会卡住你的问题5.1 中文乱码从页面到数据库的整条链路都要管现象学生姓名在列表里显示成问号或方块提交表单后存进库是乱码。原因乱码不是单点问题请求编码、响应编码、数据库字符集、连接串四处任何一处不对都会出。常见的是 POST 请求体默认编码不是 UTF-8且 JSP 页面没声明pageEncoding。解决按链路逐段设。JSP 头部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %加一个编码过滤器或在web.xml里配CharacterEncodingFilter设encodingUTF-8连接串带上characterEncodingutf8建库用utf8mb4。四处对齐之后乱码基本绝迹。5.2 数据库连接池耗尽连接关了但没真关现象用一阵子后报Too many connections或请求一直卡在获取连接。原因BaseDao里getConnection拿到的连接在finally里只close了ResultSet和Statement漏了Connection或者异常分支直接return跳过了关闭逻辑。解决用 try-with-resources 改写让编译器保证关闭或者引入 Druid/HikariCP 连接池统一管理。手工关连接这种写法在演示时没问题一旦多人同时用就暴露建议一开始就换池化方案省得后面查连接泄漏查到怀疑人生。5.3 调宿后床位数对不上冗余字段没同步现象房间显示已满但实际点进去只有两个人或者反过来明明住满了还能继续分配。原因room.occupied是冗余计数入住时加了、退宿时忘了减或者调宿时旧房间没减、新房间加了。业务分支一多就漏。解决把所有影响occupied的操作收口到RoomDao一个方法里比如syncOccupied(roomId)内部按student表实时count回写业务层只调这一个方法。牺牲一点性能换一致性在这个数据量级完全划算。5.4 分页参数被篡改page 传负数直接 SQL 报错现象手动改 URL 把page0或page-1后端抛异常或查出全表。原因(page - 1) * pageSize算出负偏移limit -10, 10在 MySQL 里直接语法错误pageSize不限制的话传个 100000 会把整表拉进内存。解决在 Controller 入口做参数兜底page小于 1 归 1pageSize夹在 1100 之间。这类边界校验不写不影响正常使用但一旦被触发就是 500属于典型的「上线才炸」问题。5.5 静态资源 404过滤器把 css 也拦了现象登录页样式全丢控制台一堆静态资源 404但接口本身正常。原因AuthFilter的url-pattern配成了/*把/static/**、/css/**也纳入拦截范围未登录时被重定向到登录页浏览器拿到的响应体是 HTML 而非 CSS。解决白名单里补齐静态资源前缀前面AuthFilter代码里已经这么做了或者把url-pattern收窄到只拦业务路径。改完记得清浏览器缓存不然你会以为是代码没生效白白多调半小时。6. 二次开发从能跑到好用的几条改造路径项目跑通只是起点真正体现功力的是怎么在它上面加东西。按投入产出比排我一般会先动这三处。第一处密码存储换成加盐哈希。初始脚本里的管理员密码多半是明文或裸 MD5MD5 已经可以被彩虹表反查。改造点集中在两处注册/改密时用BCrypt.hashpw(raw, BCrypt.gensalt())存库登录时用BCrypt.checkpw(raw, stored)比对。注意存量数据要写个一次性迁移脚本或者登录时判断密文格式做平滑升级。代码量不超过二十行但答辩时是加分项。第二处把报修状态流转扩展成带时间的工单。现在只有状态字段加两个时间戳字段accept_time、finish_time再在 Service 的状态变更处顺手写入当前时间就能算出平均处理时长。前端加一列「处理耗时」统计页做个按楼栋分组的平均时长排行业务价值立刻不一样。改造只需要动repair表、RepairService和两个 JSP边界清楚。第三处加一层操作日志。入住、退宿、调宿这类改变数据的操作建议记一条operation_log谁、什么时候、对哪个学生做了什么。实现上不用上 AOP 那么重在 Service 的关键方法末尾插一条 insert 即可。好处是出了问题能回溯比如床位对不上时能查到是哪次操作漏了同步。改造方向涉及文件难度收益密码加盐哈希LoginController、AdminDao、迁移脚本低安全性明显提升工单时间统计repair 表、RepairService、统计页中业务完整度提升操作日志新增表、Service 各处中可追溯便于排错批量导入学生StudentController、POI 依赖中高真实场景刚需改造后的验证方法别偷懒每改一处至少走一遍「新增→查询→修改→删除」的完整闭环并顺手看一眼数据库里的冗余字段对不对。我见过太多人改完接口就以为完事结果occupied早就飘了。说个自己的教训。早些年接手这类系统我习惯先跑起来再读代码图快。后来在一个调宿功能上翻了车页面显示成功数据库里旧房间的床位数没减学生的room_id却已经改了等于凭空多出一张床位。那次排查花了整整一下午最后发现是 Service 里少了一行同步调用。从那以后我每次动涉及冗余字段的逻辑都强制先把「哪些表会被改、改完谁负责核对」写在纸上再动手。希望这些能帮到你少走点弯路。本文还有配套的精品资源点击获取