☰
JSP+Servlet+MySQL学生宿舍管理系统:从建表到部署全拆解
2026/10/6 8:30:57 网站建设 项目流程

简介:一套基于Java+JSP+MySQL实现的Web学生宿舍管理系统,适合Java Web初学者、毕业设计学生以及需要快速搭建管理后台的开发者,可用于课程设计、期末项目或日常练习。系统完整覆盖宿舍管理场景:管理员能对学生信息、宿舍综合信息、卫生登记进行增删改查,并整体跟踪维修状况;学生可查看和修改个人信息、在线报修、查看卫生评比。压缩包共137个文件,包含44个Java源码、44个对应class文件、26个JSP页面,以及SQL脚本、CSS、JS、GIF演示图等资源,整体仅3.39MB,结构清晰,便于导入IDE对照学习或二次开发。资源中另附E-R图和SQL建表语句,可帮助理解数据表关系,并快速初始化数据库;各功能模块按角色拆分,适合系统了解登录、管理、报修等完整流程。该资源已有3001人学习下载,系统经多次测试运行无误,作为宿舍管理系统参考实现,能有效节省开发时间。

1. 学生宿舍管理系统:JSP、Servlet、MySQL 三件套串起来的完整闭环

做 JavaWeb 课程设计的人应该都有这种感觉:网上能搜到的“学生宿舍管理系统”几十个版本,但真正能一次跑通的没几个。这套基于 Java + JSP + MySQL 的宿舍管理系统,它的价值不在于功能多花哨,而在于把管理员、学生两条角色线、五张核心业务表、增删改查全套动作都收在了一个 Tomcat 项目里,还附带 E-R 图和 SQL 语句——这对要交课程设计、要准备答辩的人来说,是能直接照着讲清楚“我这系统怎么设计”的完整素材。它适合刚学完 JSP/Servlet 想找一个完整项目练手的人,也适合时间紧、想基于一套靠谱源码二次开发交作业的人。下面我按拆项目的思路,从模块划分到建表 SQL,再到请求流转和常见报错,一篇讲透。

2. 模块拆解与设计思路:先看清五个业务类各自管什么

2.1 管理员与学生双角色:同一套数据,两套操作权限

这套系统的业务核心是“两类人操作同一批宿舍数据”。管理员负责宿舍分配、卫生登记、维修派单、学生信息维护;学生负责查看自己的宿舍与卫生成绩、提交报修申请。两者有交叉,但操作边界很清楚。

功能点管理员学生
学生信息增删改查✅❌(仅本人可看自己)
宿舍信息管理✅✅(只读)
卫生检查登记✅❌
卫生评比查看✅✅
维修报修处理✅✅(学生提交,管理员处理)

这样的角色划分直接决定了下层表结构。学生表必须和宿舍表关联,维修表必须记录提交人和处理状态,卫生表要有对应的宿舍外键。所以代码里Student.class、Dormitory.class、Health.class、Repairs.class不只是简单的 JavaBean,而是和数据库字段一一对应的实体映射。

2.2 Servlet 类与业务动作的对应关系

从项目里的类名能看出这套系统的分层方式——典型的 JSP + Servlet + DAO 模式。很多人第一次拿到这种项目会懵,觉得类名太多、分不清谁调谁。我一般给读者一个最直观的映射表:

Servlet 类对应业务动作涉及实体
UpdateDorServlet修改宿舍信息(如床位、入住状态)Dormitory
UpdateHelServlet修改卫生检查记录Health
QueryDorServlet按条件查询宿舍列表Dormitory
QueryHelServlet查询卫生评比结果Health
DeleteHelServlet删除无效卫生记录Health

还有一类不在这份清单里但系统运行必须有的,是登录、学生信息维护、维修报修相关的 Servlet——常见做法是各自的增删改查动作各配一个 Servlet,比如AddStuServlet、UpdateStuServlet、QueryRepairsServlet。这种“一个动作一个 Servlet”的风格,虽然在 Spring Boot 时代显得有点笨,但它有很实际的好处:每个请求的处理逻辑短,答辩时被问到“点这个按钮之后浏览器发生了什么”,你能非常清楚地讲出请求从 JSP 表单到 Servlet 再到 DAO 的每一步。

