首页/新闻资讯/正文详情

Java+JSP+MySQL学生宿舍管理系统:从环境搭建到部署避坑全解析

发布时间:2026/9/25 7:33:50 来源:云帆数科 栏目:资讯中心
Java+JSP+MySQL学生宿舍管理系统:从环境搭建到部署避坑全解析
简介Java Web开发中JSP/Servlet与MySQL的组合是理解服务端动态网页技术的基础。其核心原理在于浏览器请求经Tomcat容器路由至Servlet业务逻辑通过JDBC访问MySQL最终由JSP渲染响应页面。这种分层架构虽然“传统”却清晰展示了请求分发、数据持久化、事务边界等关键机制能帮助开发者快速理解Spring Boot的自动配置与MVC模式。基于此技术栈构建的学生宿舍管理系统涵盖楼栋、床位、入住退宿、访客登记等典型管理场景正是Java课程设计、毕业设计选题及内部管理系统的高频需求。本文从环境选型、数据库设计、连接池配置到并发分配事务控制系统梳理了开发全流程与避坑要点是快速上手的实用参考。1. JavaJSPMysql实现Web学生宿舍管理系统一个看着过时却不过时的技术组合第一次听到“JavaJSPMysql实现Web学生宿舍管理系统”这个组合很多人的第一反应是“都什么年代了还用JSP”。但你如果正在准备java课程设计案例源码、做基于jsp的毕设选题或者在简历上还写着“熟悉Java Web开发”那这套技术栈恰恰是绕不开的一课。JSP页面里嵌Java代码、Servlet接请求、Mysql存数据这是2008年左右互联网公司的主流玩法也是今天理解Spring Boot“自动配置到底帮我们做了什么”的最短路径。这套系统能解决的实际问题是宿舍楼栋管理、学生入住退宿、床位分配、访客登记这类日常事务数据量不大业务逻辑直白但足够把Java Web开发的知识串成一条线。适合谁读Java基础刚学完、想做一个能跑通前后端交互项目的学生以及想快速搭内部管理系统的后端开发。下面按我自己搭建这套系统的顺序来讲从环境选型一直讲到部署避坑。2. Java JSP Mysql技术选型为什么这套组合仍是Java Web入门最快的路2.1 三个组件的分工与版本选型先把这个标题里的三个技术名词拆开搞清它们各自干什么活。Java负责写业务逻辑比如计算宿舍剩余床位、判断学生是否重复入住JSPJava Server Pages负责生成动态网页本质上是一个“在HTML里嵌入Java代码”的模板引擎它会被Tomcat翻译成一个Servlet再编译执行Mysql负责持久化把学生信息、宿舍分配记录这些数据落盘Web应用通过JDBC驱动访问它。三者合起来就是一条完整的请求链浏览器发出HTTP请求Tomcat容器找到对应的ServletServlet调用Java业务代码业务代码通过JDBC读写Mysql最后把结果转发给JSP页面渲染成HTML返回浏览器。版本选型是第一个容易翻车的地方。我一般推荐这套组合JDK 8 Tomcat 8.5 Mysql 5.7或8.0 IDEA。JDK 8不是因为它新而是因为Tomcat 8.5和大量传统JSP项目都以JDK 8为基准环境你按8的语法写放到11上大概率也能跑反过来就不一定。Tomcat版本是个大坑Tomcat 10把Servlet API的包名从javax.servlet改成了jakarta.servlet传统的JSP项目直接部署上去会报ClassNotFoundException所以做JSP项目老老实实用Tomcat 8.5或9.0。Mysql版本上5.7和8.0都行但连接驱动类和连接URL参数有差异后面避坑章节会细说。组件推荐版本理由JDK1.8Tomcat 8.5兼容性最好市场存量项目最多Tomcat8.5 / 9.0Servlet API包名仍是javax传统JSP直接跑Mysql5.7 或 8.05.7最稳8.0需要额外处理认证和时区参数IDEA2023.xNew Project向导内置Java Enterprise模板Mysql驱动mysql-connector-java 5.1.49 或 8.0.33与Mysql版本对应8.x驱动类名已变更2.2 用IntelliJ IDEA新建一个JSP Web项目的完整配置打开IDEA新建项目时选择“Java Enterprise”左侧选“Web Application”注意不要选Spring Initializr那样会引入Spring Boot偏离标题讲的传统JSP路线。Server选Tomcat 8.5并指定本地Tomcat安装目录。勾选“Create web.xml”因为这个文件是传统JSP项目的描述符很多旧教程和代码都依赖它。项目生成后的结构长这样student-dormitory/ ├── pom.xml # Maven坐标与依赖如果用Maven ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/dormitory/ │ │ │ ├── servlet/ # Servlet控制层 │ │ │ ├── dao/ # 数据访问层 │ │ │ ├── entity/ # 实体类 │ │ │ └── util/ # JDBC工具类 │ │ ├── resources/ │ │ │ └── db.properties # 数据库连接配置 │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ └── web.xml # Web部署描述符 │ │ ├── jsp/ # JSP页面目录 │ │ └── index.jsp # 入口页面 │ └── test/ └── target/ # 编译输出与war包建立项目后第一步不是写代码而是先把Tomcat跑起来验证环境。在IDEA里配置好Tomcat后先启动一次空项目看到INFO: Server startup in [xxx] milliseconds再往下走。这一步能把“环境问题”和“代码问题”切分开不然后面报错你根本分不清是Tomcat没配置好还是代码写错了。我见过太多同学把时间耗在环境上最后发现只是Tomcat的端口被占了。# 也可以手动启动Tomcat验证环境避免IDEA配置干扰 cd /path/to/apache-tomcat-8.5.xx/bin ./startup.sh # Linux/Mac catalina.bat run # Windowsrun模式前台启动日志直接打在控制台 # 启动成功后浏览器访问 # http://localhost:8080/ 看到Tomcat默认首页即环境OK这里有两个参数值得留意。一是端口默认8080被占用时改conf/server.xml里的Connector port8080改成8081或8082都行但改了之后访问路径要跟着变。二是catalina.bat run和startup.sh的区别run是前台模式日志实时打印调试阶段用它startup是后台模式日志写到logs/catalina.out适合部署后看。新手调试阶段统一用前台模式日志里报错直接定位。3. 数据库设计与JDBC连接池宿舍管理系统的数据地基3.1 六张核心表的设计从学生、宿舍到访客登记学生宿舍管理系统的业务实体不难梳理管理员要登录学生要入住宿舍属于某栋楼入住之后可能换寝校外人员来访要登记。把这些实体落到Mysql里我一般拆成六张表管理员表admin、学生表student、楼栋表building、宿舍表dormitory、入住记录表assignment、访客登记表visitor。下面是建表SQL字段设计上兼顾了“够用”和“不过度设计”。CREATE DATABASE IF NOT EXISTS dormitory DEFAULT CHARACTER SET utf8mb4; USE dormitory; -- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(32) NOT NULL UNIQUE COMMENT 登录用户名, password VARCHAR(64) NOT NULL COMMENT 登录密码明文存储仅限学习项目, real_name VARCHAR(32) COMMENT 真实姓名, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT管理员表; -- 楼栋表 CREATE TABLE building ( id INT PRIMARY KEY AUTO_INCREMENT, building_name VARCHAR(32) NOT NULL COMMENT 楼栋名称如1号楼, manager VARCHAR(32) COMMENT 楼栋管理员姓名, floor_count INT DEFAULT 6 COMMENT 楼层数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT楼栋表; -- 宿舍表 CREATE TABLE dormitory ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL COMMENT 所属楼栋id, room_number VARCHAR(16) NOT NULL COMMENT 房间号如101, bed_count INT DEFAULT 4 COMMENT 床位总数, used_bed_count INT DEFAULT 0 COMMENT 已使用床位, UNIQUE KEY uk_building_room (building_id, room_number), CONSTRAINT fk_dorm_building FOREIGN KEY (building_id) REFERENCES building(id) ) ENGINEInnoDB COMMENT宿舍表; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(32) NOT NULL COMMENT 姓名, gender ENUM(男, 女) NOT NULL COMMENT 性别, phone VARCHAR(11) COMMENT 手机号, major VARCHAR(64) COMMENT 专业, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT学生表; -- 入住记录表 CREATE TABLE assignment ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT 学生id, dormitory_id INT NOT NULL COMMENT 宿舍id, bed_no INT NOT NULL COMMENT 床位号1~bed_count, assign_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 入住时间, status ENUM(入住中, 已退宿) DEFAULT 入住中 COMMENT 状态, UNIQUE KEY uk_student_status (student_id, status), -- 注意同一学生只能有一条入住中记录 CONSTRAINT fk_assign_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_assign_dorm FOREIGN KEY (dormitory_id) REFERENCES dormitory(id) ) ENGINEInnoDB COMMENT入住记录表; -- 访客登记表 CREATE TABLE visitor ( id INT PRIMARY KEY AUTO_INCREMENT, visit_name VARCHAR(32) NOT NULL COMMENT 访客姓名, visit_phone VARCHAR(11) COMMENT 访客电话, target_student_no VARCHAR(20) COMMENT 被访学生学号, visit_reason VARCHAR(128) COMMENT 来访事由, visit_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 登记时间, leave_time DATETIME NULL COMMENT 离开时间 ) ENGINEInnoDB COMMENT访客登记表;字段类型上几个值得考量的点。学号student_no用VARCHAR(20)而不是INT因为学号可能以0开头整型会丢掉前导零而且学号不需要参与数值计算。性别字段用ENUM(男, 女)看着直观但实际业务里如果后面要扩展性别选项ENUM改起来很痛苦更稳妥的写法是存编码0/1并在Java层做映射。assignment表里给(student_id, status)加了唯一索引这是宿舍分配的核心约束——防止同一个学生被分配两次还都处于“入住中”。这个索引会在第4章的业务代码里配合事务一起生效。宿舍表dormitory里的bed_count和used_bed_count是典型的“冗余计数”字段。另一个设计是每次分配时去数assignment表里入住中的记录数来决定剩余床位不需要冗余字段。但实际场景下宿舍列表页要显示“2/4人”这种信息每次都去COUNT一次数据量上来后查询会变慢。我倾向于保留冗余字段但必须在事务里同步更新它否则会出现“显示有床位但实际已满”的数据不一致。3.2 JDBC连接Mysql的两种方式与连接池关键参数JSP项目访问Mysql最原始的方式是DriverManager.getConnection(url, user, password)每次请求都新建一个物理连接。这种方式在教程demo里没问题但放到宿舍管理系统这种多人同时访问的场景下高频创建连接会让Mysql的进程数飙升响应变慢。更推荐的常见做法是引入连接池把连接创建好放在池子里复用。轻量级方案用Druid或HikariCP都行传统JSP项目里Druid用得最多配置直观而且带监控页面。# src/main/resources/druid.properties driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/dormitory?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password你的数据库密码 initialSize5 maxActive20 maxWait3000 minIdle2// util/JdbcUtil.java package com.dormitory.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; public class JdbcUtil { private static DataSource dataSource; static { try { InputStream in JdbcUtil.class.getClassLoader() .getResourceAsStream(druid.properties); Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(初始化Druid连接池失败, e); } } public static Connection getConnection() throws Exception { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (Exception ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (Exception ignored) {} } if (conn ! null) { try { conn.close(); } catch (Exception ignored) {} } } }druid.properties里这几个参数是必调的。initialSize是启动时就创建的连接数设成5够用maxActive是池子里最多同时活跃的连接数宿舍管理系统这种规模的Web应用20已经完全够用设太大反而浪费Mysql的内存maxWait是拿不到连接时的最长等待时间单位毫秒设3000表示等3秒还拿不到就抛异常避免请求无限挂起。连接URL里的serverTimezoneAsia/Shanghai是给Mysql 8.0用的不设会报时区错误allowPublicKeyRetrievaltrue是应对Mysql 8的caching_sha2_password认证插件的后面避坑章节会专门说。这里要理解一个关键点JdbcUtil里的conn.close()并不是真的断开数据库连接而是把连接归还给连接池。很多新手以为close就是关闭导致写代码时不敢close。在连接池模式下close的含义是“归还”所以DAO层用完连接必须调用close否则连接会被一直占用最终把maxActive耗光表现就是页面卡死然后报Cannot get connection。4. 从登录到宿舍分配一条完整的JSP Servlet Mysql请求链4.1 登录模块的MVC拆解JSP页面、Servlet控制层、DAO数据层搭好了环境和数据表接下来走通第一条完整链路登录。传统JSP项目最常见的分层是JSP做视图、Servlet做控制器、DAO做数据访问再加上一个实体类在层间传递数据。对于JSP入门来说网上有些案例把JDBC代码直接写在JSP页面里一两页能跑通但业务一复杂就变成一坨没人敢动的代码。我们按MVC路子来每层各干各的活后面加功能时好扩展。先看登录页面的JSP核心就是一个表单提交到LoginServlet。注意表单提交方式和字段名要和Servlet里getParameter的参数名对得上。%-- jsp/login.jsp --% % page contentTypetext/html;charsetUTF-8 languagejava % html head title学生宿舍管理系统 - 登录/title /head body h2学生宿舍管理系统/h2 form action${pageContext.request.contextPath}/login methodpost label用户名input typetext nameusername//labelbr/ label密 码input typepassword namepassword//labelbr/ button typesubmit登 录/button /form /body /html${pageContext.request.contextPath}是EL表达式用来拼项目上下文路径。比如项目部署名是dormitory那这段会被解析成/dormitory/login。这里建议用这个写法而不是写死/login因为项目部署名改掉时表单提交路径也得跟着改写死就踩坑。methodpost是为了避免密码出现在URL上也能避开URL长度限制。接下来是Servlet。这里用注解方式配置映射不需要在web.xml里逐个声明代码量更少。登录完成后把用户信息存进Session后续页面靠Session来判断是否已登录。// servlet/LoginServlet.java package com.dormitory.servlet; import com.dormitory.dao.AdminDao; import com.dormitory.entity.Admin; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private final AdminDao adminDao new AdminDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 设置请求编码解决中文乱码 request.setCharacterEncoding(UTF-8); // 2. 获取表单参数 String username request.getParameter(username); String password request.getParameter(password); // 3. 基础非空校验 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { request.setAttribute(errorMsg, 用户名和密码不能为空); request.getRequestDispatcher(/jsp/login.jsp).forward(request, response); return; } // 4. 调用DAO验证用户名密码 Admin admin adminDao.findByUsernameAndPassword(username, password); if (admin ! null) { HttpSession session request.getSession(); session.setAttribute(loginAdmin, admin); // 重定向到主页避免刷新时重复提交表单 response.sendRedirect(request.getContextPath() /jsp/index.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/jsp/login.jsp).forward(request, response); } } }这段代码里有三个细节值得展开。第一个是request.setCharacterEncoding(UTF-8)必须放在读取参数之前放在后面读取到的参数还是乱码。第二个是request.getRequestDispatcher(...).forward(...)和response.sendRedirect(...)的区别forward是服务器内部转发浏览器URL不变适合失败时回登录页并携带错误提示sendRedirect是302重定向浏览器重新发起一次请求适合登录成功切到主页防止用户按F5刷新时表单被重复提交。第三个是Session里存了loginAdmin对象之后每个需要鉴权的页面都检查这个属性是否存在不存在就跳回登录页。然后是DAO层。用户名和密码的校验在DAO里完成返回的Admin对象为null表示查不到。// dao/AdminDao.java package com.dormitory.dao; import com.dormitory.entity.Admin; import com.dormitory.util.JdbcUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class AdminDao { public Admin findByUsernameAndPassword(String username, String password) { // 使用PreparedStatement防SQL注入不要用Statement拼字符串 String sql SELECT id, username, real_name FROM admin WHERE username ? AND password ?; try (Connection conn JdbcUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Admin admin new Admin(); admin.setId(rs.getInt(id)); admin.setUsername(rs.getString(username)); admin.setRealName(rs.getString(real_name)); return admin; } } } catch (Exception e) { e.printStackTrace(); } return null; } }DAO层必须用PreparedStatement而不是Statement。PreparedStatement把参数通过?占位符传进去由驱动做转义处理能防SQL注入Statement是直接把字符串拼进SQL如果用户名里带 or 11这类内容拼出来就是一条永远成立的条件登录直接被绕过。try-with-resources写法会自动关闭Connection、PreparedStatement、ResultSet省得手动调close。这里有个取舍密码用明文存储仅限学习项目够用但如果你要拿这套东西当毕设甚至上生产建议至少用MD5加盐或BCrypt。Mysql里存的是哈希值密码框里输入的内容经过同样算法哈希后才去比对。这个改动不复杂——在Servlet里拿到明文后调用一个EncryptUtil.md5(password)再传给DAO就行DAO层的SQL不需要改。4.2 宿舍分配的业务实现剩余床位查询与更新的事务边界宿舍分配是这个系统里最有“含金量”的业务因为它不是简单的新增一条记录而是同时涉及“查剩余床位”“更新已住人数”“插入入住记录”“防止重复入住”四个动作任何一个没做好都会出数据不一致。我还是按ServletServiceDAO来拆但核心逻辑放在Service层。先看查询剩余床位的SQL。这里的关键是used_bed_count bed_count这个条件以及分配记录时用SELECT ... FOR UPDATE锁行避免两个管理员同时分配导致超卖。// service/AssignService.java package com.dormitory.service; import com.dormitory.dao.AssignmentDao; import com.dormitory.dao.DormitoryDao; import com.dormitory.dao.StudentDao; import com.dormitory.entity.Assignment; import com.dormitory.entity.Dormitory; import com.dormitory.util.JdbcUtil; import java.sql.Connection; public class AssignService { public boolean assignDormitory(Long studentId, Long dormitoryId) { Connection conn null; try { conn JdbcUtil.getConnection(); // 开启手动事务关闭自动提交 conn.setAutoCommit(false); // 1. 锁住宿舍行防止并发分配导致床位超卖 Dormitory dorm new DormitoryDao().findByIdForUpdate(conn, dormitoryId); if (dorm null) { conn.rollback(); return false; } if (dorm.getUsedBedCount() dorm.getBedCount()) { conn.rollback(); return false; } // 2. 校验学生是否已有入住中记录 if (new AssignmentDao().existsActiveAssignment(conn, studentId)) { conn.rollback(); return false; } // 3. 计算床位号已用床位1比如4人间当前住2人新学生分到3号床 int bedNo dorm.getUsedBedCount() 1; // 4. 插入入住记录 Assignment assignment new Assignment(); assignment.setStudentId(studentId); assignment.setDormitoryId(dormitoryId); assignment.setBedNo(bedNo); assignment.setStatus(入住中); new AssignmentDao().insert(conn, assignment); // 5. 更新宿舍已住人数 new DormitoryDao().increaseUsedBedCount(conn, dormitoryId); // 全部成功才提交 conn.commit(); return true; } catch (Exception e) { e.printStackTrace(); try { if (conn ! null) conn.rollback(); } catch (Exception ignored) {} return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (Exception ignored) {} try { if (conn ! null) conn.close(); } catch (Exception ignored) {} } } }这段代码的手动事务是重点。Mysql的InnoDB引擎默认autocommit1每一条SQL都自动提交这在单条记录操作时没问题但宿舍分配这种“多步骤写操作”一旦中间某一步失败了前面成功的步骤也会留下半截子数据——比如插入了入住记录但宿舍已住人数没更新。所以这里conn.setAutoCommit(false)把多个操作包在同一个事务里要么全部成功commit要么全部回滚rollback。SELECT ... FOR UPDATE是另一个关键。它把宿舍那一行数据加了行级排他锁事务提交前其他事务想更新这一行会被阻塞。如果没有这把锁并发场景下两个管理员同时分配两个请求都读到used_bed_count2都判断“还有床位”都插入入住记录4人间就住了5个人。加了锁后第二个请求会等第一个事务提交后才能读到新的used_bed_count3再判断只剩1个床位就不会超卖。第三个值得说的是床位号算法这里简单粗暴地取used_bed_count 1比如4人间已住2人新学生就分到3号床。这样可以省去“遍历床位找空位”的查询。缺点是有中间退宿时会出现“床上有人但床位号空着”的情况比如1号床退宿后新学生被分到3号床。对于学习项目和大多数宿舍管理场景这够用了如果业务要求连续分配空床位号就需要改成SELECT所有已用床位号然后在Java里找最小空缺。%-- jsp/assign.jsp —— 宿舍分配页面的核心表单 --% % page contentTypetext/html;charsetUTF-8 languagejava % html headtitle分配宿舍/title/head body h3为学生分配宿舍/h3 form action${pageContext.request.contextPath}/assign methodpost label学号input typetext namestudentNo//labelbr/ label楼栋 select namebuildingId option value11号楼/option option value22号楼/option /select /labelbr/ label宿舍input typetext namedormitoryRoom placeholder如101//labelbr/ button typesubmit确认分配/button /form /body /htmlJSP页面里楼层和宿舍如果用下拉框动态加载就需要在页面加载时通过AJAX请求一个返回JSON列表的Servlet。传统JSP项目做这个也很直接Servlet里用response.setContentType(application/json;charsetUTF-8)然后out.write(jsonString)前端用jQuery的$.getJSON消费。如果不想引入前端框架也可以在后端用request.setAttribute(dormitoryList, list)然后JSP里c:forEach循环生成option。c:forEach需要引入JSTL标签库在pom.xml里加jstl和standard两个依赖这是传统JSP项目常用的做法。5. JSP学生宿舍管理系统避坑部署与运行中绕不开的5个坑5.1 页面中文乱码Tomcat 8.5下JSP页面和请求参数的编码不一致现象页面上显示中文正常但往Mysql里插入的学生姓名变成??或者页面上直接出现乱码。原因三处编码不一致——JSP页面本身的pageEncoding、Servlet里读取请求参数时的request.setCharacterEncoding、JDBC连接URL里的characterEncoding。三处必须统一成UTF-8少一处都会出问题。解决第一处JSP文件头部加% page contentTypetext/html;charsetUTF-8 languagejava %第二处Servlet在读取参数前加request.setCharacterEncoding(UTF-8)第三处druid.properties的URL里加characterEncodingutf8。如果查Mysql客户端里已经是乱码说明之前插入时就已经坏了删掉重插不要指望靠改查询来恢复。5.2 Tomcat 10把javax.servlet换成jakarta.servlet传统JSP项目直接404现象项目在Tomcat 8.5上跑得好好的换到Tomcat 10后部署war包打开页面报404控制台打ClassNotFoundException: javax.servlet.http.HttpServlet。原因Tomcat 10开始Servlet API的包名从javax.*迁移到jakarta.*传统JSP项目里的HttpServlet、HttpServletRequest都是老包名容器找不到对应类。解决最省事的方法是换回Tomcat 8.5或9.0这也是我做JSP项目坚持用8.5的原因。如果必须用Tomcat 10就把所有import javax.servlet改成import jakarta.servlet注意是全局替换漏一个都不行。还有一部分坑是某些老JDBC驱动或第三方库内部引用了javax.*这种只能换库版本。5.3 连接Mysql 8报错Public Key Retrieval is not allowed现象启动项目后第一次访问数据库时报Public Key Retrieval is not allowed但用Navicat连同一个数据库却正常。原因Mysql 8.0默认认证插件是caching_sha2_password客户端第一次连接时需要用RSA公钥加密密码驱动默认不允许从服务器获取公钥。解决在JDBC连接URL上加参数allowPublicKeyRetrievaltrue同时建议把useSSLfalse也带上避免SSL握手带来的额外报错。完整URL是jdbc:mysql://localhost:3306/dormitory?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue5.4 找不到Mysql驱动ClassNotFoundException: com.mysql.jdbc.Driver现象启动项目报ClassNotFoundException: com.mysql.jdbc.Driver或No suitable driver found for jdbc:mysql...。原因要么mysql-connector的jar没放进WEB-INF/lib目录要么Mysql 8.x的驱动类名变了——8.0以上类名是com.mysql.cj.jdbc.Driver老代码里写的是com.mysql.jdbc.Driver。解决确认两件事。Maven项目先看pom.xml里有没有mysql-connector依赖非Maven项目把jar物理拷到src/main/webapp/WEB-INF/lib/下类名改成com.mysql.cj.jdbc.Driver。注意用Maven时别光在pom.xml里加了依赖就以为完事IDEA要触发一次Reload All Maven Projects否则jar没实际进入classpath。5.5 并发分配宿舍时床位超卖两个管理员同时分配了同一个床位现象4人间宿舍两个管理员同时给两个学生分配最后显示住了5个人入住记录里床位号还重复了。原因分配业务里“查剩余床位”和“更新已住人数”不是原子操作两个请求同时读到used_bed_count4都判断有空床都执行了插入。解决数据库层面的方案是给宿舍行加锁即业务代码里用SELECT ... FOR UPDATE锁住宿舍记录这个在第4章的AssignService里已经实现。如果用的是共享的同一个Tomcat实例也可以在Java层用synchronized锁住分配方法这在单机部署时确实能解决但一旦部署了多个Tomcat节点或者以后换Spring Boot做集群synchronized就失效了。所以正确方向是学数据库锁和事务这是从这个项目里能带走的底层能力。顺带一个排查技巧如果怀疑超卖直接查assignment表里同一个dormitory_id不同bed_no的数量是否超过bed_count用GROUP BY dormitory_id HAVING COUNT(*) MAX(bed_count)就能扫出异常数据。6. 收尾把系统打成WAR包部署到Tomcat以及后续能走多远自己电脑上跑通只是第一步真正的Web项目要能部署到服务器上让其他人访问。传统JSP项目的部署方式是把项目打成WAR包丢进Tomcat的webapps目录Tomcat启动时自动解压。用IDEA打包很简单菜单Build→Build Artifacts→ 选择xxx:war exploded→Build或者Maven项目直接执行mvn clean package然后到target/目录下拿dormitory.war。把它复制到Tomcat的webapps/下启动Tomcat访问http://服务器IP:8080/dormitory/就能看到系统首页。检查部署是否成功看两个地方一是Tomcat的logs/catalina.out里有没有Deployment of web application archive [dormitory.war] has finished二是访问http://服务器IP:8080/dormitory/login.jsp能否正常打开登录页。如果报404先看webapps下是否已经有同名目录Tomcat不会覆盖解压出问题残留的目录删掉旧的再重启。这套系统做完之后往后走有三条路。第一条是把JSP替换成前后端分离后端用Spring Boot提供REST接口前端用Vue或React去调。你会发现Servlet里的doPost就是Controllerrequest.getParameter就是RequestParamJdbcUtil就是JdbcTemplate业务逻辑从JSP项目迁移过去只是换个壳。第二条是保留JSP但引入更多Java基础能力比如用Filter做登录鉴权拦截器用Listener在启动时初始化连接池用自定义标签简化页面重复代码这些在java面试题里都是高频考点。第三条是往工程化走加Maven依赖管理、写单元测试覆盖AssignService的分配逻辑、用Git做版本管理把项目从“能跑”推向“能维护”。我自己的习惯是每写一个Web系统前先把“更新和查询的事务边界”画出来这个从JSP时代养成的习惯到现在写Spring Boot项目都还在用。因为业务可以变、框架可以换但数据一致性的底线永远是写代码之前就要想清楚的事。希望帮到你也祝这套JavaJSPMysql的学生宿舍管理系统不仅是答辩和课程设计里的一个分数更是你读懂Java Web世界的第一块拼图。本文还有配套的精品资源点击获取

