Java学生宿舍管理系统实战:Spring Boot+MyBatis从需求到避坑
简介这份资源是面向计算机专业毕业设计场景的Java学生宿舍管理系统论文文档适合正在准备毕设选题、需要参考完整系统设计与实现思路的本科生及自学者。论文围绕宿舍信息管理混乱、出错率高、安全性差等痛点给出基于Java与MySQL的解决方案涵盖需求分析、系统设计、编码实现与测试全流程并划分管理员、宿管员、学生三类角色涉及公寓资产、缴费信息、公共场所清理、日常事务及床位申请审核等模块。资源包共1个docx文件约1.57MB即论文正文文档包含摘要、目录、开发环境与技术选型、数据库表结构设计及各功能模块实现说明可直接作为毕设写作模板与功能设计参考。目前已有553人学习下载适合需要快速搭建论文框架、梳理系统模块与数据库设计思路的读者借鉴使用。1. 从一份论文文档说起学生宿舍管理系统到底要解决什么每年毕业季计算机相关专业的群里都会冒出同一个问题基于 Java 的学生宿舍管理系统怎么做才能既跑得起来、又写得进论文。这个标题背后其实藏着两拨人——一拨是要交毕业设计的学生需要一套能演示、能答辩、代码量适中的完整系统另一拨是刚接手校园信息化改造的初级开发想找一个业务边界清晰、技术栈主流的练手项目。宿舍管理恰好卡在中间它比图书借阅多了一层床位—楼栋—人员的空间约束又比选课系统少了很多并发抢课的压力非常适合用 Spring Boot 加 MyBatis 这套组合拳落地。我见过太多人一上来就堆功能结果做到一半发现数据表关系理不清查寝记录和床位调整互相打架。真正靠谱的做法是先想清楚三件事谁在用宿管、辅导员、学生、维修工、每个角色看到什么、数据在哪个环节产生又流向哪里。把这三件事画成一张表后面写代码就是填空。这篇笔记就按这个思路从需求拆解一路讲到能跑通的最小实现再把我踩过的坑摊开给你看。2. 需求拆解与数据建模把床位—楼栋—人员关系理清楚2.1 四类角色与核心用例宿舍管理系统的角色划分直接决定权限表怎么设计。我一般会先列一张角色—用例对照表避免后期加菜单时反复改数据库。角色核心用例数据可见范围学生查看本人床位、提交报修、查看查寝结果仅本人记录宿管楼栋床位分配、查寝录入、报修派单所辖楼栋辅导员查看所带班级住宿情况、审批调宿所带班级系统管理员用户管理、楼栋维护、数据导出全部这张表的价值在于它逼你提前想清楚数据可见范围这一列。很多新手把权限做成简单的角色判断结果宿管能看到别的楼栋数据答辩时被老师一问就露馅。常见做法是在用户表里加一个scope_id字段宿管存楼栋 ID辅导员存班级 ID查询时统一拼这个条件。2.2 核心表结构与字段说明数据建模是这类系统最容易翻车的地方。床位和学生的关系不是简单的一对一因为存在调宿、退宿、临时床位等状态。我一般用床位表 住宿记录表两张表来解耦床位表只描述物理位置住宿记录表描述谁在什么时间段住了哪个床位。-- 楼栋表物理建筑 CREATE TABLE building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 楼栋名称如3号楼, floor_count INT NOT NULL COMMENT 楼层数, gender TINYINT NOT NULL COMMENT 1男 2女 ); -- 宿舍表楼栋下的房间 CREATE TABLE room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL, room_no VARCHAR(20) NOT NULL COMMENT 房间号如301, capacity INT NOT NULL DEFAULT 4 COMMENT 额定床位, INDEX idx_building (building_id) ); -- 床位表最小物理单元 CREATE TABLE bed ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL, bed_no VARCHAR(10) NOT NULL COMMENT 床位号如A/B/C/D, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1占用 2维修, INDEX idx_room (room_id) ); -- 住宿记录表谁在什么时间住了哪个床位 CREATE TABLE accommodation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, bed_id BIGINT NOT NULL, check_in DATE NOT NULL COMMENT 入住日期, check_out DATE DEFAULT NULL COMMENT 退宿日期NULL表示在住, status TINYINT NOT NULL DEFAULT 1 COMMENT 1在住 2已退宿 3待审批, INDEX idx_student (student_id), INDEX idx_bed (bed_id) );这段建表语句的关键在于accommodation表的check_out字段允许为 NULL。这样当前在住就是check_out IS NULL AND status 1历史记录也完整保留。如果只用一张学生表存床位 ID调宿时要么覆盖历史、要么加一堆冗余字段后期做住宿统计会非常痛苦。参数上要注意capacity和实际床位数可能不一致比如四人间坏了一张床只剩三个可用。所以床位状态用独立的status字段管理而不是靠capacity减去已住人数来推算。这个细节在查寝和维修场景里会反复用到。2.3 状态流转调宿与退宿怎么不打架调宿的本质是旧记录退宿 新记录入住这两步必须在同一个事务里完成否则会出现学生同时占两个床位或者一个床位都没有的脏数据。Transactional(rollbackFor Exception.class) public void changeBed(Long studentId, Long newBedId) { // 1. 查出当前在住记录 Accommodation current accommodationMapper.findActiveByStudent(studentId); if (current null) { throw new BizException(该学生当前无在住记录); } // 2. 旧床位释放 current.setCheckOut(LocalDate.now()); current.setStatus(2); accommodationMapper.updateById(current); bedMapper.updateStatus(current.getBedId(), 0); // 3. 新床位占用 Bed newBed bedMapper.selectById(newBedId); if (newBed.getStatus() ! 0) { throw new BizException(目标床位不可用); } Accommodation fresh new Accommodation(); fresh.setStudentId(studentId); fresh.setBedId(newBedId); fresh.setCheckIn(LocalDate.now()); fresh.setStatus(1); accommodationMapper.insert(fresh); bedMapper.updateStatus(newBedId, 1); }逻辑说明先校验再操作任何一步失败整体回滚。Transactional的rollbackFor一定要写Exception.class因为 Spring 默认只对运行时异常回滚业务里抛的自定义异常如果是受检异常就会导致部分提交。参数上checkOut用当天日期而不是删除记录是为了保留完整的住宿轨迹后面做某学生四年住过哪些床位的统计直接查这张表就行。3. 技术选型与最小可运行骨架Spring Boot MyBatis 怎么搭3.1 为什么是 Spring Boot 而不是纯 Servlet热搜里java基础java入门这些词说明很多人还在纠结技术栈。我的建议很直接毕业设计用 Spring Boot别用纯 Servlet 或者 JSP。原因不是 Spring Boot 更高级而是它帮你省掉了大量和业务无关的配置——内嵌 Tomcat 不用单独装服务器starter 依赖不用手动凑 jar 包版本配置文件集中在一个application.yml里。答辩时老师问你这个项目怎么部署你回答打成 jar 包 java -jar 就能跑比解释一堆 web.xml 配置要体面得多。至于前端如果时间紧就用 Thymeleaf 做服务端渲染如果想让界面好看点就上 Vue3 加 Element Plus。热搜里vue3后台管理系统热度很高但要注意前后端分离意味着你要多处理跨域、Token 传递、接口联调这些事工作量至少翻倍。我的经验是毕业设计阶段用 Thymeleaf 足够把精力放在业务逻辑上。3.2 项目骨架与依赖配置一个能跑的最小骨架包含四层controller、service、mapper、entity。目录结构按功能模块分不要按技术分层否则后期找文件很痛苦。src/main/java/com/example/dorm ├── DormApplication.java # 启动类 ├── common # 通用返回、异常、工具 │ ├── Result.java │ └── BizException.java ├── module │ ├── building # 楼栋模块 │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ └── entity │ ├── room │ ├── bed │ └── accommodation └── config # 拦截器、跨域等配置pom.xml里核心依赖就三个spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java。版本上 Spring Boot 用 2.7.x 比较稳3.x 要求 JDK17 且部分第三方库还没跟上毕业设计没必要冒这个险。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependencyapplication.yml里几个必调参数数据库连接池用 HikariCPSpring Boot 默认自带maximum-pool-size设 10 就够校园规模用MyBatis 的map-underscore-to-camel-case设为 true这样数据库的building_id能自动映射到 Java 的buildingId省掉大量 resultMap 配置。spring: datasource: url: jdbc:mysql://localhost:3306/dorm?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password hikari: maximum-pool-size: 10 minimum-idle: 2 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true3.3 一个完整的查询接口从 Controller 到 Mapper以查询某楼栋所有床位及占用情况为例走一遍完整链路。这个接口在宿管分配床位时是核心入口。// Controller RestController RequestMapping(/api/bed) public class BedController { Autowired private BedService bedService; GetMapping(/list/{buildingId}) public ResultListBedVO listByBuilding(PathVariable Long buildingId) { return Result.ok(bedService.listByBuilding(buildingId)); } }// Service Service public class BedServiceImpl implements BedService { Autowired private BedMapper bedMapper; Override public ListBedVO listByBuilding(Long buildingId) { // 直接查视图把床位、房间、当前住户一次性带出来 return bedMapper.selectVoByBuilding(buildingId); } }!-- Mapper XML -- select idselectVoByBuilding resultTypecom.example.dorm.module.bed.entity.BedVO SELECT b.id, b.bed_no, r.room_no, b.status, s.name AS studentName, s.student_no FROM bed b JOIN room r ON b.room_id r.id LEFT JOIN accommodation a ON a.bed_id b.id AND a.check_out IS NULL AND a.status 1 LEFT JOIN student s ON a.student_id s.id WHERE r.building_id #{buildingId} ORDER BY r.room_no, b.bed_no /select逻辑说明这里用LEFT JOIN而不是INNER JOIN因为空闲床位没有对应的住宿记录用内连接会直接丢掉这些行宿管就看不到空床位了。a.check_out IS NULL AND a.status 1这个条件必须写在 JOIN 的 ON 子句里而不是 WHERE 里否则同样会把空闲床位过滤掉——这是新手最容易犯的错我当年在这上面卡了整整一个下午。参数说明buildingId从路径变量传入MyBatis 用#{}预编译防止 SQL 注入。返回的BedVO是一个视图对象比直接返回 Bed 实体多了studentName和roomNo字段前端拿到就能直接渲染表格不用再发第二次请求。4. 避坑与排查那些答辩前夜才暴露的问题4.1 中文乱码从数据库一路传到浏览器现象查寝备注里写的中文在页面上显示成问号但数据库里看着又是好的。原因通常是三层编码不一致——数据库连接 URL 没加characterEncodingutf8、表字段用了latin1、或者 Tomcat 的URIEncoding没配。解决顺序是先从数据库查起SHOW CREATE TABLE accommodation看字符集再检查连接串最后看server.servlet.encoding.charset配置。我一般统一用 utf8mb4因为学生姓名里偶尔会有生僻字。4.2 调宿后旧床位状态没更新现象学生从 301 调到 302302 显示已占用但 301 还是占用状态新来的学生分不进去。原因就是前面说的调宿的两步操作没放在同一个事务里或者更新床位状态的代码漏写了。排查方法是直接查bed表里status1但accommodation表里没有对应在住记录的床位写一条 SQL 就能揪出来。解决就是严格用Transactional包住并且把床位状态更新和住宿记录更新绑在一起。4.3 分页查询总数对不上现象床位列表第一页显示 10 条翻到第二页也是 10 条但总数显示 15。原因是分页插件在统计总数时SQL 里的LEFT JOIN导致行数膨胀或者手写的 count 语句和查询语句条件不一致。用 PageHelper 的话它会自动生成 count 语句但如果你的查询里有GROUP BY或者复杂的 JOIN自动生成的 count 可能不准。稳妥做法是手写 count 语句条件部分和查询语句严格保持一致。4.4 报修图片上传后访问 404现象学生提交报修时上传了照片数据库里存了路径但页面上图片裂开。原因通常是上传目录不在 Spring Boot 的静态资源映射范围内。默认情况下src/main/resources/static下的文件才能直接访问你存到D:/upload/这种外部目录就需要额外配置映射。解决是在配置类里加一行registry.addResourceHandler(/upload/**).addResourceLocations(file:D:/upload/);注意file:前缀和结尾的斜杠都不能少。4.5 定时任务把查寝数据算重了现象每天凌晨统计未归人数跑完发现数字比实际多了一倍。原因是定时任务在集群或者多次启动的情况下重复执行或者统计逻辑里没有排除已退宿的记录。排查先看日志里任务执行了几次再看 SQL 的过滤条件。解决是给统计 SQL 加上check_out IS NULL条件并且如果部署了多个实例用数据库的乐观锁或者 Redis 锁保证同一时间只有一个任务在跑。5. 让论文和代码都站得住验证方法与一个提效技巧5.1 用接口测试代替手工点页面答辩前最怕的是演示到一半某个功能报错。我的习惯是在写完后端接口后用 Postman 或者 curl 把每个接口跑一遍把请求和响应存成集合。这样即使前端页面出问题你也能证明后端逻辑是通的。更进一步用 JUnit 写几个关键路径的测试用例比如调宿后旧床位释放退宿后床位变空闲跑一遍全绿再上演示环境。SpringBootTest class AccommodationServiceTest { Autowired private AccommodationService service; Test void changeBed_shouldReleaseOldBed() { // 假设学生1当前在床位1 service.changeBed(1L, 2L); Bed oldBed bedMapper.selectById(1L); assertEquals(0, oldBed.getStatus(), 旧床位应变为空闲); Bed newBed bedMapper.selectById(2L); assertEquals(1, newBed.getStatus(), 新床位应变为占用); } }这个测试的价值在于它把调宿这个业务规则固化下来了。以后不管谁改代码跑一遍测试就知道有没有破坏原有逻辑。参数上assertEquals的第三个参数是失败提示写清楚能省很多排查时间。5.2 论文里的系统测试章节怎么写才不空很多人论文的测试章节就是截图加功能正常四个字老师一看就知道是凑字数。我的做法是设计一张测试用例表包含用例编号、前置条件、操作步骤、预期结果、实际结果、是否通过。挑三个有代表性的场景正常流程分配床位、异常流程给已占用床位再分配、边界流程最后一张空床位分配后状态变化。这样写出来的测试章节有数据、有逻辑答辩时老师问你怎么保证系统可靠你也有话可说。用例编号前置条件操作步骤预期结果TC-01床位A空闲分配学生1到床位A床位A状态变占用生成住宿记录TC-02床位A已占用分配学生2到床位A提示床位不可用数据不变TC-03楼栋最后一张空床分配后查询楼栋空床数空床数为0楼栋状态可标记满员5.3 一个让开发速度翻倍的技巧最后说一个我压箱底的习惯先把所有实体的增删改查用 MyBatis Generator 或者自己写模板生成一遍哪怕生成的代码很粗糙。因为宿舍管理系统里 70% 的代码都是标准的 CRUD真正需要动脑的只有调宿、查寝统计、报修派单这几个核心逻辑。把重复劳动交给工具你才有时间打磨那几个能写进论文难点与创新章节的功能。我见过太多人手工敲了几十个 getter/setter结果核心业务逻辑反而没时间仔细想最后答辩被问得哑口无言。这个方向值不值得做如果你是要交毕业设计它足够完整又不至于失控如果你是想练手 Spring Boot 全栈开发它的业务复杂度刚好卡在有挑战但不劝退的区间。关键是别贪多把床位状态流转这一条线做扎实比堆十个半成品功能强得多。希望帮到你。本文还有配套的精品资源点击获取