JSP+Servlet+MySQL学生宿舍管理系统课设实战:三层架构与功能实现

发布时间:2026/10/6 8:13:21
JSP+Servlet+MySQL学生宿舍管理系统课设实战:三层架构与功能实现 简介面向高校Java Web实训课程这份JSPMySQL学生宿舍管理系统源码包适合正在完成课程设计或需要参考完整增删改查项目的学生。系统覆盖宿舍分配、住宿费管理、报修处理、统计报表等典型模块展示JSP页面、Servlet、JavaBean与MySQL数据库的协作方式可直接部署学习或在此基础上扩展。压缩包共67个文件约5.55MB包含17个Java源文件与17个class文件、10个JSP页面、8个XML配置、7个Jar依赖包、1个SQL数据库脚本及1份docx作业文档pom.xml与iml文件便于识别项目结构src、web、sql、doc等目录划分清晰。已有69人学习下载。除了完整源代码附带文档说明了系统架构、数据库设计、功能模块、安装配置与运行步骤适合对照理解后端逻辑、理清数据表关系也可作为实训报告与答辩准备的参考。SQL脚本提供建库建表与初始数据可快速完成环境初始化结合web.xml可了解应用的基础配置。1. 实训选型为什么说JSPMySQL的学生宿舍管理系统是最稳的课设题实训周最常见的进度的这样的老师说要交一个管理系统你打开Eclipse建了个Java项目写了三天登录界面最后发现数据库还没连上连DBUtil是什么都不知道。这篇笔记要讲的基于JSPMySQL的学生宿舍管理系统就是这类课设的典型代表——它不算新胜在结构完整、覆盖面广JSP负责页面展示Servlet处理请求MySQL存业务数据再配上一份数据库脚本和设计文档新手能一步步跑通熟手能快速改造成自己的需求。它适合拿来交实训作业、期末课设也适合在学Spring Boot之前练一把手。接下来的内容不是让你抄源码而是把这条链路完整拆开告诉每一步为什么要这么写、参数怎么调、哪里最容易翻车。2. 看清骨架JSP、Servlet、MySQL这三层到底怎么分工2.1 为什么是JSP而不是Spring Boot课设场景的务实选择先回答很多同学纠结的问题2025年了为什么老师还在布置JSP的作业答案很现实——实训进度决定的。Java Web课程从Servlet讲到JSP再到JDBC结课时间刚好够布置一个管理系统Spring Boot虽然好用但实训周里硬上它等于给自己加戏配Maven、理解自动配置、处理打包部署这些都不是两周能消化的。JSPMySQL的核心价值在于它把Web开发三个基本功暴露得很清楚页面怎么展示数据JSP、请求怎么分发处理Servlet、数据怎么持久化MySQL加JDBC。没有框架替你遮住这些你反而能把“浏览器发请求→Servlet接住→DAO查数据库→结果返回页面”这条链路彻底看明白。这套底子打好了后面学Spring MVC、MyBatis的时候会发现那些框架不过是你现在手写代码的封装和托管。2.2 三层架构拆解JSP、Servlet、DAO的职责边界这个标题下的代码包结构最常见的分层是这样的src/com/xxx/entity实体类和数据库表字段一一映射字段名建议保持和表结构一致src/com/xxx/dao数据访问层手写SQL对外提供增删改查方法src/com/xxx/servlet控制层接收参数、调用DAO、把结果塞进request或sessionWebContent/jsp视图层只做展示不写业务逻辑WebContent/WEB-INF/web.xml配置Servlet映射、欢迎页面、编码过滤器这里有一个实训验收时很加分的约定JSP页面只从request和session里取数据用JSTL或表达式输出不直接new DAO。这么做的直接好处是以后想换成别的框架只需要动Servlet这一层视图和数据库都不受影响。很多同学的源码被老师说“像一锅粥”问题就出在JSP里嵌了大量Java代码——页面里com.mysql开头的import一出现分基本就要扣。2.3 源码里没明说但验收一定看的三个点第一是数据库脚本完不完整。一份合格的sql文件至少要包含建库、建表、默认管理员账号和一批带中文的测试数据。老师拿到项目后第一个动作往往是直接跑你的sql文件跑不通的话后面代码都懒得看。第二是连接方式够不够讲究。课设用DriverManager就能跑但简历上写“使用Druid连接池”明显更拿得出手。连接池本质上就是帮你管理Connection的创建和复用避免每次请求都new一个连接第3章我会给接入写法。第三是文档有没有认真写。那份设计文档不是凑字数的老师往往先翻文档后看代码。文档里的ER图、用例表、数据字典比代码本身更能说明你“做了设计”。哪怕代码是参考的文档写成自己的理解答辩时也敢讲。3. 环境搭到能跑JDK、Tomcat、MySQL 8.0的安装与连通3.1 MySQL 8.0安装与初始化两个必勾的选项关于mysql安装教程网上很多但针对这个项目安装MySQL 8.0时有两点和课设强相关选错后面会多踩一小时坑。第一是字符集。安装到最后一步选Character Set时别用默认的utf8mb4吗恰恰相反就要选utf8mb4。MySQL里的utf8是旧的utf8mb3存emoji和生僻字会报错而学生填姓名时总有人名字里带很生僻的字。项目里所有数据库、表、连接URL统一utf8mb4中文乱码问题能少一半。第二是认证插件。MySQL 8.0默认的认证插件是caching_sha2_password如果你的JDBC驱动是5.x或者用了老版本Navicat连接时会直接报密码错误或连不上。解决办法是安装时选择Legacy Authentication兼容老版本或者安装后用命令行改掉root密码的插件。课设电脑上怎么省事怎么来选Legacy最稳。安装完成后建议顺手确认MySQL服务是手动还是自动启动。实训教室的电脑经常重启如果你设成手动第二天开机连接数据库报错第一反应往往是“代码坏了”其实是服务没起来。3.2 JDK、Tomcat、Eclipse的版本搭配与动态Web项目JDK建议用8或11Tomcat用9.xEclipse用Enterprise Java and Web版本这三个版本组合最省心。JDK版本太高比如17配老Tomcat会有模块访问报错Tomcat版本太旧配新版JDK启动直接失败。新建项目时选Dynamic Web Project注意三个配置Target runtime选你本机装好的TomcatDynamic web module version选3.1或4.0勾选Generate web.xml deployment descriptor因为后面要手动配置Servlet映射项目建好后把mysql-connector-java的jar包放到WebContent/WEB-INF/lib目录下这一步是初学者最容易漏的。放到别的地方哪怕写了Class.forName运行时会报ClassNotFoundException因为Tomcat只加载WEB-INF/lib下的jar。3.3 数据库连通测试JDBC连接工具类与URL参数写DAO之前先把数据库连通性搞定。下面这段就是项目里最核心的DBUtil工具类复用它就能让所有DAO免去重复创建连接的代码package com.sushe.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBUtil { // 连接串里的每个参数都有讲究见下方参数说明 private static final String URL jdbc:mysql://localhost:3306/dormitory ?useUnicodetruecharacterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { // MySQL 8.0 的驱动类名是 com.mysql.cj.jdbc.Driver Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败检查jar包是否放在WEB-INF/lib下); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { // 关闭顺序必须与打开顺序相反ResultSet - PreparedStatement - Connection if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps ! null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }连接串里四个参数逐个说清楚。useUnicode和characterEncoding是保证中文进数据库不乱码前后端和数据库三层字符集必须一致。useSSLfalse是关闭SSL握手本地开发开着它除了拖慢连接速度没别的作用。serverTimezoneAsia/Shanghai是告诉驱动用东八区时间不然MySQL 8.0会拿服务器默认时区做时间换算日期字段可能差8小时。allowPublicKeyRetrievaltrue是配合caching_sha2_password插件的不加它连接时可能报Public Key Retrieval is not allowed这个报错在MySQL 8.0特别常见。USER和PASSWORD要改成你自己MySQL的账号密码。很多同学在这里直接把视频里的root/123456套进自己电脑结果连不上就怀疑代码其实是密码没改。写代码时建议先在main方法里打印一下connection对象能拿到连接对象再往下写DAO否则后面的所有操作都会在同一个坑里反复绊倒。4. 把核心功能写出来建表、登录、宿舍分配与报修的一条龙实现4.1 数据库表设计学生表、宿舍表、报修表的字段取舍学生宿舍管理系统逃不开的几张表学生表、宿舍表、报修表外加一个公告表用来装饰首页。建表SQL如下字段注释已经写清楚直接跑即可CREATE DATABASE IF NOT EXISTS dormitory DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dormitory; CREATE TABLE t_student ( stu_id VARCHAR(20) PRIMARY KEY COMMENT 学号, stu_name VARCHAR(50) NOT NULL COMMENT 姓名, password VARCHAR(64) NOT NULL COMMENT 密码建议存MD5哈希不存明文, sex CHAR(2) COMMENT 性别, dorm_id VARCHAR(10) COMMENT 宿舍编号为空表示未分配, phone VARCHAR(11) COMMENT 手机号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) ENGINEInnoDB COMMENT学生表; CREATE TABLE t_dorm ( dorm_id VARCHAR(10) PRIMARY KEY COMMENT 宿舍编号如A-101, building VARCHAR(20) COMMENT 楼栋名, room_no VARCHAR(10) COMMENT 房间号, capacity INT DEFAULT 4 COMMENT 可住人数, current_count INT DEFAULT 0 COMMENT 当前已住人数 ) ENGINEInnoDB COMMENT宿舍表; CREATE TABLE t_repair ( rep_id INT AUTO_INCREMENT PRIMARY KEY, stu_id VARCHAR(20) COMMENT 报修人学号, dorm_id VARCHAR(10) COMMENT 宿舍号, content VARCHAR(255) COMMENT 报修内容, status TINYINT DEFAULT 0 COMMENT 0待处理 1处理中 2已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 报修时间 ) ENGINEInnoDB COMMENT报修表;关于MySQL排序和查询的细节在表设计阶段就要想好。t_repair表按create_time排序展示时直接ORDER BY create_time DESC即可但如果有“按待处理优先级高”的需求就写成ORDER BY status ASC, create_time DESC让状态为0的记录排在前面。这个语法本身不难难的是把排序规则和业务逻辑对应上。字段设计有三个经验。一是密码字段长度别用20MD5是32位、SHA-256是64位直接给64最省事。二是宿舍表和学生的关联别用外键约束。课设里写外键看着规范但后期导入测试数据、修改宿舍编号时会被外键卡住实训场景里建议只保留逻辑关联不建物理外键。三是所有时间字段给默认值CURRENT_TIMESTAMP这样插入数据时少写一个字段也避免Java端时间格式和MySQL对不上。4.2 登录功能Servlet校验、Session会话与编码过滤器登录是所有管理系统的入口也是老师必看的部分。从数据库增删改查的角度看登录就是一个最简单的SELECT加条件查询。但要把登录写得规范至少涉及三个层面的处理。先写LoginServlet的代码骨架package com.sushe.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; import com.sushe.dao.StudentDao; import com.sushe.entity.Student; import com.sushe.util.MD5Util; public class LoginServlet extends HttpServlet { private static final long serialVersionUID 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 解决中文参数乱码必须在读取参数之前设置 request.setCharacterEncoding(UTF-8); // 2. 接收前端表单参数 String stuId request.getParameter(stuId); String password request.getParameter(password); // 3. 简单非空校验避免无效查询 if (stuId null || stuId.isEmpty() || password null || password.isEmpty()) { request.setAttribute(errorMsg, 学号和密码不能为空); request.getRequestDispatcher(/jsp/login.jsp).forward(request, response); return; } // 4. 调用DAO查询用户密码存的是MD5 StudentDao dao new StudentDao(); Student student dao.findByStuIdAndPassword(stuId, MD5Util.md5(password)); if (student ! null) { // 5. 登录成功写入Session跳转主页面 HttpSession session request.getSession(); session.setAttribute(loginUser, student); // 6. 设置会话超时时间为30分钟单位是秒 session.setMaxInactiveInterval(30 * 60); response.sendRedirect(request.getContextPath() /jsp/index.jsp); } else { // 7. 登录失败回传错误信息转发回登录页 request.setAttribute(errorMsg, 学号或密码错误); request.getRequestDispatcher(/jsp/login.jsp).forward(request, response); } } }这段代码里有三个细节容易被忽略。第一是request.setCharacterEncoding必须在读取任何参数之前调用放在doPost第一行是规矩放后面就失效了。第二是登录失败用forward转发而不是sendRedirect因为需要把errorMsg带过去登录成功用sendRedirect重定向是为了防止用户刷新页面时重复提交表单。第三是Session只是简单的键值对只要有对象不需要校验客户端IP课设做到这个程度已经够了。DAO层的findByStuIdAndPassword方法用PreparedStatement而不是Statementpublic Student findByStuIdAndPassword(String stuId, String md5Password) { String sql SELECT * FROM t_student WHERE stu_id? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stuId); ps.setString(2, md5Password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Student s new Student(); s.setStuId(rs.getString(stu_id)); s.setStuName(rs.getString(stu_name)); s.setDormId(rs.getString(dorm_id)); s.setPhone(rs.getString(phone)); return s; } } } catch (Exception e) { e.printStackTrace(); } return null; }这里用PreparedStatement的原因不是方便而是安全。以前用Statement时把参数直接拼进SQL字符串stuId传一个1 OR 11登录就被绕过了。PreparedStatement先预编译再传参参数永远只是数据不会变成SQL结构。这也是“数据库sql注入”这个老话题里最基础也最关键的一道防线。登录页还有一个常被抽查的细节跨页面跳转后Session里的用户信息怎么在JSP上展示。比如首页顶部要显示“当前登录张三”JSP里直接取session即可span当前登录${sessionScope.loginUser.stuName}/span这种jsp个人信息展示页面的写法比用Java脚本片段干净也不会因为Servlet往request里塞的对象没同步而报错。4.3 宿舍分配与调换事务边界必须卡在“人数校验双表更新”宿舍管理里最有技术含量的一块是把一个学生分到某个宿舍。很多同学的写法是两步先UPDATE t_dorm的current_count加1再UPDATE t_student的dorm_id。这两步之间只要程序中途出错就会出现宿舍人数加了但学生表没更新或者反过来学生有宿舍了但计数没变。正确的做法是把两步包进同一个事务里要么都成功要么都回滚。下面这段代码是宿舍分配的核心逻辑public boolean assignDorm(String stuId, String dormId) { Connection conn null; try { conn DBUtil.getConnection(); // 1. 关闭自动提交开启手动事务 conn.setAutoCommit(false); // 2. 查询宿舍当前人数FOR UPDATE加行级锁防止并发时两人同时住进最后一个床位 String querySql SELECT current_count, capacity FROM t_dorm WHERE dorm_id? FOR UPDATE; try (PreparedStatement ps conn.prepareStatement(querySql)) { ps.setString(1, dormId); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { int current rs.getInt(current_count); int capacity rs.getInt(capacity); if (current capacity) { conn.rollback(); return false; // 宿舍已满 } } else { conn.rollback(); return false; // 宿舍不存在 } } } // 3. 宿舍人数加1 String updateDorm UPDATE t_dorm SET current_count current_count 1 WHERE dorm_id?; try (PreparedStatement ps conn.prepareStatement(updateDorm)) { ps.setString(1, dormId); ps.executeUpdate(); } // 4. 学生表绑定宿舍 String updateStudent UPDATE t_student SET dorm_id? WHERE stu_id?; try (PreparedStatement ps conn.prepareStatement(updateStudent)) { ps.setString(1, dormId); ps.setString(2, stuId); ps.executeUpdate(); } // 5. 全部成功提交事务 conn.commit(); return true; } catch (Exception e) { // 6. 任何一步出错回滚到事务开始前的状态 if (conn ! null) { try { conn.rollback(); } catch (Exception ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { // 7. 恢复自动提交并关闭连接 if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (Exception e) { e.printStackTrace(); } } } }这段代码要理解的不是语法本身而是“事务边界”的思维方式。setAutoCommit(false)一旦开启后面所有SQL都在一个事务里commit之前任何一步异常rollback就能撤销全部修改。FOR UPDATE是给宿舍记录加锁如果两个管理员同时分配同一个宿舍的最后两个床位行锁会让第二个请求等待第一个提交后再执行重新读到最新的人数这个细节在并发演示时很加分。这样做也能避免数据库事务处理课上讲过的脏读和更新丢失。实训演示时老师如果问“为什么提交失败数据没有乱”可以直接回答事务回滚了这两张表始终一致。报修功能相对简单单表INSERT即可。但有一个实操细节报修内容content字段是用户在文本域里输入的可能存在英文单引号。用PreparedStatement的setString传参时单引号会被自动转义不需要手动替换这也是为什么前面的挖坑经验里强调一定要用PreparedStatement。5. 常见问题排查学生宿舍管理系统跑不起来的5个经典原因5.1 JSP页面中文乱码三个位置必须都是UTF-8现象页面显示一堆问号或者数据库里查出来的中文变成了“???”但英文数字正常。原因字符集三层不一致。JSP文件本身的编码、Servlet接收请求时的编码、数据库连接串里的编码这三层只要有一层掉队中文就在中间某一段被错误转码。解决JSP文件头写pageEncodingUTF-8Servlet在doGet和doPost开头都加request.setCharacterEncoding(UTF-8)连接串里保留characterEncodingutf8建库时用utf8mb4。建议再用一个Filter兜底把所有请求的编码统一成UTF-8这样后续加Servlet不需要重复写。检查乱码时按“页面→Servlet→数据库”的顺序逐层打印中文看哪一层开始变样比瞎改要快。5.2 MySQL 8.0连接失败Public Key Retrieval is not allowed现象运行程序时控制台报Public Key Retrieval is not allowed或者Client does not support authentication protocol requested by server。前者多为连接串缺参数后者多为驱动版本或者认证插件不兼容。原因MySQL 8.0默认使用caching_sha2_password插件老版本JDBC驱动和这个插件握手时需要额外的公钥获取步骤。解决连接串里加allowPublicKeyRetrievaltrue同时确认用的是mysql-connector-java 8.x版本。如果驱动类名写的是com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver老驱动类在8.x里还会给警告但已经不推荐用。还没有解决就回到第3章把MySQL的认证方式改成Legacy一劳永逸。5.3 Tomcat端口被占用8080起不来现象Tomcat启动日志报Port 8080 required by Tomcat v9.0 Server at localhost is already in use或者启动后浏览器访问localhost:8080提示404。原因上一次开发时Tomcat没有正常停止或者系统里另一个Java进程占了8080端口。解决Windows下先执行netstat -ano | findstr 8080查到占用端口的PID再打开任务管理器结束对应进程。如果这个端口总被占干脆在server.xml里把HTTP端口换成9090或8081一劳永逸。注意改端口后访问地址全部跟着变如果代码里有写死8080的跳转也要一起改。5.4 JSP页面图片和CSS路径全挂坐标定位的经典翻车现象登录页能打开但img标签的小图标裂开CSS样式完全没有点击跳转后URL路径多了一层导致页面找不到静态资源。原因这是JSP页面最常见的路径基准问题。用/jsp/login.jsp直接访问时相对路径images/logo.png解析正确但从Servlet转发到该页面时浏览器地址栏停留在/login这个虚拟路径上相对路径变成了/images/logo.png自然就404了。这也牵扯到jsp图片如何对坐标定位的典型场景——不管图片放在页面什么位置src路径基准都是页面URL不是文件位置。解决所有静态资源路径都写绝对路径img src${pageContext.request.contextPath}/images/logo.png。这个写法在JSP里适用所有href、src、action属性contextPath会自动带上项目名任何来源的跳转路径都不会错。检查途径也简单浏览器F12找到Failed to load resource看URL路径是否少了项目名。5.5 数据操作后页面不刷新要么重定向要么手动刷新现象新增一条报修记录后跳回列表页看不到新数据要手动刷新一次才出现。这是jsp页面加载后没有自动刷新的典型表现。原因新增操作用的是forward转发请求里带的是旧的列表数据浏览器地址没有变化页面停在旧的内存视图上。解决表单提交完成之后凡是涉及数据变化的操作都用sendRedirect重定向到列表页让浏览器重新发起一次全新请求。比如新增报修成功后response.sendRedirect(request.getContextPath() /repair/list)。记住一个原则新增、修改、删除之后必须重定向否则按刷新按钮就会重复提交一条数据。6. 验收前的最后一小时部署演示与数据备份的实用技巧实训演示最怕的不是代码写错而是当场环境出问题。这里说一个我养成的习惯演示之前用命令行把数据库完整导出一次并确认备份文件能恢复。导出命令如下mysqldump -uroot -p123456 --default-character-setutf8mb4 dormitory dormitory_backup.sql恢复时执行mysql -uroot -p123456 --default-character-setutf8mb4 dormitory dormitory_backup.sql恢复报错就删库重来先mysql进入后用DROP DATABASE dormitory再回到第4章的建表SQL重建。备份文件里因为有INSERT语句执行时间可能超过课设其它脚本这是正常的。部署到服务器时把项目整个导出成war包放到Tomcat的webapps目录下启动Tomcat即可自动解压。war包在Eclipse里右键项目Export选WAR file生成。服务器上的MySQL密码和本机不同时记得同步修改DBUtil里的配置。连接串里的localhost要不要改取决于MySQL是不是装在同一台机器上。演示前按这个清单过一遍管理员默认账号能否登录宿舍分配后学生列表是否联动更新重复学号是否被拦截报修流程能否走完三个状态数据库脚本从零重建是否一次通过。每一项都是老师爱问的点也是系统容易翻车的暗礁。特别是重复学号如果表结构里没设主键约束就会写进两条同样的学号答辩时被问到“数据冗余怎么办”只能干瞪眼。回到最开始说的JSPMySQL的课设项目真正的分水岭不在于代码写得有多炫而在于你对自己写的东西有没有完整掌控。我当年实训时帮室友连数据库改了他MySQL的认证方式导致他后面所有项目都连不上从此就养成把所有版本和密码写进README的习惯。哪个版本对不上、哪个配置改了会有连带影响全都写清楚反而比代码本身更有价值。希望这篇笔记能帮你少走几步弯路把实训周过得从容一点。本文还有配套的精品资源点击获取