☰
学生宿舍管理系统Java毕设:SSM+MySQL+Layui全流程实现与避坑指南
2026/10/17 4:46:49 网站建设 项目流程

简介:一款基于Java的学生宿舍管理系统完整项目源码,面向Java初学者、毕业设计选题者和需要快速搭建后台管理系统的开发者。项目采用SpringBoot框架搭配MySQL数据库,前端基于Bootstrap完成界面,划分管理员、宿管、学生三类角色,覆盖学生、楼宇、宿舍、入住、宿管、管理员六大管理模块,支持增删改查与Excel批量导入导出,并内置验证码登录、角色权限控制等常见功能,可直接运行或二次改造。资源包内共含661个文件,以JavaScript、CSS、Java、HTML等前后端源码为主,辅以SQL脚本、XML配置、设计文档,并额外提供适配MySQL5与MySQL8的两份压缩工程,整体大小约72.37MB。当前已有467人学习下载。压缩包内附带修改好的MySQL5/MySQL8项目压缩包,免去环境适配的麻烦;同时包含实体类、控制器、登录验证及导入导出逻辑代码,有助于理解权限划分和功能实现,适用于课程设计、毕业设计或项目实战练手。

1. 学生宿舍管理系统:一个能覆盖“入住-查寝-报修-退宿”全流程的Java毕设项目

每年这个时候都会有一批同学在找能跑的 Java 管理系统源码,而「学生宿舍管理系统」恰恰是点名率很高的一个方向。它看着不大,但角色多、状态多、页面多,一个宿舍从新生入住到毕业退宿,中间还夹着卫生检查、报修、调宿这些日常动作,非常适合拿来当毕业设计,也适合练手理解 Java Web 三层架构。

这套资源的核心,是一个基于 SSM 架构(Spring + SpringMVC + MyBatis)的完整 Web 项目,前端用 JSP + Layui,数据库走 MySQL。系统里有学生、宿舍管理员、系统管理员三种角色,每种角色登录后看到的菜单和操作权限完全不一样。对需要交毕设、又不想从零开始写的同学来说,它最大的价值不是“看起来功能多”,而是表结构、权限控制、业务状态流转都已经替你趟过一遍,下载下来改改就能用。

2. 总体架构与技术选型:SSM + MySQL + Layui 的完整分层方案

2.1 技术栈选型与理由

这个项目的技术栈放在今天看不算新,但胜在稳。Spring 管对象和事务,SpringMVC 管请求分发,MyBatis 管数据库操作,前端没有上 Vue 那套复杂的构建链,直接用 JSP 服务端渲染加 Layui 的组件库,数据展示和弹窗表单都够用。后端分层是标准的:Controller 只做参数接收和视图转发,Service 做业务判断,Mapper 只碰数据库。

这种选型对毕设场景有一个很实际的好处:每一层的代码都好查、好改、好答辩。老师问“你这个事务是怎么控制的”,你直接指出 Spring 的事务注解加在 Service 上;问“接口和页面怎么对应的”,顺着 Controller 的路由就能讲清楚。如果你换成 Spring Boot 单体,代码会简洁不少,但答辩时很多分层细节反而没那么直观。

版本上,常见做法是 JDK 1.8 + Tomcat 8.5 + MySQL 5.7 + Maven 3.6 左右,这套组合在同一个学校机房、宿舍笔记本上都能稳定跑起来。数据库驱动和 SSM 依赖都用 Maven 管理,不需要手动往 WEB-INF/lib 里塞 jar 包。

2.2 工程目录结构

项目是一个标准的 Maven Web 工程,顶层结构大概是这样的:

