资讯动态

Spring Boot+MyBatis实战:宿舍管理系统从0到1完整开发指南

发布时间:2026/10/4 6:39:20 来源:尧图企业网站定制
简介一份基于Java学生宿舍管理系统设计与实现的完整论文文档面向计算机相关专业学生、毕业设计者以及Java Web开发初学者。文档以高校宿舍管理效率低为切入点基于B/S模式使用JSPMySQL数据库围绕用户登录、学生注册、宿舍分配、管理员管理等核心模块展开完整呈现了学生宿舍管理系统的需求分析、业务流程设计、功能模块规划、数据库概念与逻辑结构设计以及管理员、宿舍管理员、学生三类角色的系统实现和功能测试过程。资源为单个docx文档压缩包大小1.44MB内容结构清晰包含目录、前言、需求分析、数据库设计、系统实现、系统测试等章节系统介绍了J2EE、JSP、MySQL、B/S架构、Tomcat等关键技术。已有204人学习文档可作为毕业设计撰写参考、课程设计选题借鉴也可为宿舍管理系统开发提供完整思路与数据库设计参考。1. 学生宿舍管理系统到底要解决什么给谁做、做完能交付什么学生宿舍管理系统几乎是 Java 后端课程设计和毕业设计里出现频率最高的题目之一。拿到这个题目的人大多不缺 CRUD 代码缺的是把“宿舍分配、调寝、退宿、报修、卫生检查”这条业务主线讲清楚的能力。这个系统的本质是宿舍资源管理几栋楼、每栋楼多少房间、每个房间几张床、谁住在哪、设备坏了走什么流程。做它的价值在于用一套真实业务把 Spring Boot、MyBatis、MySQL 和前端页面串成一个完整闭环答辩时有业务深度可讲写简历时也有拿得出手的项目。它适合刚学完 Java 基础、想找中期练手项目的学生也适合需要快速交付一套课程设计的开发者。题面只写了 Java但今天再用 JSPServlet 那套去交既难维护也不好看下面按一套能顺利落地、能讲清业务的方案拆给你看。2. 选型先立住为什么是 Spring Boot MyBatis 加极简前端2.1 三条技术路线怎么选从 JSP 古董到前后端分离宿舍管理系统网上的资料很多但不少是十年前用 JSP Servlet JDBC 写的。JSP 那种方式不是不能跑而是页面和 Java 代码堆在一起改个页面样式要顺着 out.print 找半天答辩时老师一问“这个系统怎么部署”你也讲不清。如果你只想快速交一版作业JSP 一条路能走通如果你想在简历上写“熟悉 Spring Boot MyBatis”就用下面这套常见的项目组合Spring Boot 做后端MyBatis 写 SQL前端用 Vue 或者原生 HTML axios。就算你完全没学过 Vue用 Bootstrap 写几个页面、axios 调后端接口也能把系统跑完整。前端不是这个题目的重点后端接口和数据库设计才是。技术路线维护成本答辩友好度适合谁JSP Servlet JDBC页面与逻辑耦合改样式容易翻车中容易被追问部署细节只求快速交作业Spring SpringMVC HibernateXML 配置多环境搭建耗时低靠配置说服力弱老项目维护Spring Boot MyBatis Vue/HTML约定优于配置一键启动高能讲清接口和表结构课设、毕设、简历项目为什么选 MyBatis 而不是 JPA因为宿舍管理系统里有好几处需要手写 SQL 的点查空床位、统计入住率、按楼栋聚合。MyBatis 对这类查询很顺手SQL 看得见摸得着答辩被追问也能直接指着 XML 讲不用翻实体类猜查询逻辑。Spring Boot 这边解决的是环境问题内嵌 Tomcat 容器打成一个 jar 包就能跑部署思路一句话说清对课程设计来说足够。2.2 环境准备JDK、Maven、MySQL 的最小版本组合与配置常见组合是 JDK 8 或 JDK 17Maven 3.6MySQL 5.7 或 8.0IDEA 社区版就够用。JDK 8 配 Spring Boot 2.7 非常稳如果你机器上装的是 JDK 17就选 Spring Boot 3.x。MySQL 8.0 要用 com.mysql.cj.jdbc.Driver别再用 5.x 时代的 com.mysql.jdbc.DriverClassNotFound 已经替无数人踩过了。JDK 配好环境变量是起步的第一关。java 环境变量配置里最容易翻车的地方不在配置本身而是配完 JAVA_HOME 和 PATH 后没有重开终端java -version 还是旧值你会误以为自己没配上。JAVA_HOME 指到 JDK 安装目录不要带 bin 后缀PATH 里追加 %JAVA_HOME%\binMaven 同理配 MAVEN_HOME。配完重开一个 cmd 窗口验证# 验证 JDK 与 Maven 是否生效配置完环境变量后必须重开终端 java -version mvn -version # 如果 mvn 报 mvn 不是内部或外部命令说明 PATH 没配对或者配完没重开终端java -version 输出里要能看到版本号和位数mvn -version 会同时打印 Maven home 和 Java version 两行Maven 用的 Java 也来自 JAVA_HOME如果这里显示的 Java 版本和你要用的不一样说明 JAVA_HOME 被其他配置覆盖了。IDEA 里也要确认一次 Maven 路径Settings 下 Build Tools 里的 Maven 配置把 Maven home path 指向你解压的目录User settings file 指向 conf/settings.xml。这一步没做对会出现“mvn 命令行能跑、IDEA 里一运行就一堆依赖找不到”的怪现象。2.3 生成最小工程pom.xml 与 application.yml 怎么配项目骨架不用手敲用 Spring Initializr 生成最快。选 Maven 工程、Java 版本、Spring Boot 版本勾上 Spring Web生成后把 MyBatis 和 MySQL 依赖补进 pom.xml。课程设计不追求最新版本稳定优先我用 2.7.18 这个 2.7.x 收尾版本比较多你本地能拉到的 2.7.x 都可以。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web 起步依赖内嵌 Tomcat提供 Controller 能力 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis 整合 Spring Boot 的 starter版本要跟 Boot 主版本配套 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency !-- MySQL 驱动8.0 对应 com.mysql.cj.jdbc.Driverscope 用 runtime -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok 省掉 getter/setter 样板代码实体类会清爽很多 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies依赖配好后看 application.yml这是整个项目第一个要调明白的配置文件。端口、数据源、MyBatis 驼峰映射三件事在这里一次性说清server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/dorm_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.dorm.entity configuration: map-underscore-to-camel-case: true logging: level: com.example.dorm.mapper: debugurl 里的 useUnicode 和 characterEncoding 解决中文乱码serverTimezone 是 MySQL 8.0 驱动必填的时区参数不写会启动报错。map-underscore-to-camel-case 打开后数据库的 room_id 能自动映射到实体的 roomId后面少写一堆 resultMap。logging.level 把 MyBatis 的 SQL 打到控制台是排查“查询结果不对”最快的手段建议常开 debug答辩时关掉就行。3. 把数据库先落地五张核心表设计与初始化 SQL3.1 宿舍楼、宿舍、学生三张基础表怎么关联建表前先把关系想清楚一栋宿舍楼有多个房间一个房间住多个学生这是典型的三层结构。dorm_building 表放楼栋信息dorm_room 表放房间信息并通过 building_id 挂到楼栋下student 表通过 dorm_room_id 挂到房间下。这样设计的好处是调寝时只需要改 student 表里的 dorm_room_id 一个外键字段不用同时维护楼栋关系。表名关键字段说明dorm_buildingid, building_name, address, manager楼栋基本信息dorm_roomid, building_id, room_no, floor, bed_count, gender_type, status房间容量与性别分区在房间维度studentid, student_no, name, gender, class_name, phone, dorm_room_id, statusstatus 区分在住与离校gender_type1 男 2 女放在房间表而不是楼栋表是因为现实中常出现“一栋楼 1-3 层住女生、4-6 层住男生”的混排情况按房间存性别才有灵活性。student 表的 status 和 dorm_room 表的 status 是两个不同概念前者表示在住/离校后者表示房间启用/停用别混用。字段类型上gender 这类枚举值用 tinyint 就够别为了好看全上 varcharJava 的数据类型映射会更啰嗦。3.2 报修与卫生检查两张业务表为什么要独立出来报修和卫生检查是这个系统里最容易做成“宿舍表加两个字段”的功能但我不建议这么做。报修是一条流程学生提交、宿管处理、记录完成时间中间有状态变化。把这些塞进 dorm_room 表后面要做“本月哪个宿舍报修最多”的统计时一句 SQL 都写不出来只能在 Java 里循环遍历房间再数数页面卡成黑匣子。repair_order 表的字段这样设id、room_id、student_id、content、status、create_time、handle_time。status 用 0 待处理、1 处理中、2 已完成三态每改一次状态就更新时间。health_check 表存的是每次查寝的历史记录id、room_id、check_date、score、comment、inspector。每次查寝插一行而不是在宿舍表上 update 一个 score 字段这样能画出一条宿舍卫生趋势线也方便老师按日期范围查记录。3.3 初始化 SQL 与 MyBatis 映射从建表到查空房初始化 SQL 一次把五张表建好外键也一并加上。注意字符集统一用 utf8mb4utf8 在 MySQL 里存 emoji 和生僻字会报错宿舍管理系统虽然用不到 emoji但备注字段里出现生僻字的情况并不少见CREATE DATABASE IF NOT EXISTS dorm_system DEFAULT CHARACTER SET utf8mb4; USE dorm_system; CREATE TABLE dorm_building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_name VARCHAR(50) NOT NULL, address VARCHAR(100), manager VARCHAR(20) ); CREATE TABLE dorm_room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL, room_no VARCHAR(10) NOT NULL, floor INT DEFAULT 1, bed_count INT NOT NULL DEFAULT 4, gender_type TINYINT NOT NULL COMMENT 1男 2女, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, CONSTRAINT fk_room_building FOREIGN KEY (building_id) REFERENCES dorm_building(id) ); CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender TINYINT NOT NULL, class_name VARCHAR(50), phone VARCHAR(20), dorm_room_id BIGINT, status TINYINT DEFAULT 1 COMMENT 1在住 0离校, CONSTRAINT fk_student_room FOREIGN KEY (dorm_room_id) REFERENCES dorm_room(id) ); CREATE TABLE repair_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL, student_id BIGINT NOT NULL, content VARCHAR(500) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待处理 1处理中 2已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, handle_time DATETIME, CONSTRAINT fk_repair_room FOREIGN KEY (room_id) REFERENCES dorm_room(id) ); CREATE TABLE health_check ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL, check_date DATE NOT NULL, score DECIMAL(5,1) DEFAULT 0, comment VARCHAR(200), inspector VARCHAR(20), CONSTRAINT fk_health_room FOREIGN KEY (room_id) REFERENCES dorm_room(id) );建好表后写第一个 MyBatis 映射。查空房是宿舍分配的核心查询SQL 里的子查询算当前在住人数而不是在 dorm_room 表里冗余一个 available_bed 字段——冗余字段要在分配、退宿、调寝三个地方同步维护少改一处数据就错了select idfindAvailableRooms resultTypecom.example.dorm.entity.DormRoom SELECT r.*, b.building_name FROM dorm_room r JOIN dorm_building b ON r.building_id b.id WHERE r.gender_type #{genderType} AND r.status 1 AND r.bed_count ( SELECT COUNT(*) FROM student s WHERE s.dorm_room_id r.id AND s.status 1 ) ORDER BY r.floor ASC, r.room_no ASC /select这段 SQL 里 #{genderType} 是预编译参数能防 SQL 注入千万别用 ${} 拼参数。子查询里加了 s.status 1离校学生不占床位如果你漏掉这个条件就会出现“寝室明明空了两个人却查不到空房”的诡异现象。ORDER BY 按楼层和房间号排序分配结果稳定不会出现每次查出来顺序不一样的情况。r.* 查出来的 room_id 在开了驼峰映射后会自动变成 roomId而 b.building_name 是额外带出来的展示字段实体类里加一个 buildingName 属性接收即可。4. 核心业务接口登录、分配与调寝的三个关键实现4.1 登录态怎么设计JWT 还是 Session宿舍管理系统有学生端和管理员端登录逃不掉。前后端分离时JWT 是主流方案无状态、跨域友好、答辩时能讲清楚“服务端不存会话”的原理。如果你打算用 JSP 渲染页面Session 更省事但既然定了前后端分离就用 JWT。JWT 也不是没有缺点它一旦签发就没法在过期前吊销所以过期时间不要设太长这是我在实际项目里吃过亏的参数。我给管理员端设 2 小时给学生端设 4 小时。学生白天可能一上午都挂在系统里2 小时会突然掉线超过 4 小时token 泄漏后的风险窗口又太大。实现上用一个 JwtUtil 类处理生成和解析// 生成 token把 userId 和 role 塞进 claimsHS256 签名 public String createToken(Long userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } // 解析 token过期或签名不对会抛异常调用方捕获后返回 401 public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); }SECRET 不要硬编码在类里放到 application.yml 用 Value 注入这是 java 后端项目的基本习惯。拦截器里校验 token 时注意要对请求路径做放行处理登录接口、静态资源、前端页面这类不需要鉴权的路径在注册拦截器时直接 excludePathPatterns 排掉否则会出现“登录接口都进不去”的循环问题。4.2 宿舍分配的 3 个必调参数容量、性别分区、空床位优先宿舍分配是新生入学时最核心的操作。粗暴做法是“找到第一个没满的房间就塞进去”这会导致第一间宿舍永远爆满、后面的宿舍空着而且男女混住的问题也会漏出来。我一般会过三个参数再写逻辑床位容量、性别分区、空床位优先。容量由 dorm_room.bed_count 决定性别分区就是 room.gender_type 与学生 gender 匹配空床优先是核心排序策略让已住人数少的房间优先被选中人员分布均匀public DormRoom assignRoom(Student student) { // 1. 按学生性别匹配房间只查启用中的房间 DormRoom room dormRoomMapper.findAvailableRooms( student.getGender() 1 ? 1 : 2 ); if (room null) { throw new BusinessException(当前没有可分配的空床位); } // 2. 占用床位更新学生宿舍外键 student.setDormRoomId(room.getId()); studentMapper.updateDorm(student); return room; }findAvailableRooms 就是第 3 章里那段带子查询的 SQL它已经把容量和空床位过滤在 SQL 层做掉了Java 里不需要再判断床位余量。排序让房间住得均匀而不是永远填满第一间这样查寝、卫生评分的压力也会分散。并发分配是课程设计里容易忽略的点多个学生同时提交分配时“先查后写”会超卖。改进方案有两种一是给查询加 SELECT ... FOR UPDATE 行锁二是用 UPDATE dorm_room SET ... WHERE id ? AND bed_count (SELECT COUNT(*)...) 做条件更新更新行数为 0 说明被抢走。能做到这里答辩已经是加分项。4.3 调寝与退宿的事务边界调寝包含“释放旧床位 占用新床位 写调寝日志”三个动作任何一个失败前面写过的数据都不该留下。这就是 Transactional 最适合出现的场景。实现时注意两个细节一是事务内先锁新房间行防止两个人同时调进同一个宿舍二是 rollbackFor 设为 Exception.class因为 Spring 默认只在遇到 RuntimeException 时才回滚捕获了异常不往外抛事务不会回滚Transactional(rollbackFor Exception.class) public void changeRoom(Long studentId, Long newRoomId, Long oldRoomId, Long operatorId) { // 1. 锁住新房间行避免并发调寝看到同一个空位 DormRoom newRoom dormRoomMapper.selectByIdForUpdate(newRoomId); if (newRoom null) { throw new BusinessException(新宿舍不存在); } // 2. 校验新房间还有空床位 int occupied studentMapper.countByRoomId(newRoomId); if (occupied newRoom.getBedCount()) { throw new BusinessException(新宿舍无空床位); } // 3. 更新学生外键释放旧宿舍写入调寝日志 studentMapper.updateRoom(studentId, newRoomId); changeLogMapper.insert(studentId, oldRoomId, newRoomId, operatorId, new Date()); }selectByIdForUpdate 会锁住 dorm_room 表的对应行直到事务提交才释放。先校验再写入这套流程在并发场景下才站得住。退宿的逻辑更简单更新学生 status 为离校、清空 dorm_room_id同样放在事务里执行。事务方法的拆分也值得注意如果 changeRoom 和退宿写在同一个 Service 类里并且退宿方法里直接调 this.changeRoom()事务会失效原因是 Spring 的声明式事务靠代理对象实现this 调用绕过代理。这个问题我在第 5 章展开讲。5. 常见踩坑与排查启动失败、级联删除、事务失效怎么处理5.1 宿舍明明有空床位却分配失败性别分区与在住状态不一致现象页面上显示某间宿舍住了 2 人、床位数 4但调用分配接口时提示“当前没有可分配的空床位”日志里 SQL 查出来 0 条。原因最常见的是查空床位的子查询里漏了 s.status 1离校学生的 dorm_room_id 还挂在老房间上把床位占着或者房间 gender_type 与前端传入的性别参数方向相反男生被拿去查 gender_type 2 的房间。解决先把 MyBatis 日志打开看 findAvailableRooms 实际执行的 SQL 是什么参数传的是什么。SQL 里子查询必须带 AND s.status 1性别开关前后端约定统一别一个用 0/1、一个用 1/2。用数据说话别靠猜。5.2 调寝写入一半报错事务没生效现象调寝日志插入失败后发现学生的 dorm_room_id 已经被更新了数据处于“新宿舍没住上、旧宿舍已释放”的中间态。原因两个典型。一是同一个类里 this.changeRoom() 调用Transactional 代理没生效二是建表时用了 MyISAM 引擎它根本不支持事务Transactional 注解一点用没有。解决事务方法拆到独立的 Service 组件里或者注入自身代理再调用检查 MySQL 建表语句确认是 ENGINEInnoDB。排查顺序先用 show table status 看引擎再断点看类里调用方式。这个坑顺带也是 java 八股文里常考的 Spring 事务失效场景答辨时能自己说出来反而不丢分。5.3 前端联调时登录态丢了跨域把 Authorization 头挡了现象登录接口能正常调通但带 token 的接口全部 401浏览器控制台里能看到 CORS preflight 报错或者请求自动变成 OPTIONS。原因前端页面跑在 5173 端口后端在 8080跨域触发了浏览器预检请求 OPTIONS而 JWT 拦截器把 OPTIONS 请求也拦下来校验 token校验失败直接返回 401预检都过不去真实请求根本发不出去。解决在拦截器 preHandle 里直接放行 OPTIONS 请求Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行跨域预检请求否则带 Authorization 的请求会被 CORS 卡死 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } return true; }这个点也是面试题里过滤器与拦截器执行顺序的常客CORS 过滤器要放在拦截器之前或者拦截器里显式放行 OPTIONS。前后端联调时先在浏览器 Network 里看请求方法是 OPTIONS 还是 GET/POST方法不对就优先查拦截器。5.4 MyBatis 多表查询一直返回 null驼峰映射没开现象接口返回的 JSON 里 roomId、buildingName 全是 null但数据库里字段明明有值前端渲染出来一片空白。原因MyBatis 默认不做下划线到驼峰的自动转换room_id 这个列映射不到 roomId 属性。只有开了 map-underscore-to-camel-case: true 才会自动转没开的话要么写 resultMap 手动映射要么在 SQL 里写别名 AS roomId。解决最好的做法是在 application.yml 统一打开驼峰映射别依赖 SQL 别名因为多表查询里重名列会让你写别名写到崩溃。打开后重新启动再看返回结果。如果某个字段还是 null检查实体类是不是用了 Lombok DataIDE 有没有装 Lombok 插件这个翻车现场也很常见。5.5 端口被占用导致启动失败java 启动失败怎么解决现象启动 Spring Boot 项目控制台报错 Port 8080 was already in use或者干脆显示 APPLICATION FAILED TO START。原因上一次运行的进程没杀掉IDEA 里点停止只停了当前调试会话后台 jar 进程还占着端口也可能是本机其他服务占了 8080。解决开发环境直接改 application.yml 里的 server.port比如 8081要排查占用进程Windows 下用 netstat -ano | findstr 8080 找到 PID再 taskkill /PID 对应PID /F。这是 java 项目启动失败里最常见的一类问题先看端口再看日志最后怀疑依赖版本这个排查顺序能省一半时间。6. 收尾把入住率报表做成一个可验证的接口6.1 用一条 SQL 算出全楼入住率宿舍管理系统交付时通常要能回答“每栋楼住了多少人、入住率多少”这种管理问题。不要在 Java 里循环查宿舍再聚合一栋楼一个 SELECT页面加载要等好几秒。一条聚合 SQL 就能把楼栋维度的入住率算出来SELECT b.building_name, SUM(r.bed_count) AS total_beds, COUNT(s.id) AS occupied_beds, CONCAT(ROUND(COUNT(s.id) * 100.0 / SUM(r.bed_count), 1), %) AS rate FROM dorm_building b JOIN dorm_room r ON r.building_id b.id LEFT JOIN student s ON s.dorm_room_id r.id AND s.status 1 GROUP BY b.id, b.building_name;LEFT JOIN 保证没有学生的空房间也会出现在结果里COUNT(s.id) 只数在住学生离校的不占床位。6.2 用 Scheduled 做定时刷新与验证统计结果如果每次实时查数据量大了还是会慢。常见做法是每天凌晨用定时任务刷一次结果到缓存表页面读的是缓存快照。Spring 自带的 Scheduled 就能实现不需要额外引入框架Scheduled(cron 0 30 2 * * ?) public void refreshOccupancyReport() { ListOccupancyVO list dormReportMapper.summaryByBuilding(); reportCache.put(occupancy, list); log.info(入住率统计已刷新共 {} 栋楼, list.size()); }cron 表示每天凌晨 2:30 执行低峰期不干扰业务。开发时为了验证 SQL 对不对可以临时把这个 cron 改成每 10 秒跑一次看到缓存数据变化后立刻改回正式值。我第一次做宿舍管理系统时把统计逻辑全写在 Java 循环里页面卡三秒才出数据后来换成聚合 SQL 加定时刷新响应降到几十毫秒这套“先验证再收尾”的习惯也一直留到了现在希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