提示:如果你拿到的源码里 Servlet 用的是@WebServlet("/xxx")注解方式,省去了 web.xml 的配置;如果是老项目则可能在 web.xml 里逐条注册。先看清楚再用,排错时能少走弯路。

3. E-R 图与 SQL 建表:把宿舍管理的数据模型落成四张表

3.1 实体与关系:一对多关系决定外键怎么放

拿到一份带 E-R 图的资源,重点不是看图好不好看,而是看它能不能回答“这张表为什么要这么设计”。宿舍管理系统的核心实体有四个:管理员、学生、宿舍、卫生检查记录、维修记录。关系也很直观——一个宿舍住多个学生,一个学生属于一个宿舍;一次卫生检查对应一个宿舍;一条维修记录对应一个宿舍。

这里有两个容易被忽略的设计点:

  • 卫生检查记录不直接关联学生,而是关联宿舍。因为卫生检查评的是房间,不是个人;
  • 维修记录里要同时保留“哪个宿舍报修”和“处理状态”,否则管理员没法分清待处理与已处理。

所以建表时外键的放置原则是:多的一方放外键。学生表放dorm_id,卫生表放dorm_id,维修表放dorm_id。管理员表独立,不参与外键关联。

3.2 建表 SQL:字段类型、默认值、字符集一次定对

下面这段建表 SQL 是我按这套系统的常见数据模型整理的,字段名和你拿到的源码可能略有差异,但结构可以对照着看:

