ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java+JSP+MySQL学生宿舍管理系统全链路开发实战

Java+JSP+MySQL学生宿舍管理系统全链路开发实战 简介一套采用JavaJSPMySQL架构的Web学生宿舍管理系统面向Java Web初学者、课程设计与毕业设计人群。系统覆盖管理员与学生两类角色管理员可完成学生信息增删查改、卫生信息登记与查看、宿舍综合管理以及维修进度处理学生可自助修改个人信息、在线报修和查看卫生评比。功能模块划分清晰贴合宿舍管理实际场景适合作为Java Web课程设计或毕业设计参考。压缩包共137个文件主要包含44个Java源码、44个Class编译文件、26个JSP页面、1个SQL数据库脚本以及CSS/GIF/JS等页面资源完整呈现从数据库建表、实体类、Servlet控制层到JSP展示层的Java Web分层实现整体约3.39MB下载部署都很便捷。目前已有3001人学习参考适合需要快速入手或借鉴完整宿舍管理系统的开发者。包内附E-R图与SQL语句可一键建库源码注释清晰、命名规范修改维护方便能帮助读者理解宿舍、学生、卫生、维修等核心模块的关联设计与实际复用。1. JavaJSPMysql学生宿舍管理系统这不只是课设模板而是一条完整的数据到页面链路每年毕设季总有一批人打开搜索框输入“JavaJSPMysql实现Web学生宿舍管理系统”。这个组合听起来老但它是Java Web方向上生命周期最长的题目之一JSP负责把数据渲染成页面Java处理业务逻辑MySQL存宿舍、床位、学生和入住记录而E-R图和SQL语句决定了你后面是三天交差还是三周返工。这套方案服务的人很明确手头有这个问题、想照着做并跑通的人。我会把数据建模、JDBC到JSP渲染的完整链路、以及跑这类项目时踩过的坑一次写完让你拿到就能动手。2. 从E-R图到SQL语句把宿舍、学生和入住记录建成不会返工的表2.1 为什么先画E-R图再做表实体、属性和联系三件事动手建表前先确认MySQL已经装好并能连接。如果还没装建议先把mysql安装配置教程走一遍再回来不然下面SQL跑不起来。很多同学拿到这个题目第一件事是写代码结果做到第三天发现宿舍表里少了一个床位字段学生表里没有身份证号退宿记录没地方存只能删表重来。我的习惯是先在纸上把E-R图画明白再动手建表。学生宿舍管理系统最常见的实体有五个管理员admin、楼栋building、宿舍dormitory、学生student、入住记录checkin_record。每个实体先列属性比如学生有学号、姓名、性别、班级、电话号码、身份证号、宿舍ID、入住时间。注意学号要作为主键身份证号要加唯一约束。楼栋有楼栋编号、楼栋名称、楼层数、宿管电话。宿舍有宿舍ID、所属楼栋、房间号、床位数、已住人数。联系也要写清楚一个楼栋包含多间宿舍这是1:N一间宿舍可以住多个学生但床位有限按简化模型处理成1:N一个学生可以有多条入住记录换寝所以入住记录表里存学生ID、旧宿舍ID、新宿舍ID、操作时间。把这三条关系理清了外键怎么加、删除时受什么限制就全清楚了。这里有一个经验如果只是应付课设学生表直接带dorm_id外键就够了入住记录表可以不要。但答辩老师很喜欢问“学生换寝室了你怎么处理”所以我会保留checkin_record表哪怕平时用不到它也能让E-R图更完整顺带展示你会设计多表关联。E-R图和SQL语句在这个题目里是绑定出现的图里画了多少张表SQL里就应该出现多少张。2.2 从E-R图到MySQL建表SQL一套可抄的建库建表脚本宿舍管理系统需要的表我是这么建的。先建库指定字符集和排序规则CREATE DATABASE IF NOT EXISTS dormitory_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE dormitory_db;这里把字符集写成utf8mb4而不是utf8原因在后面的避坑章节会细说简短讲就是utf8mb4能存表情符号而且MySQL 8.0里这个字符集几乎成了默认选择不会出现“Emoji插入失败”这种课设答辩现场的惨案。collate选utf8mb4_general_ci够用了不需要去追求utf8mb4_unicode_ci在排序上的细微差异。接着建管理员表和楼栋表CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE building ( id INT PRIMARY KEY AUTO_INCREMENT, building_name VARCHAR(50) NOT NULL, floors TINYINT NOT NULL DEFAULT 6, manager_phone VARCHAR(20) );admin表的password字段长度我故意留了64位原因是如果你以后想用MD5或SHA-256存密码32位和64位都放得下。用varchar(64)存明文虽然浪费一点空间但课设阶段换来的是不用改表。create_time用DATETIME DEFAULT CURRENT_TIMESTAMP插入的时候就不用手动填时间。building表的floors用TINYINT宿舍楼一般不会超过127层省空间还带默认值。然后是核心的宿舍表和学生表这两张表是整个系统外键关系最密集的地方CREATE TABLE dormitory ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL, room_number VARCHAR(10) NOT NULL, bed_count TINYINT NOT NULL DEFAULT 4, occupied_count TINYINT NOT NULL DEFAULT 0, UNIQUE KEY uk_building_room (building_id, room_number), CONSTRAINT fk_dorm_building FOREIGN KEY (building_id) REFERENCES building (id) ON DELETE CASCADE ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender ENUM(男, 女) NOT NULL, class_name VARCHAR(50), phone VARCHAR(20), id_card VARCHAR(18) UNIQUE, dorm_id INT, checkin_date DATE, CONSTRAINT fk_student_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory (id) ON DELETE SET NULL );宿舍表里我用了联合唯一键uk_building_room意思是同一栋楼里不允许出现两个相同的房间号。这是只靠单表主键约束不住的必须用组合唯一键属于SQL语句编写格式要求里的高频考点。外键fk_dorm_building用了ON DELETE CASCADE删除楼栋时自动删掉下面所有宿舍符合“楼栋没了宿舍也没意义”的业务直觉。学生表的dorm_id外键用ON DELETE SET NULL删除宿舍时学生记录还在只是变成未分配状态这比直接删学生安全得多。为什么学生表不直接外键到宿舍表用CASCADE因为学生是人宿舍是床宿舍拆了人不能跟着没了。如果这里选了CASCADE你删除一间宿舍会把住过的学生记录一起删掉这种“数据连坐”在答辩演示时翻车概率极高老师只要点一次删除宿舍再查学生列表就直接露馅。接着建入住记录表CREATE TABLE checkin_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, old_dorm_id INT, new_dorm_id INT NOT NULL, change_type ENUM(入住, 换寝, 退宿) NOT NULL, change_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_record_student FOREIGN KEY (student_id) REFERENCES student (id) ON DELETE CASCADE, CONSTRAINT fk_record_old_dorm FOREIGN KEY (old_dorm_id) REFERENCES dormitory (id) ON DELETE SET NULL, CONSTRAINT fk_record_new_dorm FOREIGN KEY (new_dorm_id) REFERENCES dormitory (id) ON DELETE SET NULL );这张表的作用是记录学生宿舍的变动历史old_dorm_id允许为空因为首次入住时没有旧宿舍。外键的删除策略是学生删除记录跟着删CASCADE旧宿舍删除只把记录里的旧宿舍ID置空SET NULL。这一个表就把E-R图里的多对多关系彻底解开了。注意gender字段用ENUM之后插入时只能写“男”或“女”写“male”会报错。如果你不想被这个约束折磨可以在建表时直接用VARCHAR然后靠前端下拉框保证数据合法。最后插入测试数据数据量不用太多但一定要覆盖“有学生住、有空宿舍、有换寝记录”三个场景INSERT INTO admin (username, password, real_name) VALUES (admin, 123456, 系统管理员); INSERT INTO building (building_name, floors, manager_phone) VALUES (梅苑1栋, 6, 13800000001), (梅苑2栋, 6, 13800000002); INSERT INTO dormitory (building_id, room_number, bed_count, occupied_count) VALUES (1, 101, 4, 2), (1, 102, 4, 0), (2, 201, 4, 1); INSERT INTO student (student_no, name, gender, class_name, phone, id_card, dorm_id, checkin_date) VALUES (20240001, 张伟, 男, 计科2401, 13900000001, 110101200501011234, 1, 2024-09-01), (20240002, 李娜, 女, 软工2402, 13900000002, 110101200502021234, 2, 2024-09-01), (20240003, 王强, 男, 计科2401, 13900000003, 110101200503031234, 3, 2024-09-02);注意student表的dorm_id我故意给三人分配了宿舍但宿舍101的occupied_count是2与实际分配人数一致。这里有个常见的坑很多人只改了学生表的dorm_id忘了同步维护dormitory表的occupied_count导致“宿舍显示住了0人但明细里有人住”。后续做入住登记时这两个字段必须放在一个事务里更新第4章会展开说。2.3 用SQL语句验证模型连表查询、去重和统计一次测完表建完不要急着写Java先用SQL语句把数据查一遍检查外键和关联逻辑对不对。下面这几条是MySQL常用sql语句里最容易考到的组合-- 查询每个宿舍的入住详情楼栋名 房间号 已住人数 SELECT b.building_name, d.room_number, d.occupied_count, d.bed_count FROM dormitory d JOIN building b ON d.building_id b.id ORDER BY d.id; -- 查询所有已分配宿舍的学生按楼栋分组 SELECT b.building_name, d.room_number, s.name, s.student_no FROM student s JOIN dormitory d ON s.dorm_id d.id JOIN building b ON d.building_id b.id ORDER BY b.id, d.id; -- 统计每个宿舍的空床位 SELECT d.id, d.room_number, d.bed_count - d.occupied_count AS free_beds FROM dormitory d WHERE d.bed_count - d.occupied_count 0;这三条能跑通就说明外键方向、JOIN条件、聚合逻辑都没问题。接着做两条稍微有难度的SQL语句验证去重和分组-- 按班级统计学生人数班级名相同的只会出现一行 SELECT class_name, COUNT(*) AS stu_count FROM student GROUP BY class_name; -- 查班级里有没有重名学生去重后数量不等于原始数量 SELECT class_name, name, COUNT(*) AS c FROM student GROUP BY class_name, name HAVING c 1;GROUP BY和HAVING是这里的关键GROUP BY把相同班级的学生归成一组COUNT(*)统计每组行数如果想找出重复数据就按班级和姓名两个字段一起分组最后用HAVING把出现次数大于1的过滤出来。这条写法在工作里也常用属于sql语句去重的一个变体。注意SQL语句执行顺序是FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY所以HAVING里不能用SELECT里的别名这个问题很多新手会在答辩时被问住。到此数据模型已经立住了。建表SQL和测试数据就是你后面所有Java代码的输入表结构一变DAO、Servlet、JSP全要跟着改所以这一章多花一小时后面能省一整天。3. JavaJSPMysql实现从JDBC连接到JSP列表渲染的分层代码3.1 项目结构与JDBC工具类先搭一个不被MySQL版本卡住的底座用Eclipse或IDEA建一个Dynamic Web Project项目结构固定是这样src/ com.dorm.bean -- JavaBean com.dorm.dao -- 数据库访问 com.dorm.servlet -- 控制器 com.dorm.util -- 工具类 WebContent/ WEB-INF/lib/ -- mysql驱动jar放这里 admin_login.jsp student_list.jsp dorm_list.jsp开发阶段直接用Servlet不引Spring原因是这个题目的核心考察点就是JSPServletJDBC这条链路的底层理解。你把这条链路跑通了以后学Spring MVC会很快本质还是“请求 → Controller → 业务 → DAO → 数据库 → 响应”只是框架帮你把胶水代码藏起来了。数据库连接工具类我一般这么写package com.dorm.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/dormitory_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码最关键的是URL里的四个参数。useUnicodetrue和characterEncodingutf8解决中文写入乱码useSSLfalse是为了避免MySQL 8.0连接时提示SSL握手警告课设环境没必要启用SSLserverTimezoneAsia/Shanghai是MySQL 8.0和JDBC驱动的硬性要求不写这一项大概率会报“The server time zone value is unrecognized”。驱动类名com.mysql.cj.jdbc.Driver是Connector/J 5.1.6之后的写法到了8.x驱动里旧的com.mysql.jdbc.Driver被移除了这是版本差异导致的经典报错。注意mysql驱动jar包必须放在WEB-INF/lib目录下而不是只加在Build Path里。Tomcat运行JSP时加载的是WEB-INF/lib下的jarIDE里构建路径只是编译期可见运行期找不到类就会抛ClassNotFoundException。我见过太多同学在IDE里能跑、打war包部署就报找不到驱动原因就是这个。3.2 JavaBean与DAO把学生表的增删改查写成可复用方法先写JavaBean字段要和student表对齐package com.dorm.bean; import java.util.Date; public class Student { private Integer id; private String studentNo; private String name; private String gender; private String className; private String phone; private String idCard; private Integer dormId; private Date checkinDate; // 省略getter和setterIDE自动生成即可 }然后是DAO层用PreparedStatement而不是Statement防止SQL注入这是最基本的数据库安全习惯。查询所有学生的代码如下package com.dorm.dao; import com.dorm.bean.Student; import com.dorm.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class StudentDao { public ListStudent findAll() { ListStudent list new ArrayList(); String sql SELECT * FROM student ORDER BY id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setClassName(rs.getString(class_name)); s.setPhone(rs.getString(phone)); s.setIdCard(rs.getString(id_card)); s.setDormId(rs.getInt(dorm_id)); s.setCheckinDate(rs.getDate(checkin_date)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; } }注意try-with-resources写法的细节Connection、PreparedStatement、ResultSet都实现了AutoCloseabletry块结束后会自动关闭不需要手动写finally。ResultSet里的字段名必须和数据库列名一致比如student表里是student_noJavaBean里是studentNo所以映射时要手动set。这是课设里极其常见的“字段对不上导致属性全是null”的原因。除了findAll我一般还会补四个方法findById按ID查单个学生、add新增学生、update修改学生、delete删除学生。delete要注意外键约束的问题如果这个学生已有入住记录直接删会失败。处理办法是先把checkin_record里相关记录删掉再删student这两个操作要用事务包起来后面避坑章节专门说。3.3 Servlet控制器与JSP列表渲染从数据库到页面的一次完整链路Servlet负责接收请求、调DAO、把数据塞进request、转发给JSP。查询列表的Servletpackage com.dorm.servlet; import com.dorm.bean.Student; import com.dorm.dao.StudentDao; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; WebServlet(/studentList) public class StudentListServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListStudent students studentDao.findAll(); req.setAttribute(students, students); req.getRequestDispatcher(student_list.jsp).forward(req, resp); } }注解WebServlet(/studentList)是Servlet 3.0以后支持的写法不用再改web.xml。如果你用的Tomcat是7.0以下要改成在web.xml里配置servlet和servlet-mapping这是老项目和新写法的分水岭。请求转发用forward而不是sendRedirect因为转发是服务器内部跳转request里的attribute还能带到JSP重定向是浏览器重新发一次请求request里的数据就丢了。对应的student_list.jsp% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 % % page importjava.util.List % % page importcom.dorm.bean.Student % html head title学生列表/title /head body table border1 tr th学号/thth姓名/thth性别/thth班级/thth宿舍ID/th /tr % ListStudent students (ListStudent) request.getAttribute(students); if (students ! null) { for (Student s : students) { % tr td% s.getStudentNo() %/td td% s.getName() %/td td% s.getGender() %/td td% s.getClassName() %/td td% s.getDormId() %/td /tr % } } % /table /body /htmlJSP里%%是脚本表达式% s.getStudentNo() %会把结果直接输出到页面上。这种写法是JSP最原始的形态虽然不推荐在大型项目里用但课设阶段它是最容易理解的。如果你对EL表达式和JSTL熟悉完全可以用${student.studentNo}替换效果一样且页面更干净。这里有个细节request.getAttribute返回的是Object所以强转成List 时如果忘了判空页面会直接抛NullPointerException。我一般会在Servlet里设置一个空List而不是null这样JSP里就不用每次都判空。3.4 分页查询必考的limit写法与页码保护学生列表超过20条以后一个页面全列出来就很难看了。分页是答辩老师最爱问的点没有之一。分页的核心是SQL的limitSELECT * FROM student ORDER BY id LIMIT 0, 10; -- 第一页每页10条 SELECT * FROM student ORDER BY id LIMIT 10, 10; -- 第二页LIMIT后面第一个参数是偏移量第二个是返回条数。第二页偏移量是10第三页是20规律是(page - 1) * pageSize。Servlet里这么算int pageSize 10; int currentPage 1; String pageParam req.getParameter(currentPage); if (pageParam ! null !pageParam.trim().isEmpty()) { currentPage Integer.parseInt(pageParam); } if (currentPage 1) currentPage 1; int totalCount studentDao.countAll(); int totalPages (int) Math.ceil(totalCount * 1.0 / pageSize); if (currentPage totalPages totalPages 0) currentPage totalPages; int offset (currentPage - 1) * pageSize; ListStudent students studentDao.findByPage(offset, pageSize); req.setAttribute(students, students); req.setAttribute(totalPages, totalPages); req.setAttribute(currentPage, currentPage); req.getRequestDispatcher(student_list.jsp).forward(req, resp);前端直接把字符串参数传进来Integer.parseInt如果遇到非数字会抛NumberFormatException我建议在parse外面套一个try-catch解析失败就归为默认值1。还有currentPage不能小于1不能大于totalPages这两个边界不卡住用户手动改URL查询?currentPage999就能把数据库拖慢属于页面翻车最常见原因之一。DAO层对应的分页查询方法public ListStudent findByPage(int offset, int pageSize) { ListStudent list new ArrayList(); String sql SELECT * FROM student ORDER BY id LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, offset); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { // 字段映射同findAll略 } } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT后面的参数必须通过setInt设置不能拼字符串否则又是SQL注入的隐患。MySQL的LIMIT偏移量在数据量大时会有性能问题offset100000时会把前面十万行都扫一遍但课设数据量到不了这个量级不用优化。如果真想优化可以改成WHERE id ? LIMIT 10的子查询方案但那是后话。4. 部署运行避坑指南乱码、时区、外键和SQL去重的5个现场4.1 MySQL 8.0驱动加载失败ClassNotFoundException: com.mysql.jdbc.Driver现象Tomcat一启动或第一次访问页面就抛ClassNotFoundException定位到的行是Class.forName这一行。原因两个。一是驱动类名写错老教程都写com.mysql.jdbc.Driver这个类在MySQL 5.x驱动里有在MySQL 8.0驱动里被移除了要改成com.mysql.cj.jdbc.Driver二是驱动jar根本没到运行环境只在IDE的Build Path里没复制到WEB-INF/lib。解决统一写com.mysql.cj.jdbc.Driver确认WEB-INF/lib下有对应MySQL大版本的mysql-connector-java驱动包改完清理Tomcat的work目录再重启防止旧class缓存作怪。这一步做完90%的驱动问题都能消掉。4.2 JSP中文乱码插入的“张伟”变成“å¼ æ°”现象JSP页面上填写学生姓名提交到数据库后变成乱码或者JSP页面本身显示一堆问号。原因字符集在四个环节里有一个不一致就会乱JSP页面编码、HTTP请求编码、数据库连接编码、表字符集。解决第一JSP页面第一行写满三样东西contentTypetext/html; charsetUTF-8、pageEncodingUTF-8、以及HTML里的 三处一个都不能少第二JDBC的URL带上useUnicodetruecharacterEncodingutf8第三表用utf8mb4第四在Servlet里对请求做一次编码处理req.setCharacterEncoding(UTF-8)。如果这样还有问题检查Tomcat的server.xml里Connector是否配了URIEncodingUTF-8虽然现代Tomcat默认就是UTF-8但老版本不是。提示修乱码最忌讳“这里改一下、那里改一下”然后到处试。按JSP页面 → 请求 → 连接URL → 表结构这条链路从前往后检查一次只改一个变量改完重启Tomcat再试两个小时的车祸十分钟就能定位。4.3 多表联查结果重复一条学生记录被COUNT成两遍现象统计班级人数结果比实际人数多查询已入住学生同一个学生出现两行。原因学生表JOIN入住记录表时一个学生有多条记录JOIN结果行数被放大。我见过有人查班级人数直接这样写SELECT s.class_name, COUNT(*) FROM student s JOIN checkin_record r ON s.id r.student_id GROUP BY s.class_name;一个换过宿舍的学生会产生两条入住记录COUNT(*)就把这个人算了两遍。解决先想清楚计数粒度。如果统计“有多少学生”就数学生表的行数用COUNT(DISTINCT s.id)或者先按学生表GROUP BY再关联子表SELECT s.class_name, COUNT(DISTINCT s.id) AS stu_count FROM student s LEFT JOIN checkin_record r ON s.id r.student_id GROUP BY s.class_name;这个场景就是典型的SQL语句去重去重的手段不是DISTINCT一个关键字那么简单而是先理解JOIN会不会放大行数再决定在哪个表上做DISTINCT。经验是多表JOIN后看到某个COUNT结果比表中记录数大第一时间检查JOIN字段是不是唯一键。4.4 删除宿舍报外键约束错误Cannot delete or update a parent row现象在宿舍管理页面点击删除一间宿舍控制台报java.sql.SQLException: Cannot delete or update a parent row: a foreign key constraint fails。原因student表里有学生的dorm_id指向这间宿舍外键默认RESTRICT限制宿舍作为“父行”不能在有子行引用时被删除。解决想清楚业务语义。如果这间宿舍要拆掉学生应该变成“未分配”还是转去别的宿舍我的做法是在删除宿舍的Service方法里开启事务先把住在这个宿舍的学生dorm_id置空再删除宿舍记录最后提交Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); String updateStudent UPDATE student SET dorm_id NULL WHERE dorm_id ?; String deleteDorm DELETE FROM dormitory WHERE id ?; try (PreparedStatement ps1 conn.prepareStatement(updateStudent); PreparedStatement ps2 conn.prepareStatement(deleteDorm)) { ps1.setInt(1, dormId); ps1.executeUpdate(); ps2.setInt(1, dormId); ps2.executeUpdate(); conn.commit(); } } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }注意的两点setAutoCommit(false)之后如果中途任何一条SQL失败rollback能保证数据库回到删除前的状态这就是MySQL事务处理在课设里的真实应用事务必须在同一个Connection上执行不能在一个方法里各拿一个连接那样事务根本没覆盖到两条操作。我见过最不靠谱的写法是两个DAO方法各拿一个Connection看起来每步都成功但中间如果断电数据就处于学生还指着宿舍、宿舍已被删的脏状态。4.5 Tomcat端口被占用和MySQL时区警告两个环境噪音问题现象启动Tomcat时报Address already in use: JVM_Bind:8080或者用浏览器访问一切正常但控制台不断刷The server time zone value。原因端口被占用多半是上次Tomcat没关干净或者别的开发工具占了8080时区警告是MySQL 8.0的JDBC驱动默认会检查服务器时区MySQL服务器用的是系统时区驱动不认识。解决端口问题先用netstat -ano | findstr 8080查到占用进程PID再用taskkill /PID PID /F杀掉或者直接把Tomcat的server.xml里端口改成8081改完别忘了把浏览器访问地址也改了。时区问题就是在JDBC URL最后加serverTimezoneAsia/Shanghai这一加警告就没了。这两件事都不是业务逻辑问题五分钟能搞定但会浪费很多人在答辩前最后一晚的时间属于典型的玄学问题看似是代码问题其实是环境问题。5. 把宿舍管理系统升级成能过验收的项目登录过滤、事务完整性和验证流程如果前面四章做到位了系统已经能跑通“管理员登录 → 看学生列表 → 看宿舍列表 → 增删改查”的基本流程。但课设答辩和实际验收往往会再往深问一层这里给出三个低成本高回报的强化动作。第一个动作是登录过滤器。你现在的系统如果直接在浏览器输入student_list.jsp页面照样能开这就是一个明显的安全缺失。加一个Filter把所有JSP和Servlet拦下来判断session里有没有登录标记package com.dorm.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(/login) || uri.endsWith(.css) || uri.endsWith(.js)) { chain.doFilter(req, resp); return; } if (session ! null session.getAttribute(admin) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(login.jsp); } } }注意getSession(false)拿不到session时返回null而不是新建这样没登录的请求不会被悄悄创建session。放行的白名单要包含登录页面、登录接口、以及静态资源否则CSS样式加载不出来又是另一个坑。第二个动作是把“分配宿舍”做成一个完整事务。业务上分配宿舍要做三步检查目标宿舍是否有空床位、把宿舍的已住人数加1、在学生表里写入dorm_id。三步之间不允许中途失败。用和4.4一样的套路setAutoCommit(false)开始三条SQL在一个Connection里执行最后commit。答辩时你可以口头说明如果不做事务第一步查到有床位但第二步更新失败时两个人可能同时抢到最后一间房这就是并发下的超卖问题。第三个动作是验证流程。我自己的习惯是每次改完代码走一遍完整流程并且每一步都对着数据库验证数据变化。推荐流程登录 → 新增楼栋 → 新增宿舍 → 新增学生不要分配宿舍→ 分配宿舍 → 查看学生列表 → 修改学生信息 → 退宿/换寝 → 删除一个空宿舍。每做完一步用第2章写的那几条SQL语句查一下对应的表确认数据变化符合预期。这套流程走完基本上常见的CRUD、外键、事务、分页问题都会暴露干净。我当年第一次做这个题目跳过了E-R图直接建表后来学生表加了两次字段、删了三次外键最后一天还在改SQL语句。现在回头看E-R图画清楚的那一个小时是整个项目里性价比最高的一个小时。如果你今天正在为这个题目熬夜按顺序把建表SQL、JDBC工具类、DAO、Servlet、JSP串起来多半能在一天内跑通。如果某个环节卡住了回头翻一下第4章大部分坑都在里面。希望帮到你。本文还有配套的精品资源点击获取
返回列表