简介:这是一套基于Servlet+JSP+MySQL开发的JavaWeb学生宿舍管理系统源码及配套教程,面向计算机专业本科生,适用于期末大作业、课程设计与毕业设计等实践场景。系统功能完整,涵盖学生入住、退宿、调宿、报修及管理员权限管理等核心模块,代码结构清晰、注释规范,已通过本地编译验证并获98分高分评审,助教审定确保教学适用性。资源包共202个文件,含21个Java后端逻辑类、22个JSP页面视图、73个JS交互脚本、35个PNG图标资源、16个CSS样式文件及1个SQL建表脚本,辅以Bootstrap与jQuery DataTables等前端组件,整体压缩包仅7.21MB,轻量易部署。目前已有248人学习下载,提供从环境配置、数据库初始化到功能演示的全流程使用教程,帮助初学者快速理解MVC分层架构与Web应用开发闭环。
1. 为什么一个“学生宿舍管理系统”能成为JavaWeb入门者的通关钥匙:Servlet+JSP+MySQL组合不是过时技术,而是理解Web分层本质的最小可行闭环
你可能在招聘JD里看到“熟悉SpringBoot即可”,也可能在GitHub上刷到满屏的Vue+Node项目——但当你真正要从零搭起一个带登录、增删改查、数据持久化的完整Web应用时,Servlet+JSP+MySQL这套“老三样”反而成了最透明、最可控、最容易debug的起点。它不藏黑匣子:HTTP请求怎么进来的、业务逻辑在哪执行、HTML怎么拼出来的、SQL怎么发出去的,每一步都裸露在代码里。这不是复古情怀,而是工程直觉训练——比如你改错一个<c:forEach>标签嵌套顺序,页面立刻白屏;漏写一句request.setCharacterEncoding("UTF-8"),中文就变乱码;MySQL驱动没放进WEB-INF/lib,控制台直接报ClassNotFoundException。这种“改一行,看一眼,错在哪”的反馈节奏,比SpringBoot自动装配出错后翻10层堆栈更利于建立肌肉记忆。本篇聚焦真实可运行的学生宿舍管理系统源码,不讲理论推导,只拆解:如何用原生JavaWeb三件套,在Tomcat上跑通一个含学生入住、宿舍分配、管理员审核的真实业务流。适合刚学完Java基础、想验证自己能否独立交付Web模块的开发者,也适合需要快速搭建教学演示系统的教师或课程设计者。
2. 搭建环境:用最简路径把Servlet+JSP+MySQL串成一条能跑的流水线
2.1 JDK+Tomcat+MySQL三件套的版本锚点与安装确认
别被网上“最新版教程”带偏——这个组合对版本敏感度极高。我实测过JDK 17 + Tomcat 10.1 + MySQL 8.0.33的组合,结果JSP编译失败(Tomcat 10默认使用Jakarta EE 9命名空间,而老JSP标签库仍用javax.*)。必须锁定兼容链:
- JDK 8u202(非JDK 11+):
java -version输出含1.8.0_202 - Tomcat 8.5.94(非9.x/10.x):解压即用,
bin/startup.bat启动后访问http://localhost:8080出现猫图标 - MySQL 5.7.44(非8.0+):安装时勾选
Add to PATH,mysql --version返回5.7.44
提示:MySQL 5.7默认关闭
ONLY_FULL_GROUP_BY模式,避免后续查询报错;Tomcat 8.5自带JSP引擎(Jasper),无需额外配置JSP编译器。
2.2 创建数据库与初始化表结构:用SQL脚本一次性铺平数据底座
宿舍管理核心表只有4张:student(学生)、dormitory(宿舍)、admin(管理员)、repair_record(报修记录)。关键字段设计直指业务痛点:
student表中dorm_id为外键,但不设ON DELETE CASCADE——防止误删宿舍导致学生数据丢失;dormitory表中capacity(床位数)和current_occupancy(当前入住数)分离,避免并发修改时计数错误;repair_record表中status ENUM('pending','processing','done') DEFAULT 'pending',用枚举约束状态流转。
执行以下SQL创建数据库及表(保存为init.sql,用MySQL命令行导入):
-- 创建数据库 CREATE DATABASE IF NOT EXISTS dorm_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 切换数据库 USE dorm_management; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, gender ENUM('male','female') NOT NULL, phone VARCHAR(15), dorm_id INT, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (dorm_id) REFERENCES dormitory(id) ON UPDATE CASCADE ); -- 宿舍表 CREATE TABLE dormitory ( id INT PRIMARY KEY AUTO_INCREMENT, building VARCHAR(10) NOT NULL, -- 如"A栋" floor INT NOT NULL, -- 楼层 room_number VARCHAR(10) NOT NULL, -- 如"201" capacity INT NOT NULL DEFAULT 4, current_occupancy INT NOT NULL DEFAULT 0, remark TEXT ); -- 管理员表(密码明文存储仅用于教学演示,生产环境必须加密) CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, role ENUM('super','normal') DEFAULT 'normal' ); -- 报修记录表 CREATE TABLE repair_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, dorm_id INT NOT NULL, description TEXT NOT NULL, status ENUM('pending','processing','done') DEFAULT 'pending', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, FOREIGN KEY (dorm_id) REFERENCES dormitory(id) ON DELETE CASCADE );执行命令:
mysql -u root -p < init.sql输入密码后无报错即成功。用SHOW TABLES;确认四张表存在。
2.3 配置Tomcat连接池:用context.xml实现数据库连接复用
把数据库连接硬编码在Servlet里是新手常见翻车点——每次请求都新建Connection,10个并发就耗尽MySQL连接数。正确做法是让Tomcat管理连接池,在Tomcat/conf/context.xml中添加:
<!-- 在<Context>标签内添加 --> <Resource name="jdbc/DormDB" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/dorm_management?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8" username="root" password="your_password" maxActive="20" minIdle="5" maxWait="10000" validationQuery="SELECT 1" testOnBorrow="true"/>注意:
url中的&必须写成&(XML转义),serverTimezone=GMT%2B8解决MySQL 5.7时区问题;validationQuery确保连接有效性,避免空闲连接超时失效。
在Servlet中获取连接的代码(BaseServlet.java):
// 获取JNDI数据源 Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/DormDB"); Connection conn = ds.getConnection(); // 此处不再new DriverManager.getConnection()3. 核心业务实现:用Servlet处理请求,JSP渲染页面,三层结构不靠框架也能清晰落地
3.1 登录模块:从HTTP请求解析到Session状态保持的完整链路
登录不是简单比对用户名密码,而是涉及请求参数获取→业务校验→状态存储→跳转控制四步闭环。LoginServlet.java代码如下:
@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取请求参数(注意:GET请求用getParameter,POST用getParameter) String username = request.getParameter("username"); String password = request.getParameter("password"); // 2. 业务校验(空值检查放在DAO之前,减少数据库压力) if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) { request.setAttribute("error", "用户名或密码不能为空"); request.getRequestDispatcher("/login.jsp").forward(request, response); return; } // 3. 调用DAO查询管理员(此处简化,实际应加密比对) AdminDAO adminDAO = new AdminDAO(); Admin admin = adminDAO.findByUsername(username); // 4. 密码校验与Session设置 if (admin != null && admin.getPassword().equals(password)) { // 将用户信息存入Session,供后续页面使用 HttpSession session = request.getSession(); session.setAttribute("admin", admin); session.setMaxInactiveInterval(1800); // Session超时30分钟 // 重定向到主页,避免刷新重复提交 response.sendRedirect("main.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } }关键细节:
response.sendRedirect()用于重定向(URL变更,GET请求),request.getRequestDispatcher().forward()用于服务器端转发(URL不变,保留POST参数)。登录成功必须重定向,否则F5刷新会重复提交表单。
3.2 学生列表页:JSP+JSTL+EL表达式协同渲染动态表格
student_list.jsp不写Java脚本(<% %>),全部用JSTL标签和EL表达式,保证前后端关注点分离:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <title>学生列表</title> <link rel="stylesheet" href="css/bootstrap.min.css"> </head> <body> <div class="container mt-4"> <h2>学生信息列表</h2> <a href="add_student.jsp" class="btn btn-primary mb-3">新增学生</a> <!-- 表格头部 --> <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>性别</th> <th>电话</th> <th>宿舍</th> <th>操作</th> </tr> </thead> <tbody> <!-- JSTL遍历students集合 --> <c:forEach var="student" items="${students}"> <tr> <td>${student.id}</td> <td>${student.name}</td> <td><c:if test="${student.gender == 'male'}">男</c:if> <c:if test="${student.gender == 'female'}">女</c:if></td> <td>${student.phone}</td> <td> <c:choose> <c:when test="${empty student.dormId}">未分配</c:when> <c:otherwise> <c:forEach var="dorm" items="${dorms}"> <c:if test="${dorm.id == student.dormId}"> ${dorm.building}-${dorm.floor}F-${dorm.roomNumber} </c:if> </c:forEach> </c:otherwise> </c:choose> </td> <td> <a href="edit_student.jsp?id=${student.id}" class="btn btn-sm btn-info">编辑</a> <a href="delete_student?id=${student.id}" class="btn btn-sm btn-danger" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach> </tbody> </table> </div> </body> </html>逻辑说明:
${students}来自Servlet中request.setAttribute("students", studentList);<c:forEach>遍历集合;<c:choose>替代if-else;onclick="return confirm()"阻止误删。所有数据库查询都在Servlet完成,JSP只负责展示——这是MVC分层铁律。
3.3 宿舍分配逻辑:事务控制下的多表更新与并发安全
给学生分配宿舍需同时更新student.dorm_id和dormitory.current_occupancy,且必须原子性执行。DormAssignServlet.java中使用JDBC事务:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int studentId = Integer.parseInt(request.getParameter("studentId")); int dormId = Integer.parseInt(request.getParameter("dormId")); Connection conn = null; PreparedStatement ps1 = null, ps2 = null; try { conn = DataSourceUtil.getConnection(); // 从连接池获取 conn.setAutoCommit(false); // 关闭自动提交 // 更新学生宿舍ID ps1 = conn.prepareStatement("UPDATE student SET dorm_id = ? WHERE id = ?"); ps1.setInt(1, dormId); ps1.setInt(2, studentId); ps1.executeUpdate(); // 更新宿舍当前入住数(先查再+1,避免脏读) ps2 = conn.prepareStatement("SELECT current_occupancy FROM dormitory WHERE id = ?"); ps2.setInt(1, dormId); ResultSet rs = ps2.executeQuery(); if (rs.next()) { int current = rs.getInt("current_occupancy"); if (current < getCapacity(dormId)) { // 需先查capacity,此处省略方法 PreparedStatement ps3 = conn.prepareStatement("UPDATE dormitory SET current_occupancy = ? WHERE id = ?"); ps3.setInt(1, current + 1); ps3.setInt(2, dormId); ps3.executeUpdate(); conn.commit(); // 提交事务 request.setAttribute("msg", "分配成功"); } else { throw new RuntimeException("宿舍已满"); } } } catch (Exception e) { try { if (conn != null) conn.rollback(); // 回滚事务 } catch (SQLException ex) { ex.printStackTrace(); } request.setAttribute("error", "分配失败:" + e.getMessage()); } finally { DataSourceUtil.closeAll(conn, ps1, ps2, null); } request.getRequestDispatcher("student_list.jsp").forward(request, response); }参数说明:
conn.setAutoCommit(false)开启手动事务;conn.commit()和conn.rollback()控制边界;DataSourceUtil.closeAll()确保资源释放。没有事务,宿舍超员分配就是定时炸弹。
4. 避坑指南:那些让新手调试到凌晨三点的典型故障与根因定位
4.1 现象:JSP页面中文显示为“???”,但数据库里存的是正确汉字
原因:Tomcat默认使用ISO-8859-1解码POST请求参数,而浏览器发送UTF-8编码的中文,导致解码错乱。
解决:在每个处理POST请求的Servlet开头添加:
request.setCharacterEncoding("UTF-8"); // 必须在getParameter()之前调用 response.setContentType("text/html;charset=UTF-8");进阶方案:写一个
CharacterEncodingFilter全局过滤器,避免每个Servlet重复写。
4.2 现象:登录后跳转到main.jsp,但页面提示“session is null”
原因:request.getSession()默认创建新Session,但若web.xml中未配置<session-config>,某些Tomcat版本Session Cookie的Path为/projectName而非/,导致跨路径访问时Session丢失。
解决:在web.xml中显式配置:
<session-config> <session-timeout>30</session-timeout> <cookie-config> <path>/</path> <!-- 关键!设置Cookie路径为根路径 --> <http-only>true</http-only> </cookie-config> </session-config>4.3 现象:点击“删除学生”,控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
原因:MySQL 5.7使用com.mysql.jdbc.Driver,但MySQL 8.0+改为com.mysql.cj.jdbc.Driver;而你的WEB-INF/lib下放的是8.0驱动jar包。
解决:
- 下载MySQL 5.7专用驱动:
mysql-connector-java-5.1.47.jar - 删除旧驱动,将新jar包放入
WEB-INF/lib - 重启Tomcat(必须重启,热部署不生效)
4.4 现象:<c:forEach>遍历不出数据,页面空白
原因:JSTL标签库未正确引入,或web.xml中缺少<taglib>声明(Tomcat 8.5+通常不需要,但若用老版本IDE生成的web.xml可能遗漏)。
解决:
- 确认
WEB-INF/lib中有jstl-1.2.jar和standard-1.1.2.jar(或jakarta.servlet.jsp.jstl-2.0.0.jar) - 在JSP顶部声明:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - 若用Tomcat 9+,uri应改为
http://xmlns.jcp.org/jsp/jstl/core
4.5 现象:新增学生后,宿舍current_occupancy没增加,但学生表已更新
原因:事务未提交,或conn.commit()前发生异常但未被捕获,导致回滚失败。
排查:
- 在
catch块中打印e.printStackTrace(),确认是否抛出SQLException - 检查
getCapacity(dormId)方法是否真的返回了宿舍容量(常因SQL写错返回0) - 在MySQL命令行执行
SELECT * FROM dormitory WHERE id = X,确认current_occupancy初始值是否为0(若为NULL,current + 1结果为NULL)
5. 源码结构与部署验证:从文件组织到真机运行的最后五步 checklist
5.1 项目目录结构:看清每个文件夹的职责边界
一个可运行的JavaWeb项目,目录结构必须严格遵循Servlet规范。以下是本宿舍管理系统的标准布局(基于Eclipse Dynamic Web Project):
dorm-management/ ├── src/ # Java源码 │ ├── dao/ # 数据访问对象(StudentDAO.java等) │ ├── service/ # 业务逻辑(StudentService.java,可选) │ ├── servlet/ # 控制器(LoginServlet.java等) │ └── entity/ # 实体类(Student.java, Dormitory.java) ├── WebContent/ # Web资源根目录(Tomcat部署时的webapp) │ ├── css/ # 样式文件(bootstrap.min.css) │ ├── js/ # JS脚本(validate.js表单校验) │ ├── images/ # 图片资源 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符(Servlet映射、过滤器等) │ │ ├── lib/ # 第三方jar包(mysql-connector-java-5.1.47.jar, jstl-1.2.jar) │ │ └── views/ # JSP页面(按功能分组,如student/, dormitory/) │ │ ├── login.jsp │ │ ├── main.jsp │ │ ├── student_list.jsp │ │ └── add_student.jsp │ ├── index.jsp # 默认首页(重定向到login.jsp) │ └── error.jsp # 统一错误页 └── build.xml # Ant构建脚本(可选,用于自动化打包)关键原则:
src/下Java类编译后自动输出到WebContent/WEB-INF/classes/;WEB-INF/下内容不可被客户端直接访问(如WEB-INF/web.xml无法通过URL打开);所有JSP必须放在WebContent/或其子目录,不能放在WEB-INF/外的其他位置。
5.2 部署到Tomcat的五个必检动作
- 确认项目编码:Eclipse中右键项目 → Properties → Resource → Text file encoding → 选择
UTF-8(避免JSP中文乱码) - 验证Servlet映射:检查
WebContent/WEB-INF/web.xml中<servlet-mapping>是否匹配URL(如<url-pattern>/login</url-pattern>对应@WebServlet("/login")) - 检查jar包完整性:展开
WebContent/WEB-INF/lib/,确认存在mysql-connector-java-5.1.47.jar、jstl-1.2.jar、standard-1.1.2.jar(缺一不可) - 启动日志扫描:启动Tomcat后,查看
logs/catalina.out,确认无ClassNotFoundException或SQLException(重点搜SEVERE关键字) - 浏览器开发者工具验证:F12打开Network面板,刷新页面,观察:
login.jsp加载状态码200,Response Headers中Content-Type: text/html;charset=UTF-8- 提交登录表单后,Network中出现
/login请求,Status为302(重定向)或200(转发) student_list.jsp的Response中包含<table>标签,且<c:forEach>已渲染出数据
5.3 功能验证清单:用真实数据跑通核心业务流
不要只测单点功能,必须走通端到端业务链。按顺序执行以下操作,并记录预期结果:
| 步骤 | 操作 | 预期结果 | 验证方式 |
|---|---|---|---|
| 1 | 访问http://localhost:8080/dorm-management/login.jsp | 显示登录表单 | 浏览器页面可见 |
| 2 | 输入管理员账号admin/密码123456,点击登录 | 跳转到main.jsp,顶部显示“欢迎,admin” | URL变为/main.jsp,页面有欢迎文字 |
| 3 | 点击“学生管理”→“学生列表” | 显示空表格(因无数据) | 表格有表头,无数据行 |
| 4 | 点击“新增学生”,填入姓名“张三”、性别“male”、电话“13800138000”,提交 | 返回学生列表页,新增一行数据 | 表格第二行显示张三信息,ID为1 |
| 5 | 点击张三所在行的“分配宿舍”,选择A栋2楼201房 | 提示“分配成功”,宿舍当前入住数+1 | 查看数据库dormitory表,current_occupancy从0变为1 |
血泪经验:第4步若失败,立即检查
StudentDAO.insert()方法中SQL语句的字段顺序是否与INSERT INTO student (...) VALUES (...)完全一致;第5步若失败,用MySQL命令行执行UPDATE student SET dorm_id=1 WHERE id=1,确认SQL语法无误后再查Java代码。
5.4 性能与安全加固的三个低成本动作
源码教学项目常忽略生产级细节,但以下三点只需5分钟就能大幅提升健壮性:
SQL注入防护:所有DAO层查询必须用
PreparedStatement,禁用字符串拼接。例如:
❌ 危险写法:"SELECT * FROM student WHERE name = '" + name + "'"
✅ 正确写法:ps = conn.prepareStatement("SELECT * FROM student WHERE name = ?"); ps.setString(1, name);XSS防护:JSP中输出用户输入内容时,用JSTL的
<c:out>标签自动转义:<td><c:out value="${student.name}"/></td> <!-- 自动将<转为< -->密码明文存储替换:
AdminDAO中密码校验改为SHA-256哈希比对:// 注册时 String hashedPassword = DigestUtils.sha256Hex(rawPassword); // 登录时 if (admin.getPassword().equals(DigestUtils.sha256Hex(inputPassword))) { ... }依赖Apache Commons Codec:
commons-codec-1.15.jar加入WEB-INF/lib/
我带过3届学生做课程设计,90%的项目卡在环境配置和字符编码上。后来我把Tomcat启动日志、MySQL连接测试SQL、JSP中文乱码修复步骤,全写进README.md的“5分钟排错清单”里——学生照着做,平均调试时间从8小时降到1.5小时。这套Servlet+JSP+MySQL组合,不是让你停留在过去,而是给你一把解剖Web本质的手术刀。当SpringBoot的自动配置让你困惑“Bean到底在哪创建的”,回头看看web.xml里那一行<servlet-class>,答案就赤裸裸躺在那里。希望帮到你。
本文还有配套的精品资源,点击获取