-- 宿舍表 CREATE TABLE dormitory ( dorm_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '宿舍ID', dorm_no VARCHAR(20) NOT NULL UNIQUE COMMENT '宿舍编号,如 3-101', building VARCHAR(20) COMMENT '楼栋', floor_no INT COMMENT '楼层', bed_count INT DEFAULT 4 COMMENT '床位数', used_bed INT DEFAULT 0 COMMENT '已用床位' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍信息表'; -- 学生表 CREATE TABLE student ( stu_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID', stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号', stu_name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT DEFAULT 1 COMMENT '1男 0女', dorm_id INT, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_stu_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory(dorm_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表'; -- 卫生检查表 CREATE TABLE health ( health_id INT PRIMARY KEY AUTO_INCREMENT, dorm_id INT NOT NULL, check_date DATE NOT NULL, score DECIMAL(4,1) COMMENT '卫生评分,如 92.5', check_user VARCHAR(50) COMMENT '检查人', remark VARCHAR(200), CONSTRAINT fk_health_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory(dorm_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='卫生检查记录表'; -- 维修表 CREATE TABLE repairs ( repair_id INT PRIMARY KEY AUTO_INCREMENT, dorm_id INT NOT NULL, stu_id INT COMMENT '报修学生', content VARCHAR(500) COMMENT '维修内容', status TINYINT DEFAULT 0 COMMENT '0待处理 1已处理', submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, deal_time DATETIME NULL, CONSTRAINT fk_rep_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory(dorm_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍维修记录表';

建表时要注意utf8mb4而不是utf8——后者存不了 emoji 和部分生僻字,学生报修留言里只要出现一个特殊符号,整条记录写入就会报错。这个坑我在老项目里踩过,后来一律默认utf8mb4。

DECIMAL(4,1)这种字段类型,是给卫生评分留了三位整数加一位小数的空间。如果你嫌麻烦直接用INT也可以,但评分往往有 92.5 这种带小数的情况,用整数就得把成绩换算成 925,展示层再处理,绕了一圈不划算。

注意:外键约束在 MySQL 5.7 与 8.0 的写法是一样的,但 InnoDB 引擎下外键字段必须建索引。上面 SQL 里外键列dorm_id都在业务里有查询需求,所以这里建外键顺带就是索引,没问题。

3.3 初始化数据:没有演示数据,系统效果少一半

拿到 SQL 文件后,我建议先执行建表脚本,再执行一份初始化数据脚本。常见做法是给管理员表插一条默认账号、给宿舍表插几栋楼的数据、给学生表插几个测试学生,这样登录进去就有东西可看可改。初始化数据可以这样写:

INSERT INTO dormitory (dorm_no, building, floor_no, bed_count, used_bed) VALUES ('1-101', '1号楼', 1, 4, 3), ('1-102', '1号楼', 1, 4, 2), ('2-201', '2号楼', 2, 6, 5); INSERT INTO student (stu_no, stu_name, gender, dorm_id, phone) VALUES ('2023001', '张明', 1, 1, '13800000001'), ('2023002', '李婷', 0, 2, '13800000002'); INSERT INTO admin (username, password) VALUES ('admin', '123456');

INSERT语句里写具体数值,而不是用子查询,是因为初始化数据阶段表里数据还少,直接写 ID 更直观。注意学生表里dorm_id必须和宿舍表的主键对得上,否则外键约束会直接拒绝插入。真遇到这种报错,别急着改数据,先查是不是宿舍表那行数据没插入成功。

这套表结构就是整个系统的地基。后面 Servlet 里的增删改查,本质上都是围绕这四张表做 CRUD,理解了这个模型,后面看任何一段 DAO 代码都不会觉得乱。

4. 请求流转与核心代码:从登录到完成一次宿舍信息更新

4.1 登录校验与 Session:第一次请求是怎么被接住的

系统里任何角色进来都先过登录。登录请求的路径一般是login.jsp上填账号密码,提交到LoginServlet的doPost方法。这里我用这套系统里最常见的写法演示:

@WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); // 常见做法是单独写 AdminDao 做数据库校验 AdminDao dao = new AdminDao(); Admin admin = dao.findByUsernameAndPassword(username, password); if (admin != null) { HttpSession session = request.getSession(); session.setAttribute("loginAdmin", admin); response.sendRedirect("index.jsp"); } else { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }

这段代码里有几个参数值得细说。request.setCharacterEncoding("utf-8")如果漏了,表单提交的中文在 Tomcat 8 以下会乱码;放在doPost最前面,是为了让请求体里的参数按 UTF-8 解码。sendRedirect和forward的区别是——前者告诉浏览器重新发起一次 GET 请求,地址栏会变;后者是服务端内部转发,地址栏不变,但能带着request域里的属性。

如果你是第一次看这套代码,记住一个原则:登录成功用重定向,失败用转发。因为成功之后如果直接转发到index.jsp,用户刷新页面会重复提交表单;重定向之后刷新,只是重新请求一次首页,没有副作用。

4.2 查询逻辑:QueryDorServlet 如何把宿舍列表带回页面

宿舍列表查询是这套系统里最典型的读操作。QueryDorServlet负责接收请求参数(比如按楼栋筛选),查数据库,再把结果放进request域转发到 JSP 页面渲染。核心 DAO 方法大概长这样:

public List<Dormitory> findDormList(String building) { List<Dormitory> list = new ArrayList<>(); String sql = "SELECT * FROM dormitory WHERE 1=1"; // 常见做法是拼条件时用 StringBuilder,注意参数化 if (building != null && !building.isEmpty()) { sql += " AND building = ?"; } try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (building != null && !building.isEmpty()) { ps.setString(1, building); } ResultSet rs = ps.executeQuery(); while (rs.next()) { Dormitory d = new Dormitory(); d.setDormId(rs.getInt("dorm_id")); d.setDormNo(rs.getString("dorm_no")); d.setBuilding(rs.getString("building")); d.setBedCount(rs.getInt("bed_count")); d.setUsedBed(rs.getInt("used_bed")); list.add(d); } } catch (SQLException e) { e.printStackTrace(); } return list; }

这段是整套系统里最常用的套路,看似简单,但有两个点你得明白:

  • WHERE 1=1不是废话,是为了后面拼AND building = ?时不用操心前面有没有条件。老程序员喜欢这么写,新同事常看不懂,但它确实能减少拼接判断的逻辑;
  • 用PreparedStatement而不是直接拼 SQL 字符串,是为了防SQL 注入。学生输入的宿舍编号是普通文本,万一有人输入' OR '1'='1这种攻击串,参数化查询会让它变成一个普通字符串参数,而不是拼进 SQL 里。

Servlet 拿到这个List<Dormitory>之后,request.setAttribute("dormList", list),然后forward到dorm_list.jsp,页面里用 JSTL 的<c:forEach>遍历展示。这也是为什么这套系统能保持 JSP 里几乎看不到 Java 代码——数据都在后台组装好,页面只负责渲染。

4.3 更新逻辑:UpdateDorServlet 的提交-更新-跳转三步

更新操作是“读”的逆过程:先有表单页面回显旧数据,用户改完提交,Servlet 接收参数,调 DAO 更新,最后跳回列表页。这部分用UpdateDorServlet举例:

@WebServlet("/updateDor") public class UpdateDorServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); int dormId = Integer.parseInt(request.getParameter("dormId")); String dormNo = request.getParameter("dormNo"); String building = request.getParameter("building"); int bedCount = Integer.parseInt(request.getParameter("bedCount")); int usedBed = Integer.parseInt(request.getParameter("usedBed")); Dormitory d = new Dormitory(); d.setDormId(dormId); d.setDormNo(dormNo); d.setBuilding(building); d.setBedCount(bedCount); d.setUsedBed(usedBed); DormitoryDao dao = new DormitoryDao(); boolean ok = dao.updateDormitory(d); if (ok) { // 更新成功回到列表,刷新出最新数据 response.sendRedirect("queryDor?building="); } else { request.setAttribute("msg", "更新失败,请检查输入"); request.getRequestDispatcher("dorm_edit.jsp").forward(request, response); } } }

Integer.parseInt那几行是表单字符串转数字的常规操作,但这里有个隐藏风险——如果前端 JSP 输入框里被填了非数字内容,parseInt会抛NumberFormatException。稳妥的做法是在 JSP 端用 JavaScript 先校验,或者在 Java 端 try-catch。课程设计答辩时,老师很爱问“如果用户输入不合法怎么办”,你得有个说法。

更新完成后跳回queryDor查询列表,而不是直接跳到静态页面,这是保证“改完能看到新数据”的关键。这套系统的所有更新操作都遵循这个三步逻辑:接收参数 → 更新数据库 → 重定向回查询接口。

提示:看到queryDor这个路径不要奇怪,它既是列表页的 Servlet 地址,也是更新成功后跳转的地址。一个 Servlet 同时承担“展示列表”和“刷新列表”两个职责,在小型系统里很普遍。

5. 避坑与排查:驱动、乱码、404、连接断开的四个真实翻车现场

5.1 JDBC 驱动找不到:Class.forName 报 ClassNotFoundException

现象:Tomcat 启动后访问首页正常,但点击任意一个需要查数据库的功能,页面直接报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。

原因:数据库驱动 jar 包没放进WEB-INF/lib目录,或者放进去后没重启 Tomcat。很多初学者把mysql-connector-java的 jar 放在项目根目录、甚至放在系统盘的某个下载文件夹里,编译能通过,因为 IDE 的 classpath 里能看到,但 Tomcat 运行时不认。

解决:把驱动 jar 复制到项目/webapps/工程名/WEB-INF/lib/下,然后重启Tomcat。注意是重启不是热部署,热部署往往不加载新的 jar。如果你用的是 Maven 工程,就在pom.xml里声明依赖,但课程设计这类直接拷文件夹的工程,手动放 jar 反而更直观。

5.2 中文乱码:改 JSP 没用,问题在请求与响应编码

现象:系统登录后,学生姓名、宿舍楼栋显示成????,或者表单提交后数据库中存进去的就是乱码。

原因:三层编码不一致。第一层是 HTTP 请求编码,表单提交时浏览器按页面编码发送;第二层是 Tomcat 接收请求时用的编码;第三层是 MySQL 连接串里的编码。只改 JSP 页面的pageEncoding只能管页面本身,管不到 Servlet 收参数和 JDBC 写库。

解决:按顺序做三件事。第一,JSP 头部写成<%@ page contentType="text/html;charset=UTF-8" language="java" %>;第二,每个接收请求的 Servlet 里doPost第一行加request.setCharacterEncoding("UTF-8");第三,JDBC 连接串里追加?useUnicode=true&characterEncoding=UTF-8。三个都到位,乱码基本消失。

5.3 表单提交后 404:Servlet 路径对不上,或注解没生效

现象:列表页正常,但点击“添加”“删除”按钮后浏览器地址栏变了,紧接着出现 404 页面。

原因:八成是表单的action路径和@WebServlet里的值对不上。比如 JSP 里写action="dorm/addDor",而@WebServlet("/addDor"),Tomcat 会去找名为/dorm/addDor的映射,找不到就 404。另一种可能是你用了web.xml注册 Servlet,但url-pattern写成了/dorm/*,预期与实际不符。

解决:第一步看浏览器地址栏,确认请求到底打到哪个路径。第二步全局搜索每一个@WebServlet注解,把路径列一张表,再和 JSP 里所有表单的action、所有超链接的href对照。项目小,但路径多,列一次表之后思路立刻清晰。

5.4 数据库连接断掉:长时间挂着再操作报 Connection is not available

现象:系统刚部署时一切正常,放一个晚上第二天再操作,页面卡很久,最后报Connection is not available或Communications link failure。

原因:代码里每次操作都DriverManager.getConnection()新建连接,用完就关。MySQL 默认的wait_timeout是 8 小时,连接空闲超过这个时间会被服务端断开,但客户端不知道,于是拿到一个“假”的连接。常见问题,不玄学。

解决:两选一。短期办法是把 MySQL 的wait_timeout调大,比如SET GLOBAL wait_timeout = 28800;,但这不是根治。长期做法是写一个DBUtil工具类,统一管理连接获取与释放,连接参数集中配置,别在每一个 DAO 里散落着写getConnection。更进一步可以引入连接池,但课程设计阶段,先保证每次用完关掉、参数集中在一个类里就够。

6. 部署验证与二次开发:从打包 War 到给维修模块加一个状态位

6.1 Tomcat 部署与运行验证:三分钟确认系统是否可用

拿到项目源码之后,先不要急着看代码,把环境跑起来再逐个功能点验证,这是最省时间的习惯。先把项目文件夹拷贝到Tomcat/webapps/下,启动 Tomcat,然后在浏览器里依次走一遍关键路径:

# 确认 Tomcat 没启动就直接启动 cd /usr/local/tomcat/bin ./startup.sh # 看启动日志,确认没有异常 tail -f /usr/local/tomcat/logs/catalina.out # 本地访问系统入口 curl -I http://localhost:8080/dormitory_system/login.jsp

curl -I返回HTTP/1.1 200说明页面可达。如果 404,先检查 URL 里的工程名和你webapps下的文件夹名是否一致,Tomcat 的默认访问路径是http://localhost:8080/文件夹名/页面名。这一步其实能省掉后面一半的排错。

6.2 二次开发示例:给维修记录加“处理人”字段

拿到一套能跑的源码,最有价值的动作是顺着它的结构加一个小功能。这里我以维修表为例,加一个handler字段,记录谁处理了这条报修——这也是真实宿舍管理场景里一定会有的需求。

第一步改数据库:

ALTER TABLE repairs ADD COLUMN handler VARCHAR(50) COMMENT '处理人' AFTER status;

第二步改Repairs实体类,增加private String handler;属性和对应的 getter/setter。第三步在RepairsDao的更新方法里补上这个字段:

// 在原有 update 方法中追加 handler 字段的更新 String sql = "UPDATE repairs SET status = ?, handler = ?, deal_time = NOW() WHERE repair_id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, rep.getStatus()); ps.setString(2, rep.getHandler()); ps.setInt(3, rep.getRepairId());

第四步在维修处理的 JSP 页面表单里增加一个“处理人”输入框,name 设为handler,提交后 Servlet 里用request.getParameter("handler")接收,再 set 到实体对象里。一个完整的功能扩展就闭环了。

从这套流程你能看出来,这套系统的扩展思路非常线性:数据库加一列 → 实体加一个属性 → DAO 加一个字段更新 → Servlet 接收一个参数 → JSP 加一个输入框。顺着这个链路改,不会漏。我最开始拆这类项目时,总是习惯先改 JSP 再改数据库,结果经常漏掉 DAO 层,页面提交了数据没写进去,查半天才发现问题。后来我强制自己按“数据库 → 实体 → DAO → Servlet → JSP”的顺序走,再没翻过车。

系统跑通之后,建议你做的第一件事不是继续加功能,而是把管理员、学生两条线各自的操作路径手写一遍——哪一步调了哪个 Servlet、查了哪张表,画成一张纸上的流程图。答辩问到任何一个功能,你都能顺这条线讲清楚。希望这篇拆解帮到你,至少让你拿到源码之后,不至于对着十几个 Servlet 和 JSP 页面发懵。

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

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

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

立即咨询