JSP+Servlet宿舍管理系统源码解析:MVC分层与部署避坑指南
简介:基于Java Web的宿舍管理系统项目,采用JSP与Servlet开发,面向毕业设计、课程设计及JavaWeb初学者,可帮助理解MVC分层、HTTP请求处理、会话管理等核心机制。压缩包共281个文件,大小约4.68MB,包含44个Java源文件与44个class字节码文件、16个JSP页面、32个HTML页面、24个JavaScript脚本、7个CSS样式表,还配有jar依赖库、SQL数据库脚本、项目配置文件及说明文档,目录结构清晰,覆盖前端展示、后端业务、数据访问与部署配置各层。系统包含用户管理、宿舍分配、资源预订等典型功能模块,代码中体现Servlet作为控制器、JSP作为视图、JavaBean作为数据模型的经典MVC设计,可学习JDBC数据库CRUD操作、JSP内置对象在请求与响应中的使用、session会话状态管理、异常处理以及基础的用户登录校验与权限判断。已有64人学习下载,适合通过源码研读掌握Web应用从请求到响应的完整流程,并基于现有结构扩展新功能或优化项目性能。 一个成熟的后端项目,拆开看才知道值不值得下。这个宿舍管理系统基于 JSP+Servlet 实现,class 文件暴露了清晰的 DAO、Service、Servlet 分层,对课程设计和毕业设计都很友好。我拆了整套源码,把架构、部署、避坑、进阶一次讲透。
1. 为什么我建议你把 JSP+Servlet 宿舍管理系统当 JavaWeb 练手项目
别一听 JSP+Servlet 就觉得老掉牙——不管现在 Spring Boot 多流行,高校的 JavaWeb 课程设计和毕业设计里,JSP+Servlet 依然是出镜率最高的组合之一。原因很实在:它把 HTTP 请求、会话跟踪、JDBC 连接、MVC 分层这些 JavaWeb 底层原理暴露得干干净净,比 Spring Boot 的自动化黑匣子更适合拿来答辩。这个宿舍管理系统项目,class 文件里能看到 StudentDaoImpl、StudentServiceImpl、StudentAddServlet、DormSelectServlet、EmailUtil 这些类,说明它不是那种把所有代码堆在 JSP 里的反面教材,而是真正按三层架构组织起来的可运行项目。适合三种人:正在做 JavaWeb 课程设计的学生、需要用完整案例准备毕业设计的人、以及想通过读源码理解 MVC 和 JDBC 的初学者。
2. 从 class 文件反推架构:这个宿舍管理系统的 MVC 分层与真实调用链
拿到一个 zip 包,里面的 .class 文件其实就是项目的骨架说明书。通过 javap 反编译和源码比对,能快速还原出项目的包结构、类职责和调用关系。这一章我用实际文件带你把这个项目拆开看明白。
2.1 先用 javap 反向解析 class 文件,搞清项目真实结构
不要急着打开 Java 源码,先看 class 文件列表。这个项目里出现的类可以分成四组:
javap -p -c StudentDaoImpl.class javap -p -c StudentServiceImpl.class javap -p -c StudentAddServlet.class javap -p -c EmailUtil.class-p参数会显示私有成员,-c会输出字节码指令。第一次跑的时候你会发现,StudentDaoImpl 里基本就是 Connection、PreparedStatement、ResultSet 这三件套,StudentServiceImpl 里就是 Dao 方法的组合调用,而 Servlet 里全是 doGet/doPost 和 request.getParameter。从这些类名能直接推断出项目的分层:Bean 类(StudentBean)属于模型层,DaoImpl 属于数据访问层,ServiceImpl 是业务逻辑层,Servlet 是控制器层,JSP 是视图层。
2.2 MVC 职责边界:JSP 只做展示,Servlet 只做转发
这个项目的核心设计思想是让 JSP 页面里不出现 Java 业务代码,所有请求先打到 Servlet,再由 Servlet 调用 Service 和 Dao。比如 StudentAddServlet 的典型处理流程是:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String stuName = request.getParameter("stuName"); String stuNo = request.getParameter("stuNo"); String dormId = request.getParameter("dormId"); StudentBean student = new StudentBean(); student.setStuName(stuName); student.setStuNo(stuNo); student.setDormId(dormId); StudentService service = new StudentServiceImpl(); boolean ok = service.addStudent(student); if (ok) { response.sendRedirect(request.getContextPath() + "/studentList.jsp"); } else { request.setAttribute("errorMsg", "添加失败,请检查学号是否重复"); request.getRequestDispatcher("/studentAdd.jsp").forward(request, response); } }从这段代码能看出 Servlet 的两个任务:第一,接收参数并封装成 StudentBean —— 这是参数绑定;第二,根据业务结果决定重定向还是转发 —— 这是流程控制。注意 sendRedirect 和 forward 的区别,重定向是浏览器再发一次新请求,地址栏会变,转发是服务器内部跳转,地址栏不变。
2.3 Service 层的意义:为什么不能直接在 Servlet 里写 JDBC
很多初学项目偷懒,直接在 Servlet 里写 Connection、PreparedStatement。这个项目没有这么做,而是多包了一层 StudentServiceImpl。好处在修改时立刻体现:假如你需要在添加学生时同时给该宿舍的宿管发一封邮件,只要在 StudentServiceImpl.addStudent 里加上 EmailUtil.sendMail() 这一行,Servlet 层的代码一行都不用动。如果你当初直接写在 Servlet 里,就得把 JDBC 代码和发邮件逻辑揉在一起,改一处崩三处——这就是分层最朴素的价值。用图来表示的话,调用链是这样的:
JSP 表单 → StudentAddServlet(Controller) → StudentServiceImpl(Service) → StudentDaoImpl(DAO) → MySQL 数据库2.4 从 StudentSelectDelListServlet 看批量操作的实现思路
这个类的名字看起来别扭,拆开读就明白:Student + Select + Del + List,它承担的是"查询学生列表并勾选待删除项"的复合功能。常见实现是 GET 请求先查全部学生分页展示,每行带一个 checkbox,表单提交到 StudentDelListServlet 后,批量删除的代码长这样:
String[] ids = request.getParameterValues("stuIds"); if (ids != null) { for (String id : ids) { studentService.deleteStudent(Integer.parseInt(id)); } }getParameterValues返回的是字符串数组,因为页面里可能有多个同名 checkbox。用Integer.parseInt转成 int 再传给 Service 层,注意要放在 try-catch 里处理。这个模式在几乎所有管理系统的"批量删除"功能里通用,值得记下来。
3. 把项目跑起来:Tomcat 部署、JDBC 配置与数据库初始化
源码看懂了不代表能跑通。很多同学栽在环境配置上,花在 Tomcat 和 JDBC 上的时间比写代码还多。这一章直接给出我验证过的最稳步骤,照做基本一次过。
3.1 环境选型:JDK 8 + Tomcat 8/9 + MySQL 5.7/8.0 的搭配理由
这个组合不是随便选的,背后是有真实教训的。Tomcat 10 开始把javax.servlet迁移到了jakarta.servlet,如果你拿这个项目的源码直接部署到 Tomcat 10,会因为 import 路径不一致直接报 ClassNotFoundException。JDK 版本同理,高版本 JDK 对老项目的编译兼容性并不好。推荐组合是 JDK 8 + Tomcat 8.5 或 9 + MySQL 5.7,能跑通的项目没必要追求新版。
java -version # 输出示例:java version "1.8.0_202"MySQL 8.0 的注意点主要是连接驱动要换成com.mysql.cj.jdbc.Driver,且 URL 后面必须加上时区参数 serverTimezone,后面避坑章节会专门讲。数据库脚本一般在项目的 db 或 sql 目录下,执行source dormitory.sql即可。
3.2 IDEA 导入项目三步走:打开、配 Artifacts、部署 Tomcat
IDEA 打开一个 JavaWeb 项目,最容易卡住的是部署时的 Artifacts 配置。一共就三步,不要省略:
第一步,File → Open选择项目目录,等 Maven 或普通项目索引完成。这个项目如果没有 pom.xml,说明是普通 Web 项目,直接手动添加 Web Facet。
第二步,File → Project Structure → Artifacts点击加号,选择Web Application: Exploded,再把右侧可用的构建输出拖到左侧WEB-INF/classes里。这里的关键是 lib 目录下必须有WEB-INF/lib文件夹并且把项目依赖的 jar 都放进去,否则运行时报ClassNotFoundException: com.mysql.jdbc.Driver。
第三步,点击运行配置的加号选择 Tomcat Server Local,在 Deployment 标签页点加号添加这个 Artifact,Application context 建议写成/dormitory。打开浏览器访问http://localhost:8080/dormitory/index.jsp,设置好 8080 端口不冲突就没问题。
3.3 数据库连接参数与连接池配置:从 db 目录初始化到 c3p0
这个项目的数据库配置在 db 目录或 src 下的 jdbc.properties 里。JSP+Servlet 时代最常用的连接池是 C3P0,因为它只要一个依赖 jar 加一个配置文件,不需要额外配置数据源。
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/dormitory?useUnicode=true&characterEncoding=UTF-8&useSSL=false jdbc.username=root jdbc.password=123456这里三个参数必须解释清楚:useUnicode=true&characterEncoding=UTF-8是解决中文乱码的钥匙,去掉useSSL=false的话 MySQL 8 会频繁告警性能下降。C3P0 的配置文件放在 src/c3p0-config.xml 里:
<c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/dormitory</property> <property name="user">root</property> <property name="password">123456</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> </c3p0-config>initialPoolSize 是启动时创建的连接数,maxPoolSize 是最大连接数,如果并发访问不高,maxPoolSize 设 20 足够了。设太大反而浪费资源,设太小高峰期会频繁等待连接。
4. 核心功能拆解:学生增删改查、宿舍分配与 EmailUtil 源码逻辑
整个系统说到底就三件事:人、房、通知。学生管理管人,宿舍分配管房,EmailUtil 管通知。把这三个模块看透,你就能答上毕设答辩里八成的问题。
4.1 学生管理模块:从 StudentAddServlet 到 StudentDelListServlet 的完整链路
上一章贴了添加学生的核心代码,这里补上查询和删除。查询逻辑是查全部学生还是按条件查,取决于页面有没有传搜索关键字。典型的查询链路是StudentSelectServlet接收 keyword 参数,拼接 SQL 时用 LIKE 模糊匹配:
String keyword = request.getParameter("keyword"); if (keyword != null && !keyword.isEmpty()) { sql += " WHERE stu_name LIKE ? OR stu_no LIKE ?"; ps.setString(1, "%" + keyword + "%"); ps.setString(2, "%" + keyword + "%"); }注意 LIKE 的百分号是在 setString 里拼接的,不是拼在 SQL 字符串里——这样能避免 SQL 注入。很多初学者习惯写成"LIKE '%" + keyword + "%'",一旦 keyword 里有单引号就翻车。删除模块用的是上一章说的getParameterValues批量删除,删除成功后记得带上重定向,避免刷新页面时重复提交。
4.2 宿舍分配逻辑:DormSelectServlet 与 StudentBean 的关联查询
宿舍和学生的关系是典型的多对一:一个宿舍住多个学生,一个学生只属于一个宿舍。在表设计上,学生表里放一个 dormId 外键。DormSelectServlet 的核心工作就是把这个关联查出来:
public List<DormBean> selectAllDorm() { String sql = "SELECT d.*, COUNT(s.id) AS student_count " + "FROM dorm d LEFT JOIN student s ON d.id = s.dorm_id " + "GROUP BY d.id"; }LEFT JOIN 而不是 INNER JOIN 的真正含义是:没分配到学生的空宿舍也会出现在列表里,只是 count 为 0。如果你用 INNER JOIN,空宿舍就被过滤掉了,而实际上宿舍管理里恰恰需要看到哪些宿舍是空的才能分配给新生。查询出来的 student_count 会作为辅助字段填到 DormBean 里,在 JSP 页面直接显示"已住 3/6 人"。这种聚合查询是管理信息系统的标配,答答辩的时候如果能主动说出"空宿舍也需要展示所以用 LEFT JOIN",老师印象分会好很多。
4.3 EmailUtil 里的 JavaMail 参数:host、port、auth 的真实含义
这个项目里出现 EmailUtil.class 说明做了一半邮件通知功能。JavaMail 的核心就是配置一个会话然后发送 MimeMessage:
Properties props = new Properties(); props.put("mail.smtp.host", "smtp.qq.com"); props.put("mail.smtp.port", "465"); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.ssl.enable", "true"); Session session = Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("sender@qq.com", "授权码"); } });注意这个"授权码"不是 QQ 邮箱的登录密码,而是开启 SMTP 服务后生成的专用密码。auth必须为 true,否则服务器拒绝匿名发送,ssl.enable打开后端口用 465。如果用的是网易邮箱,端口通常是 465 或 994,需要自己去邮箱设置里看 SMTP 服务说明。这个类对于一个课程设计而言不是必须功能,但如果实现了,展示出来的加分效果很直观。
5. 避坑手记:JSP+Servlet 项目最常见的 6 个翻车现场
这些坑我基本全踩过,每个都按"现象 → 原因 → 解决"写清楚。你照着排查,省下的时间至少够重写一个模块。
5.1 现象:项目部署到 Tomcat 10 直接 404,控制台 ClassNotFoundException
原因:Tomcat 10 的 Servlet API 包名从 javax.servlet 换成了 jakarta.servlet,老项目的 import 语句找不到类。 解决:去 Tomcat 官网下载 9.0.x 版本,或者全局搜索替换import javax.servlet为import jakarta.servlet。更稳妥的做法是直接用 Tomcat 9,因为替换 import 可能漏掉 web.xml 里的 schema 引用。
5.2 现象:页面中文全部变成???或乱码
原因:三个地方的编码不统一——JSP 页面编码、Servlet 的 request 编码、JDBC 连接 URL 的编码。只要有一处不是 UTF-8,中文就乱。 解决:JSP 文件头部写<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>,Servlet 里先执行request.setCharacterEncoding("UTF-8")再读参数,JDBC 连接 URL 加上characterEncoding=UTF-8。这三处设置全对上,问题必然消失。
5.3 现象:IDEA 里找不到 Artifacts 选项,Deployment 是空的
原因:项目没有识别成 Web 项目,IDEA 不知道 web 目录在哪。 解决:File → Project Structure → Facets点加号选 Web,在 Web Resource Directory 里指定项目的 webapp 或 WebContent 目录。然后Artifacts里重新点加号选 Web Application: Exploded,部署配置里才有东西可选。
5.4 现象:改了 JSP 代码刷新页面不生效
原因:IDEA 的 Tomcat 默认没有开启热部署。 解决:Run Configuration 里找到On frame deactivation,改成Update resources。意思是不管焦点什么时候离开 IDEA 窗口,都自动把改过的 JSP 推给 Tomcat。改 Java 代码仍然需要Update classes and resources或重启,因为类需要重新编译。
5.5 现象:添加学生时成功,查列表时查出来的 dormId 是 null
原因:DormBean 和 StudentBean 之间的关联查询没写好,或者添加学生时 dormId 没有从前端正确传过来。 解决:先在前端页面确认 select 的 name 是 dormId 而不是 id,再到 Servlet 里断点看request.getParameter("dormId")拿到什么值,最后检查 SQL 里是不是真的查了 dorm 关联表而只是 select 了 student 表。90% 的情况是参数名没对上。
5.6 现象:MySQL 8.0.33 连接时 PublicKeyRetrieval 报错
原因:MySQL 8 默认使用 caching_sha2_password 认证,客户端需要服务端公钥来加密密码。 解决:JDBC URL 后面追加allowPublicKeyRetrieval=true&useSSL=false。如果用的是 5.7,这条可以忽略。要记住 5.7 和 8.0 的驱动类名也不同,8.0 是com.mysql.cj.jdbc.Driver,别拿 5.7 的老驱动硬跑 8.0。
6. 从"能跑"到"能答辩":三个立竿见影的代码优化
项目能跑只是及格线,答辩想拿高分还得再走三步。这三步改动量都不大,但每一处都能变成答辩时的加分讲点。
第一步,用 Filter 统一处理编码和登录校验。写一个 CharacterEncodingFilter 实现javax.servlet.Filter,在 web.xml 里映射/*,这样所有 Servlet 都自动过滤编码问题,就不用每个 doPost 开头手动 setCharacterEncoding 了。再写一个 LoginFilter,校验 session 里有没有登录标记,没有就重定向到 login.jsp。这两个 Filter 的代码加一起不到 30 行,但能体现你对"横切关注点"的理解。
第二步,把 DAO 里的 JDBC 样板代码抽到 BaseDAO。Connection、PreparedStatement、ResultSet 的关闭操作重复度太高,写模板方法封装起来:
public abstract class BaseDAO { protected Object querySingle(String sql, Object[] params, RowMapper mapper) { try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } ResultSet rs = ps.executeQuery(); if (rs.next()) { return mapper.mapRow(rs); } } catch (Exception e) { throw new RuntimeException("查询失败", e); } return null; } }RowMapper 是一个函数式接口,具体怎么把 ResultSet 变成对象由调用方决定。这样 StudentDaoImpl 里只需关注 SQL 和字段映射,不再重复写 15 行连接代码,改动后的 DAO 可读性高一个档次。
第三步,给一个 JSP 页面加一版 Ajax 交互。JSP 时代最常见的交互是表单提交后整页刷新,但如果你在 JSP 里写一段 JavaScript,用fetch或 jQuery 的$.get请求一个返回 JSON 的 Servlet,然后局部更新页面,这就能在答辩时直接说"我了解前后端分离的基本思路"。不需要把整个系统改成前后端分离,一个学生查询页就足以作为亮点展示。
从那以后我每次拿到这种课程设计项目,都会强制自己先跑一遍 javap 看清楚结构再动手部署,这半小时的投入能让后面所有调试顺畅不少。希望帮到你。
本文还有配套的精品资源,点击获取