相关推荐

从异步导出到Redis连接池:第三十二周技术复盘与思考
从异步导出到Redis连接池:第三十二周技术复盘与思考

第三十二周的周报我拖到周四深夜才动笔。不是没东西写,恰恰相反,这周经历了订单模块重构收尾、报表导出功能发布、还有一次线上接口超时的排查,随便挑一件都够写两千字。但真正坐下来打开文档的时候,我反而反复删了好几版——原因… · 2026/9/25 7:33:44

ESXi将USB硬盘映射为本地磁盘并创建VMFS的完整实操指南
ESXi将USB硬盘映射为本地磁盘并创建VMFS的完整实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:33:38

开源LLM代码审查工作流:CLI+Git原生集成实践
开源LLM代码审查工作流:CLI+Git原生集成实践

1. 项目概述:这不是一个“工具”,而是一套可落地的开源代码审查工作流open-code-review 这个名字乍看像某个具体软件,但实际它代表的是一类正在快速成型的新型开发实践——用开源、透明、可审计的方式,把大语言模型(LL… · 2026/9/25 7:33:38

昇腾Atlas 300V 24G加速卡部署YOLO全流程实战
昇腾Atlas 300V 24G加速卡部署YOLO全流程实战

1. 先搞清楚Atlas 300V 24G的定位:是加速卡,但不是你以为的那种加速卡1.1 一张卡解决什么问题看到热搜里连续出现“atlas部署yolo”和“atlas 300v 24g 是运算加速卡吗”这两条,我就知道又有一批做边缘AI或服务器推理的同学被这张卡吸引过来了… · 2026/9/25 7:53:27

ExternalDNS 与 AWS Load Balancer Controller 集成实战:ALB/NLB Ingress 的 DNS 自动化管理
ExternalDNS 与 AWS Load Balancer Controller 集成实战:ALB/NLB Ingress 的 DNS 自动化管理

云原生 【免费下载链接】external-dns Configure external DNS servers dynamically from Kubernetes resources 项目地址: https://gitcode.com/gh_mirrors/ex/external-dns 点击查看 免费下载 ExternalDNS 与 AWS Load Balancer Controller(原 ALB In… · 2026/9/25 7:53:20

Apache Flink Checkpoint 监控指南:读懂 Web UI 四大标签页与每项指标
Apache Flink Checkpoint 监控指南:读懂 Web UI 四大标签页与每项指标

大数据流处理批处理数据工程 【免费下载链接】flink 项目地址: https://gitcode.com/gh_mirrors/fli/flink 点击查看 免费下载 Flink 的 Web 界面提供了专门监控作业 Checkpoint 的入口,且作业终止后这些统计依然可查。本文围绕官方文档 docs/content/d… · 2026/9/25 7:53:08

AIO Sandbox:桌面级开发环境的原子化容器封装
AIO Sandbox:桌面级开发环境的原子化容器封装

1. 这不是沙箱,是“桌面级开发环境”的原子化封装你有没有过这种体验:调试一个前端页面,得开着 Chrome DevTools 查 DOM,同时切到终端敲curl测试 API,再切回 VSCode 改代码,顺手还要用chmod修个文件权限&am… · 2026/9/25 7:52:50

运算符与条件分支的底层逻辑:从优先级到if/switch的高效写法
运算符与条件分支的底层逻辑:从优先级到if/switch的高效写法

1. 把运算符当成"决策细胞"来理解1.1 运算符的本质:从一次计算到一次判断很多人学编程时,运算符是被一笔带过的基础章节。但我一直觉得,运算符才是整个程序流程控制里最核心的"细胞"。为什么这么说?因为不管你… · 2026/9/25 7:52:50

豆瓣图书知识图谱实战:Neo4j图数据库推荐系统搭建
豆瓣图书知识图谱实战:Neo4j图数据库推荐系统搭建

简介:本资源是一套面向高校计算机及相关专业(人工智能、自动化、物联网等)学生的毕业设计级实践项目,聚焦豆瓣图书推荐系统与知识图谱构建,深度融合Neo4j图数据库应用开发。项目完整覆盖数据采集、清洗、图模型设计、实… · 2026/9/25 7:52:43

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码