JavaWeb项目完整案例:Servlet+JSP+MySQL学生宿舍管理系统实战

发布时间:2026/10/8 23:30:29
JavaWeb项目完整案例:Servlet+JSP+MySQL学生宿舍管理系统实战 简介这是一套面向JavaWeb初学者与课程设计需求的完整学生宿舍管理系统源码采用ServletJSPMySQL技术栈实现适合正在学习JavaWeb开发、需要完成毕业设计或课程作业的学生参考。压缩包共295个文件约16.88MB包含30个Java源文件与30个class编译文件覆盖登录、宿舍、楼宇、管理员、学生等核心业务Servlet另有13个JSP页面、40个CSS样式、10个JS脚本及111张PNG图片配合25个jar依赖包与1个SQL建库脚本前端资源与后端逻辑齐备。资源还附带数据库文件与项目配置文件目录结构清晰便于直接导入运行与二次开发。目前已有5921人学习下载读者可借此理解Servlet分层设计、BaseDao封装与验证码工具类的实现思路快速搭建可运行的宿舍管理项目并在此基础上完成功能扩展与代码调试。1. 从零手写 ServletJSPMySQL 学生宿舍管理系统这套老技术栈为什么还值得做如果你正在搜「javaweb 项目完整案例 mysql」大概率不是想听 Spring Boot 有多香而是手里有个课程设计、毕设或者转行练手项目要求用 ServletJSPMySQL 做一个学生宿舍管理系统。这套组合看着老但它恰恰是理解 JavaWeb 请求链路最直白的入口浏览器发请求Tomcat 把请求交给 ServletServlet 查 MySQL把结果塞进 request转发给 JSP 渲染成 HTML。整条链路没有注解魔法没有自动装配每一层你都得自己接上。学生宿舍管理系统这个场景也选得好。它不像电商那样动辄高并发业务边界清晰楼栋、房间、床位、学生、入住退宿、报修、访客登记。表关系能画出典型的一对多和多对一正好练手 JDBC 和事务。适合谁适合刚学完 Java 语法、想搞明白「一个网页到底怎么从数据库里把数据捞出来」的人也适合被框架惯坏、想回头补基础的人。下面我按真实开发顺序把选型、建库、Servlet 写法、JSP 渲染和踩坑一次讲透。2. 环境与选型Tomcat、JDK、MySQL 版本怎么搭才不返工2.1 为什么这套项目仍然用 Servlet 3.x JSP而不是直接上框架先说清楚选型理由不然做到一半你会怀疑人生。ServletJSP 的核心价值是「显式」请求映射写在web.xml或WebServlet注解里数据库连接自己管页面跳转自己控制。这对学习者意味着每一步都可追踪出错时你能定位到是 Servlet 没匹配上、SQL 写错了还是 JSP 表达式取不到值。版本上我一般这样配JDK 用 8 或 11Tomcat 用 8.5 或 9MySQL 用 5.7 或 8.0。注意 Tomcat 10 之后包名从javax.servlet变成了jakarta.servlet网上大量老教程还是javax你如果下了 Tomcat 10 却抄javax的代码会直接编译不过。这是新手第一个大坑后面避坑章节还会细说。MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver连接串要带时区和 SSL 参数5.7 则是com.mysql.jdbc.Driver两者不能混。提示如果你搜到的是「mysql 5.7.44 安装过程详细」这类内容注意 5.7 系列已停止官方维护新项目建议直接上 8.0本文示例以 8.0 为准5.7 只需改驱动类名和连接串。2.2 建库建表宿舍管理系统的六张核心表数据库设计决定了后面代码好不好写。宿舍管理最小可用模型我建议六张表楼栋dorm_building、房间dorm_room、学生student、床位分配bed_allocation、报修repair_order、管理员admin_user。核心关系是一栋楼有多个房间一个房间有多个床位一个学生占一个床位。-- 建库字符集用 utf8mb4避免中文和 emoji 乱码 CREATE DATABASE dorm_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dorm_db; -- 楼栋表 CREATE TABLE dorm_building ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 楼栋名如1号楼, floors INT NOT NULL DEFAULT 6 COMMENT 楼层数 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 房间表building_id 外键指向楼栋 CREATE TABLE dorm_room ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL, room_no VARCHAR(20) NOT NULL COMMENT 房间号如301, capacity INT NOT NULL DEFAULT 4 COMMENT 可住人数, INDEX idx_building (building_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(30) NOT NULL, gender TINYINT NOT NULL DEFAULT 1 COMMENT 1男2女, phone VARCHAR(20) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 床位分配表一个学生同一时间只能有一条有效记录 CREATE TABLE bed_allocation ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, room_id INT NOT NULL, bed_no INT NOT NULL COMMENT 床位号, check_in DATE NOT NULL, check_out DATE DEFAULT NULL COMMENT 为空表示在住, INDEX idx_student (student_id), INDEX idx_room (room_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时几个参数值得说utf8mb4而不是utf8因为 MySQL 的utf8其实是三字节存不全InnoDB引擎支持事务和外键退宿这种要同时改多张表的操作必须用它bed_allocation里用check_out是否为空来判断在住状态比单独加状态字段更省事但查询时要记得带上check_out IS NULL条件否则会把历史记录也算进去。2.3 IDEA 里跑通第一个 Servlet从配置到访问环境搭好后先在 IDEA 里建一个 Web 项目。常见做法是 New Project 选 Java Enterprise勾选 Web Application指定 Tomcat 目录。如果没有这个选项就建普通 Maven 项目打包方式设为war手动加 servlet-api 依赖。!-- pom.xml 关键依赖scope 必须是 provided因为 Tomcat 自带 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependencyprovided这个 scope 是血泪经验如果你写成默认的compileservlet-api 会被打进 war 包部署到 Tomcat 时和容器自带的版本冲突启动就报ClassCastException或者类找不到。写完依赖后写第一个 ServletWebServlet(/hello) public class HelloServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setAttribute(msg, 宿舍管理系统启动成功); // 转发到 JSP由 JSP 负责渲染 req.getRequestDispatcher(/hello.jsp).forward(req, resp); } }WebServlet(/hello)把 URL 和这个类绑定访问http://localhost:8080/项目名/hello就会进doGet。这里用forward而不是sendRedirect因为转发是服务器内部跳转request 里的msg能带到 JSP重定向是两次请求属性会丢。这是理解 Servlet 生命周期的关键一步后面所有列表页都靠这个模式。3. 用 JDBC 打通 Servlet 与 MySQL连接、查询、事务一个不落3.1 数据库连接工具类别在每个 Servlet 里重复写 DriverManager新手最容易犯的错是每个 Servlet 里都写一遍DriverManager.getConnection代码重复不说连接用完不关跑几次就报Too many connections。正确做法是抽一个工具类把驱动加载和连接获取封装起来。public class DBUtil { // 连接串8.0 必须带 serverTimezone否则时间字段会差8小时 private static final String URL jdbc:mysql://localhost:3306/dorm_db?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PWD 你的密码; static { try { // 8.0 驱动类名带 cj5.7 是 com.mysql.jdbc.Driver Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 统一关闭资源顺序是结果集-语句-连接 public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (SQLException ignored) {} try { if (s ! null) s.close(); } catch (SQLException ignored) {} try { if (c ! null) c.close(); } catch (SQLException ignored) {} } }连接串里serverTimezoneAsia/Shanghai是必须的MySQL 8.0 驱动不指定时区会抛The server time zone value is unrecognizeduseSSLfalse是本地开发省去证书配置生产环境要开allowPublicKeyRetrievaltrue配合 8.0 的加密插件不加可能报公钥检索失败。这几个参数就是搜「mysql ssl连接错误」的人最常卡住的地方。3.2 学生列表查询从 SQL 到 JSP 表格的完整链路有了工具类写一个学生列表功能。Servlet 负责查数据JSP 负责展示这是 MVC 的雏形。WebServlet(/student/list) public class StudentListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListStudent list new ArrayList(); String sql SELECT id, sno, name, gender, phone FROM student ORDER BY id DESC; try (Connection c DBUtil.getConnection(); PreparedStatement ps c.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setSno(rs.getString(sno)); s.setName(rs.getString(name)); s.setGender(rs.getInt(gender)); s.setPhone(rs.getString(phone)); list.add(s); } } catch (SQLException e) { throw new ServletException(查询学生列表失败, e); } req.setAttribute(students, list); req.getRequestDispatcher(/student/list.jsp).forward(req, resp); } }这里用try-with-resources自动关连接比手动 close 更稳。SQL 用PreparedStatement而不是Statement拼接一是防 SQL 注入二是预编译对重复查询更快。ORDER BY id DESC让最新录入的排前面这是列表页的常见需求。对应的 JSP% page contentTypetext/html;charsetUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trth学号/thth姓名/thth性别/thth电话/th/tr c:forEach items${students} vars tr td${s.sno}/td td${s.name}/td td${s.gender 1 ? 男 : 女}/td td${s.phone}/td /tr /c:forEach /tableJSTL 的c:forEach替代了 JSP 脚本片段% %页面更干净。注意contentType里的charsetUTF-8和数据库的utf8mb4要对应否则中文姓名会变问号。${s.gender 1 ? 男 : 女}用 EL 三元表达式做字典转换比在 Java 里转好再传更省事。3.3 入住与退宿一个必须用事务的场景分配床位和退宿是典型的多表操作。学生入住要往bed_allocation插一条记录同时要校验房间是否还有空床位退宿要更新check_out日期。如果插入成功但校验失败数据就脏了所以必须包在事务里。public boolean checkIn(int studentId, int roomId, int bedNo) throws SQLException { Connection c null; try { c DBUtil.getConnection(); c.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 校验该床位是否已被占用check_out 为空表示在住 String checkSql SELECT COUNT(*) FROM bed_allocation WHERE room_id? AND bed_no? AND check_out IS NULL; try (PreparedStatement ps c.prepareStatement(checkSql)) { ps.setInt(1, roomId); ps.setInt(2, bedNo); try (ResultSet rs ps.executeQuery()) { if (rs.next() rs.getInt(1) 0) { c.rollback(); return false; // 床位已占用 } } } // 2. 插入入住记录 String insertSql INSERT INTO bed_allocation(student_id,room_id,bed_no,check_in) VALUES(?,?,?,CURDATE()); try (PreparedStatement ps c.prepareStatement(insertSql)) { ps.setInt(1, studentId); ps.setInt(2, roomId); ps.setInt(3, bedNo); ps.executeUpdate(); } c.commit(); return true; } catch (SQLException e) { if (c ! null) c.rollback(); throw e; } finally { if (c ! null) { c.setAutoCommit(true); c.close(); } } }事务的三个关键点setAutoCommit(false)开启commit()提交异常时rollback()回滚。校验和插入必须在同一个连接里否则事务不生效。CURDATE()是 MySQL 函数取当天日期比在 Java 里格式化再传更简洁。退宿同理把UPDATE bed_allocation SET check_outCURDATE() WHERE student_id? AND check_out IS NULL包进事务即可。4. 避坑与排查ServletJSPMySQL 最容易翻车的五个地方4.1 中文乱码请求、响应、数据库三处都要设现象表单提交的中文姓名存进数据库变成???或者页面显示乱码。原因通常有三层请求体编码、响应编码、数据库连接编码。解决要三管齐下。请求侧在 Servlet 里第一行加req.setCharacterEncoding(UTF-8)注意这行必须在读取任何参数之前响应侧设resp.setContentType(text/html;charsetUTF-8)数据库侧连接串带characterEncodingutf8建库用utf8mb4。Tomcat 8 之后 GET 请求默认已是 UTF-8但 POST 表单不设就会乱这是最常见的翻车点。4.2 404 与 500先分清是路径问题还是代码异常现象访问页面报 404 或 500。404 是找不到资源多半是WebServlet的路径和访问 URL 对不上或者 JSP 放在WEB-INF下被直接访问了WEB-INF里的页面只能通过转发访问这是安全设计。500 是服务器内部错误看 Tomcat 控制台的堆栈最常见是NullPointerExceptionrequest 属性没设就取和SQLException连接串或 SQL 写错。排查顺序先看控制台完整堆栈定位到具体行再判断是路径还是逻辑。4.3 连接泄漏为什么跑一会儿就 Too many connections现象系统用一段时间后所有查询都卡住报连接数超限。原因是某个分支里Connection没关比如查询抛异常后直接 return跳过了 close。解决是用try-with-resources或在finally里关绝不把 close 写在 try 块末尾。更彻底的做法是引入连接池但练手阶段先把手动关闭写对。判断有没有泄漏可以在 MySQL 里执行SHOW PROCESSLIST看连接数是否只增不减。4.4 Tomcat 10 的 javax 到 jakarta 迁移现象代码在别人机器上能跑你这一编译就报package javax.servlet does not exist。原因是 Tomcat 10 起把 Servlet API 的包名从javax.servlet改成了jakarta.servlet。解决有两条路要么把 Tomcat 降到 9继续用javax要么把所有 import 和依赖换成jakarta版本。新手建议直接用 Tomcat 9避免和网上教程对不上。这个坑搜「idea运行javaweb项目配置」的人十有八九会遇到。4.5 JSP 里取不到 request 属性转发和重定向别搞混现象Servlet 里明明setAttribute了JSP 里${students}却是空。原因多半是用了sendRedirect而不是forward。重定向是浏览器发起的第二次请求request 对象是新的属性自然丢了。解决是把resp.sendRedirect(...)改成req.getRequestDispatcher(...).forward(req, resp)。如果确实需要重定向比如防止表单重复提交那就把数据存 session 或走 URL 参数别指望 request 能跨重定向。5. 进阶技巧用过滤器统一编码、用分页扛住数据量5.1 一个 CharacterEncodingFilter 干掉所有乱码每个 Servlet 都写setCharacterEncoding太啰嗦用过滤器统一处理。过滤器在请求到达 Servlet 之前执行正好做编码设置。WebFilter(/*) // 拦截所有请求 public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); // 放行交给下一个过滤器或Servlet } }WebFilter(/*)里的/*表示拦截所有路径chain.doFilter是放行。这样所有请求的编码一次性搞定新增 Servlet 不用再操心。注意过滤器的执行顺序按web.xml里的配置或注解扫描顺序多个过滤器时编码过滤器要放最前。5.2 列表分页LIMIT 加 COUNT 的标准写法学生多了以后列表页一次查全部会卡必须分页。MySQL 用LIMIT offset, size配合一条COUNT(*)查总数。public PageResultStudent page(int pageNo, int pageSize) throws SQLException { int offset (pageNo - 1) * pageSize; // 页码从1开始偏移量要减1 String countSql SELECT COUNT(*) FROM student; String dataSql SELECT * FROM student ORDER BY id DESC LIMIT ?, ?; try (Connection c DBUtil.getConnection()) { int total; try (PreparedStatement ps c.prepareStatement(countSql); ResultSet rs ps.executeQuery()) { rs.next(); total rs.getInt(1); } ListStudent list new ArrayList(); try (PreparedStatement ps c.prepareStatement(dataSql)) { ps.setInt(1, offset); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { // 映射逻辑同上略 } } } return new PageResult(list, total, pageNo, pageSize); } }offset的计算是(pageNo - 1) * pageSize这是分页最容易算错的地方页码从 1 开始但偏移从 0 开始。LIMIT的两个参数用?占位注意 MySQL 的LIMIT占位符在旧版本驱动里可能不支持8.0 驱动没问题。总页数用total / pageSize向上取整前端根据总页数渲染上一页下一页。5.3 验证清单上线前我会逐条过的检查项检查项合格标准常见问题编码请求/响应/库三处 UTF-8中文变问号连接关闭无泄漏PROCESSLIST 稳定Too many connectionsSQL 注入全部用 PreparedStatement拼接字符串事务多表操作包事务数据不一致路径转发用 forward防重复提交用 redirect属性丢失依赖 scopeservlet-api 为 provided启动冲突这张表我每次交付前都会过一遍尤其是连接关闭和事务这两项出问题最难查。分页和过滤器是这套系统的进阶点做完这两个你对 JavaWeb 的请求生命周期就算真正摸透了。我自己带人做这个项目时最深的体会是别急着上框架先把 Servlet 的doGet、forward、PreparedStatement这三样写熟后面学 Spring MVC 时你会发现它只是把这些封装了一层。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询