student-dormitory/ ├── pom.xml ├── src/main/java │ └── com/dormitory │ ├── controller # 控制器:拦截请求、转发视图 │ ├── service # 业务层接口和实现 │ ├── mapper # MyBatis Mapper 接口 │ ├── pojo # 实体类,对应数据库表 │ ├── common # 拦截器、工具类、常量 │ └── config # Spring 和 SpringMVC 配置 ├── src/main/resources │ ├── mapper # MyBatis 的 XML 映射文件 │ ├── jdbc.properties # 数据库连接配置 │ ├── mybatis-config.xml │ ├── spring-mvc.xml │ └── applicationContext.xml └── src/main/webapp ├── WEB-INF │ ├── web.xml │ └── jsp # 各角色的 JSP 页面 ├── static # css、js、layui 组件库 └── index.jsp

这个结构最大的好处是“照目录找代码”非常快。你想改登录页逻辑,去 controller 里找 LoginController;想改宿舍分配规则,去 service 里找 DormitoryServiceImpl;想调整列表查询 SQL,去 resources/mapper 里找对应的 XML。不会出现翻半天不知道改哪个文件的尴尬。

2.3 数据库表设计

数据库是整个项目最值得先看的部分。宿舍管理系统的核心表有 7 张,表之间通过外键逻辑关联:

表名职责关键字段
t_user登录账号表id, username, password, role
t_student学生信息表stu_id, name, gender, class_name, dorm_id, bed_no, phone
t_building宿舍楼表id, name, floors, room_count, manager
t_dormitory宿舍表id, building_id, room_no, capacity, current_count, status
t_repair报修工单表id, dorm_id, applicant, reason, status, create_time
t_health卫生检查记录表id, dorm_id, score, check_date, inspector
t_notice公告表id, title, content, create_time, publisher

角色关系是这样设计的:t_user 只负责登录认证,存的是账号、密码和角色;t_student 才是业务主体,存学生的学号、班级、入住宿舍、床位号。角色字段用 role 区分,1 是系统管理员,2 是宿舍管理员,3 是学生。登录成功后,拦截器根据 session 里存的 role 决定能访问哪些路径。

t_student 和 t_dormitory 之间是典型的多对一,一个宿舍可以住多个学生,宿舍表里用 current_count 记录已住人数,capacity 记录上限。status 字段标记宿舍状态,0 表示有空位、1 表示已满、2 表示维修中。这套设计在入住和调宿的业务逻辑里会被反复用到。

2.4 请求从进入到返回的完整链路

看懂了表结构,再看一个请求是怎么走完一圈的。以“宿舍管理员提交一条卫生评分”为例:

浏览器提交表单 → SpringMVC 的前端控制器 DispatcherServlet 拦截到/health/add路径 → 处理器映射找到 HealthController 的 add 方法 → Controller 把表单参数封装成 Health 对象 → 调用 HealthService.add() → Service 里做基础校验(宿舍是否存在、分数范围)然后调用 Mapper → MyBatis 执行 insert SQL → 返回主键 → Controller 拿到结果后重定向或者返回 JSON → 页面刷新列表。

这中间有几个隐藏细节。Spring 的事务默认只对 Service 方法生效,如果在 Controller 里写多表操作,事务可能会失效;MyBatis 的 mapper 接口和 XML 通过 namespace 和接口全限定名绑定;JSP 页面里的表单 action 写的是带项目名的完整路径,比如/dormitory/health/add,改项目部署名的时候页面里的路径也要同步改,这点很容易踩坑。

3. 核心模块实现:登录鉴权、宿舍分配和报修状态流转

3.1 登录认证与角色权限控制

登录是整个系统的入口,也是最需要看清楚的部分。AdminController 中的登录方法大概是这样的:

@Controller @RequestMapping("/login") public class LoginController { @Autowired private UserService userService; @RequestMapping("/doLogin") public String doLogin(String username, String password, String role, HttpSession session) { User user = userService.login(username, password, role); if (user == null) { // 用户名、密码、角色任一不匹配都算失败 return "redirect:/login.jsp?error=1"; } session.setAttribute("loginUser", user); session.setAttribute("role", user.getRole()); return "redirect:/index"; } }

这里有个判断容易忽略:登录时是不是要校验角色?常见做法是把 role 也作为查询条件,而不是登录成功后再单独判断。这样三种角色的账号就算密码撞库了,也只能进自己角色的后台,避免学生账号直接访问管理员页面的问题。

光靠登录成功还不够,项目里有一个 AuthInterceptor 拦截器做全局校验:

public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); String requestUri = request.getRequestURI(); // 放行登录页、静态资源 if (requestUri.contains("/login") || requestUri.contains("/static/")) { return true; } if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return false; } // 角色路径校验:管理员只能访问 /admin/,宿管只能访问 /manager/,学生只能访问 /student/ String role = user.getRole(); if ("1".equals(role) && requestUri.contains("/manager/")) { response.sendRedirect(request.getContextPath() + "/noAuth.jsp"); return false; } if ("2".equals(role) && requestUri.contains("/admin/")) { response.sendRedirect(request.getContextPath() + "/noAuth.jsp"); return false; } return true; } }

拦截器的配置在 spring-mvc.xml 里注册,拦截所有/**路径,白名单只有 login.jsp 和 static 目录。这里最关键的判断是“先查登录状态,再查角色路径”:如果只查 session 里有没有 user,没登录的用户直接敲/admin/student/list的 URL 也能绕过登录;加上角色判断后,即使登录了学生账号,也进不去宿管页面。

3.2 宿舍分配与床位抢占

宿舍分配是这个项目里业务含金量最高的一段代码。很多人会写成“先查空闲宿舍,再 update 入住人数”的两步操作,但这样并发时会出差错:两个管理员同时给新生安排同一个床位,两人都查到有空位,结果都执行 update,出现超住。合格的做法是把“判断”和“更新”合成一条语句:

public boolean assignDormitory(String stuId, Integer dormId, String bedNo) { // 写法一:先检查再更新(不推荐,存在并发风险) Dormitory dorm = dormitoryMapper.selectById(dormId); if (dorm.getCurrentCount() >= dorm.getCapacity()) { return false; // 已满 } // 写法二:把状态判断写进 update 的 where 条件,用“受影响行数”来判断 int rows = dormitoryMapper.updateOccupied(dormId); // 执行的是: // UPDATE t_dormitory SET current_count = current_count + 1 // WHERE id = #{dormId} AND current_count < capacity if (rows == 0) { return false; // 说明宿舍已满,抢占失败 } // 床位同理:UPDATE t_student SET dorm_id=?, bed_no=? WHERE stu_id=? AND dorm_id IS NULL studentMapper.bindDorm(stuId, dormId, bedNo); return true; }

这个写法的巧妙之处在于把并发风险交给数据库的行锁机制去解决。update 语句在执行时会对命中行加锁,两个请求同时进来时,第二个请求会等第一个提交后再执行 update,此时 current_count 已经加 1,如果达到了 capacity,影响行数就是 0,程序就能立刻感知到“分配失败”。

对应的 Mapper XML 里,updateOccupied 的条件判断是:

<update id="updateOccupied"> UPDATE t_dormitory SET current_count = current_count + 1 WHERE id = #{dormId} AND current_count &lt; capacity </update>

注意 XML 里小于号必须写成&lt;,不然会被当成标签解析报错。这段代码同时展示了 MyBatis 动态 SQL 的一个基础用法:在 update 语句里拼接业务条件,用返回值决定业务走向。退宿时反向操作,current_count 减 1,同时把学生的 dorm_id 置空、bed_no 置空。

3.3 报修工单的状态流转

报修模块虽然简单,但状态机的设计可以直接用在其它管理类毕设里。报修单有四个状态:1 待处理、2 处理中、3 已完成、4 已取消。学生只能发起报修和取消未处理的单子,宿管可以接单、完工。核心状态更新方法:

public boolean updateRepairStatus(Integer repairId, Integer targetStatus, User operator) { Repair repair = repairMapper.selectById(repairId); if (repair == null) { return false; } Integer current = repair.getStatus(); String operatorRole = operator.getRole(); // 状态流转校验:不是所有角色都能改任意状态 if ("1".equals(current) && "4".equals(targetStatus) && "3".equals(operatorRole)) { // 学生只能取消“待处理”的报修 return repairMapper.updateStatus(repairId, targetStatus) > 0; } if ("1".equals(current) && "2".equals(targetStatus) && "2".equals(operatorRole)) { // 宿管接单:待处理 -> 处理中 return repairMapper.updateStatus(repairId, targetStatus) > 0; } if ("2".equals(current) && "3".equals(targetStatus) && "2".equals(operatorRole)) { // 宿管完工:处理中 -> 已完成 return repairMapper.updateStatus(repairId, targetStatus) > 0; } return false; }

这里的核心思想是“谁在什么状态下能做什么操作”都写在一个方法里,而不是在 JSP 页面上用 if 判断隐藏按钮。页面上做按钮权限只是锦上添花,Service 层做状态校验才是真正拦住非法操作的地方。如果你在二次开发时加了“拒绝报修”的功能,只需要在这个方法里加一条2 待处理 -> 4 已取消的流转分支,并限制操作角色为宿管就行。

3.4 卫生评分的统计查询

卫生管理页面除了列表,还有一个按楼栋统计平均分的图表。对应的 SQL 是一段典型的多表联查,在 Mapper XML 里这样写:

<select id="selectHealthStatsByBuilding" resultType="map"> SELECT b.name AS buildingName, ROUND(AVG(h.score), 1) AS avgScore, COUNT(h.id) AS checkCount FROM t_health h INNER JOIN t_dormitory d ON h.dorm_id = d.id INNER JOIN t_building b ON d.building_id = b.id <where> <if test="buildingId != null and buildingId != ''"> AND b.id = #{buildingId} </if> <if test="startDate != null and startDate != ''"> AND h.check_date &gt;= #{startDate} </if> <if test="endDate != null and endDate != ''"> AND h.check_date &lt;= #{endDate} </if> </where> GROUP BY b.id, b.name ORDER BY avgScore DESC </select>

用<where>和<if>做动态筛选是这个项目里用得最多的查询技巧。三个条件哪个填了就拼哪个,没填就不拼,避免拼出AND 1=1这种不太体面的 SQL。返回类型定义成 map,是因为查询结果的列是动态聚合出来的,没有对应的实体类,resultType="map"可以直接把每行结果变成字段名和值的 Map。

4. 本地运行:从源码到 Tomcat 跑通的七个步骤

4.1 环境准备与版本说明

先把环境对齐,这是减少翻车概率的第一步。建议用 JDK 1.8,因为 SSM 项目在 JDK 11 以上的某些版本会遇到 JSP 支持问题和反射相关的报错,不是不能跑,是没必要花时间折腾。MySQL 用 5.7 就好,8.0 也能用但需要换成对应的驱动版本。Tomcat 用 8.5 和 JDK 8 的兼容性最稳;Tomcat 10 的 javax.servlet 包名变成了 jakarta.servlet,SpringMVC 4 的依赖会直接报类找不到,所以不要选新版本。

Maven 建议用 3.6.x,版本太新的话和项目里老插件的兼容性也容易出现警告。装好之后在命令行执行mvn -v能看到版本信息,就说明环境基本就绪。

4.2 初始化数据库

第一次跑起来之前,先把数据库结构和初始数据准备好。项目里通常会带一个sql/init.sql脚本,里面建好了全部表并预置了三个角色的测试账号。导入命令:

mysql -uroot -p < sql/init.sql

导入完成后,用SHOW TABLES;检查是否有 7 张表。如果脚本里带中文注释,导入时出现乱码,可以在命令里加上默认字符集参数:mysql --default-character-set=utf8 -uroot -p < sql/init.sql。这一步虽然简单,但确实是我见过同学最常卡住的地方——表没建成功,后面所有页面打开都会报 500。

4.3 修改数据库连接配置

源码里有一个jdbc.properties文件,里面写的是开发者的本机配置,需要改成你自己的:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/dormitory?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

数据库密码一定要改,驱动和 url 保持原样即可。url 里的useUnicode=true&characterEncoding=utf8是负责中文不乱码的关键参数,建议不要删。如果 MySQL 是 8.0,把 driver 换成com.mysql.cj.jdbc.Driver,url 里最好再加一个serverTimezone=Asia/Shanghai,否则时间字段的读写会报错或者出现时区偏移。

4.4 打包并部署到 Tomcat

修改完配置就可以打 war 包了。在项目根目录打开命令行:

mvn clean package -DskipTests

打包完成后,在target/目录下会出现一个 war 文件,文件名通常是dormitory.war。把这个 war 文件复制到 Tomcat 安装目录的webapps/下,然后启动 Tomcat:

# Windows bin/startup.bat # macOS / Linux bin/startup.sh

Tomcat 启动时会自动解压 war 包。等日志里出现Deploying web application archive且没有 ERROR 输出后,浏览器访问http://localhost:8080/dormitory/,应该能跳到登录页。

4.5 功能验收清单

跑通之后不要只看登录页,按角色走一遍核心链路才算真的跑起来了。系统管理员的初始账号通常是 admin/admin123,登录后能进学生管理页面,能新建宿舍楼和宿舍、分配新生床位、发布公告。宿舍管理员的账号一般是 manager/123456,登录后能看到本楼栋的学生列表,能进行卫生评分、处理报修单。学生的账号一般是 2023001/123456,登录后能查看自己的宿舍信息、提交报修、查看公告。

验收的时候重点检查三个页面:学生列表的分页是否正常、卫生评分的编辑是否回显、报修单的状态按钮是否随状态变化而禁用。这三个页面分别对应列表查询、表单回显、状态流转三种最常见的功能,也是后面改代码最可能动到的部分。

5. 避坑记录:乱码、端口冲突和权限绕过

5.1 中文全部变成问号

现象:学生姓名、宿舍楼名等中文数据在页面上一片问号,新增的记录也是乱码。 原因:三层编码有断点。第一层,Tomcat 默认的 GET/POST 请求编码不是 UTF-8;第二层,JDBC 连接串里没有指定字符编码;第三层,MySQL 表默认可能是 latin1。 解决:在 web.xml 里配一个 CharacterEncodingFilter 强制请求编码为 UTF-8;JDBC url 加上characterEncoding=utf8;建表时使用DEFAULT CHARSET=utf8mb4。这三处都对齐后中文基本不会再出问题。

5.2 Tomcat 端口被占用

现象:启动 Tomcat 时控制台报Port 8080 required by Tomcat v8.5 is already in use。 原因:之前的 Tomcat 实例没有完全关闭,或者 8080 被其它程序占用。最常见的是开了多个 IDE 窗口,每个窗口都启动了一遍 Tomcat。 解决:Windows 下执行netstat -ano | findstr 8080查看占用进程的 PID,在任务管理器里结束对应进程;或者改Tomcat/conf/server.xml里的 Connector port 为 8081,重启后访问端口跟着变。如果 IDE 的配置里同时设置了热部署和自动重启,也可能触发端口没释放干净,把 Tomcat 从 IDE 中移除再重新添加一次即可。

5.3 不登录直接敲 URL 能访问后台页面

现象:浏览器没登录,直接输入/dormitory/admin/student/list的完整地址,页面照样打开。 原因:某些页面只在菜单层面做了隐藏,没在访问层做拦截。如果 session 里没有 user 对象,依旧可以访问到 JSP 页面。 解决:拦截器的 preHandle 里检查 session 中是否存在 loginUser,不存在的重定向到 login.jsp。注意放行逻辑只能放行 login 相关路径和 static 目录,其余路径一律拦截。配置完成后再用一个无痕窗口测试一次,输入后台地址时应该会跳回登录页。

5.4 查询结果对象字段全是 null

现象:学生列表页能查到记录数,但姓名、班级这些列都是空的。 原因:数据库字段是下划线命名stu_id、class_name,Java 实体属性是驼峰命名stuId、className,MyBatis 默认不会自动把stu_id映射到stuId,此时必须手动写 resultMap 或者在全局配置里开启驼峰映射。 解决:在 mybatis-config.xml 里加上<setting name="mapUnderscoreToCamelCase" value="true"/>。但要注意,有些老项目的 mapper XML 里手写了 resultMap,这时开启驼峰映射不会覆盖 resultMap 的显式映射,需要两边检查一遍。

5.5 同一张床被分配给了两个人

现象:宿管查宿舍入住名单,发现某床位被重复分配,一个床住了两个人。 原因:分配逻辑是先查询宿舍是否满员,再执行 update。两个管理员同时操作时,都查到“还有床位”,都走了 update,数据库层面没有限制。 解决:给宿舍表加一个capacity和current_count的字段约束,同时在业务代码里把 update 语句和“未满”条件合并执行,用影响行数判断是否分配成功。表结构加上CHECK (current_count <= capacity)也可以兜底,但 MySQL 5.7 对 check 约束的支持不完整,不能只依赖这一层。

6. 二次开发:从改字段到加模块的落地思路

6.1 优先改造点与工作量评估

拿到源码后,先别急着大改,按“改动面小、效果明显”的优先级来规划。我把常见改造方向按工作量排了一个参考顺序:

改造方向现状建议改法涉及文件
密码加密明文存储用 MD5 或 BCrypt 存入数据库UserServiceImpl、登录注册逻辑
学生批量导入逐条新增用 POI 解析 Excel 逐行 insert新增 Controller 方法 + JSP 上传表单
宿舍分配可视化下拉列表选宿舍新增楼栋-宿舍两级联动JSP 引入 Layui 的 form 模块
报修进度提醒无通知接入阿里云短信或企业微信机器人报修状态更新处调通知接口
技术栈升级SSM 架构整体迁移 Spring Boot + MyBatis-Plus全项目

密码加密是毕业设计里老师最爱问的一个点,改动也小。现在的登录逻辑是SELECT * FROM t_user WHERE username=? AND password=?,改成先按用户名查出来,再用 MD5 工具类把用户输入的密码加密后比对。只需要改登录 Service 这一个方法,外加初始化 SQL 里的密码字段值。

6.2 一个马上能落地的技巧:多条件学生检索

如果你只想体验一次真正的改造流程,推荐在学生列表页加一个多条件筛选功能。原来的列表是查全部加分页,现在要支持按姓名、班级、宿舍楼三个条件筛选。Mapper 里这样写:

<select id="selectStudentList" resultMap="StudentResultMap"> SELECT s.*, b.name AS building_name, d.room_no FROM t_student s LEFT JOIN t_dormitory d ON s.dorm_id = d.id LEFT JOIN t_building b ON d.building_id = b.id <where> <if test="name != null and name != ''"> AND s.name LIKE CONCAT('%', #{name}, '%') </if> <if test="className != null and className != ''"> AND s.class_name = #{className} </if> <if test="buildingId != null and buildingId != ''"> AND b.id = #{buildingId} </if> </where> ORDER BY s.stu_id </select>

Controller 层接收三个参数并封装成查询对象传给 Service,Service 原样传给 Mapper。JSP 页面的搜索表单写上对应的三个输入框和提交按钮即可。改完验证时注意一点:三个条件全不填的时候,SQL 的 where 标签会自动忽略,查询全部;填一个条件时只拼一段条件。这是我后来做每个项目都强制走一遍的验证流程——先空条件跑一次,再单条件跑一次,最后三个条件组合跑一次,确保没有 SQL 拼接错误。希望帮到你,早点跑通早点毕业。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询