十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

JSP+MySQL学生宿舍管理系统开发:数据库设计、连接池与部署排错实战

JSP+MySQL学生宿舍管理系统开发:数据库设计、连接池与部署排错实战 简介这是一份基于JSPMySQL的学生宿舍管理系统完整实训作业包面向高校软件技术、计算机相关专业学生及Java Web入门开发者解决宿舍管理课程设计或实训项目缺乏完整可运行代码的问题。资源共67个文件包含17个Java源文件、17个编译后的class文件、10个JSP页面、8个XML配置、7个JAR依赖库、SQL数据库脚本和Word作业文档等压缩包仅5.55MB结构清晰、便于按需取用。核心功能覆盖学生信息管理、宿舍分配与调整、住宿费用管理、在线报修维修、统计报表和用户权限管理基本串起JSPServletMySQL的典型开发链路。内置的SQL初始化脚本可一键初始化数据库作业文档中另有系统架构、数据库设计及配置步骤说明大幅降低复现门槛。目前已有69人学习下载适合作为课程设计参考、实训报告配套源码或初学Java Web时练习综合项目开发的样板。1. 实训作业里最容易被问倒的不是写代码是连不上数据库做JSPMySQL的学生宿舍管理系统大多数同学卡住的地方不是功能没写完而是从“代码能编译”到“Tomcat里能跑通”之间的那段路。课程设计或实训作业通常要求交付三样东西源代码、数据库脚本、说明文档但评分时老师打开项目先看的不一定是界面有多花哨而是你能不能当场把项目跑起来、能不能解释清楚request.getParameter之后数据经历了什么。这套系统的业务本身并不复杂楼栋、宿舍、学生、入住、退宿、报修、访客、水电费记录属于典型的管理信息系统CRUD。真正区分及格和优秀的分水岭在于你是否理解了三层结构里JSP、Servlet、DAO各自的职责以及MySQL连接串里那串useSSL、serverTimezone参数为什么不能删。顺着“基于JSPMySQL”这个标题往下拆你会发现它等价于一套完整的Web开发基本功训练JDBC连接管理、连接池参数调优、JSP的page指令和taglib引入、Servlet跳转与session控制、数据库外键设计、以及最后的部署路径检查。我一般会把这类项目当作一条“最小可运行的Java Web生产线”来复现建库、建表、写工具类、写DAO、写Servlet、写JSP、配置Tomcat、部署、排错。这篇博文按实战顺序展开每一步都有可以直接抄走的代码和参数表最后收在几个只有动手跑过才会碰到的细节上。2. 宿舍管理系统的数据模型从需求到MySQL建表SQL2.1 先划分业务边界别一上来就写建表语句学生宿舍管理系统的核心业务围绕“宿舍资源”和“学生住宿”展开常见的功能模块有系统登录、楼栋管理、宿舍管理、学生管理、入住分配、退宿处理、来访登记、报修管理、水电费记录。实训作业的评分标准里功能完整度是一方面数据库设计的规范性是另一方面。很多人为了省事把学生姓名、宿舍号、报修内容全部塞进一张大表里导致很多冗余字段后面写统计SQL时要到处distinct或者group by一堆无关列。更合理的做法是先按业务对象拆表再按关系建外键。每张表的主键建议用自增id如果是纯粹给实训评分用的项目不需要引入雪花算法或分布式主键。宿舍表和楼栋表是主从关系学生表和宿舍表是多对一关系报修表、访客表、水电费表都以学生或宿舍作为外键来源。之所以不建议在实训项目里把所有关联都加物理外键是因为你后期往表里插测试数据、批量导入Excel时会频繁碰到外键约束卡插入顺序的问题但完全不加外键又解释不通“合理性”。我一般的做法是在表结构上保留外键字段并在设计文档里画好ER图同时把物理外键加上但只要在代码里把级联和删除策略想清楚就不会被约束绊住。2.2 建成5张核心表覆盖实训评分的基本功能点下面这套建表SQL覆盖了登录、楼栋、宿舍、学生、报修、水电费六个功能点其中登录账号我单独抽了一张sys_user表不和学生表混在一起。这样做的原因是admin账号和普通学生的信息字段完全不同硬塞进同一张表会导致大量列为空。CREATE DATABASE IF NOT EXISTS dorm DEFAULT CHARSET utf8mb4; USE dorm; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 使用MD5加密后存储, real_name VARCHAR(50), role TINYINT DEFAULT 2 COMMENT 1管理员 2宿管 3学生, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE building ( id INT PRIMARY KEY AUTO_INCREMENT, building_no VARCHAR(20) NOT NULL, floor_count INT DEFAULT 6, room_count INT DEFAULT 60, manager VARCHAR(50) ) ENGINEInnoDB; CREATE TABLE dorm_room ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL, room_no VARCHAR(20) NOT NULL, bed_count INT DEFAULT 4, used_bed_count INT DEFAULT 0, tel VARCHAR(20), KEY idx_building (building_id), CONSTRAINT fk_room_building FOREIGN KEY (building_id) REFERENCES building(id) ) ENGINEInnoDB; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender CHAR(1), college VARCHAR(50), major VARCHAR(50), phone VARCHAR(20), dorm_room_id INT, bed_index TINYINT COMMENT 1-4标识床位号, status TINYINT DEFAULT 1 COMMENT 1在住 0退宿, KEY idx_dorm (dorm_room_id), CONSTRAINT fk_student_room FOREIGN KEY (dorm_room_id) REFERENCES dorm_room(id) ) ENGINEInnoDB; CREATE TABLE repair ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, dorm_room_id INT NOT NULL, content VARCHAR(255) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待处理 1处理中 2已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME, KEY idx_status (status) ) ENGINEInnoDB;建表SQL拆成两层来看第一层是主表building、dorm_room、student它们决定了系统里最核心的“人在哪里住”第二层是业务表repair、water_electric它们依赖前面的主表生成新的流水数据。CHARSET选了utf8mb4而不是utf8是因为如果学生姓名里出现生僻字或者emojiutf8mb3在MySQL 5.7以上版本会报“Incorrect string value”错误这个坑在实训演示现场非常常见。ENGINE统一用InnoDB保证事务和外键支持。KEY idx_status (status)这行是给常用查询字段建单列索引状态筛选在报修列表里几乎每次都会用不加索引的话数据量上千后排序和筛选会有明显卡顿。2.3 给表加索引的优先级按查询习惯来不按字段多少来很多实训文档里写“索引越多越好”这句话对MySQL是完全错误的。索引会加速查询但会拖慢insert和update因为每次写操作都要同步维护索引树。宿舍管理系统里最常用的查询场景是学生列表按学生姓名模糊搜索、宿舍查询按楼栋和房间号精确匹配、报修列表按状态筛选。针对这三种场景索引策略就明确了student表的student_no用唯一索引name字段用普通索引实际使用LIKE keyword%时能命中索引、status这种低基数字段不适合单独建索引但和create_time组合成联合索引时可以用于“待处理报修按时间排序”的查询。在Navicat里导入脚本后可以用SHOW INDEX FROM student;查看当前表上的索引注意看Cardinality数值这个值越接近实际行数说明索引区分度越好。MySQL安装配置教程里通常不会讲这一步但课程设计文档里如果能写清“哪些索引是根据哪条业务SQL设计的”得分会比堆一堆KEY要高。3. 从JDBC到JSP落地连接池参数、DAO模式与JSP页面取值3.1 连接管理别用DriverManager裸连直接上连接池实训项目里最常见的写法是在每个DAO类里写Class.forName(com.mysql.jdbc.Driver)然后DriverManager.getConnection(url, user, password)再在finally里关闭连接。这个写法能跑但有两个问题。第一com.mysql.jdbc.Driver在MySQL 8.x的jdbc驱动里已经被标记为过时新驱动类名是com.mysql.cj.jdbc.Driver同时连接串里必须加serverTimezone和useSSLfalse否则会报时区错误和SSL告警。第二每个请求都新建物理连接伸手党式地从连接池里取一个现成的连接性能完全不同。实训演示时如果同时开着多个页面或者用脚本快速刷新裸连方式很容易把数据库的连接数打满。更稳妥的方案是用Apache DBCP或者阿里的Druid做连接池在webapp/WEB-INF/web.xml里配置Resource并通过JNDI获取数据源。但考虑到很多实训环境用的是Tomcat 8/9直接在项目里用Druid的jar包配合一个工具类读取配置的方式依赖更少、更好解释。public class DBHelper { private static DruidDataSource dataSource; static { try { Properties props new Properties(); props.load(DBHelper.class.getClassLoader().getResourceAsStream(db.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { } } if (ps ! null) { try { ps.close(); } catch (SQLException e) { } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { } } } }这段代码里最关键的是DruidDataSourceFactory.createDataSource(props)它会把properties文件里的配置直接映射到连接池对象上不需要手动一个个set。db.properties里几个必调参数如下driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/dorm?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password123456 initialSize5 maxActive20 maxWait60000 validationQuerySELECT 1 testWhileIdletrueallowPublicKeyRetrieval这个参数只有在MySQL 8.x的caching_sha2_password认证插件下才需要如果你用的MySQL是5.7或更早版本这个参数不写也没事。initialSize设5就够了不要为了“看起来性能好”设成50连接池的启动时间和占用的数据库连接数都会增加实训评分机器的内存往往不大很容易直接把MySQL拖慢。maxWait60000表示从连接池获取连接时最多等60秒如果60秒内拿不到连接就抛异常这样至少能让你从异常栈里看到“wait millis”相关日志而不是让请求无限挂起。3.2 DAO层只做数据访问别把业务逻辑塞进JDBC方法里JSPMySQL的宿舍管理系统代码组织上最容易被扣分的地方是JSP页面里直接写了一堆% %脚本片段里面又套着JDBC查询逻辑。这种写法在开发初期很快但页面稍微复杂一点HTML标签和Java代码混在一起后面改一个字段要翻几百行而且报错时Tomcat只会告诉你这一行编译失败不会告诉你是什么业务逻辑错了。按实训评分标准的套路正确做法是JSP只负责展示数据Servlet接收请求并调用Service/DAO层DAO层只负责SQL操作。关于要不要再套一层Service这个没有硬性规定但为了答辩时好讲我建议至少把“查询某栋楼的空闲宿舍”这类规则写在DAO方法里而不是写在JSP里。public class StudentDAO { public ListStudent findByCondition(String keyword, int page, int pageSize) { ListStudent list new ArrayList(); StringBuilder sql new StringBuilder(SELECT * FROM student WHERE 11 ); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append(AND name LIKE ? ); params.add(% keyword.trim() %); } sql.append(ORDER BY id DESC LIMIT ?, ?); params.add((page - 1) * pageSize); params.add(pageSize); try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student stu new Student(); stu.setId(rs.getInt(id)); stu.setName(rs.getString(name)); stu.setStudentNo(rs.getString(student_no)); stu.setDormRoomId(rs.getInt(dorm_room_id)); stu.setStatus(rs.getInt(status)); list.add(stu); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }这段代码里有两个容易被忽略的细节。第一个是使用了LIMIT ?, ?这是MySQL的分页写法第一个问号是偏移量(page-1)*pageSize第二个问号是每页条数。如果数据库换成Oracle或SQL Server这个SQL语法就不能用实训答辩时老师常问“你的分页SQL在Oracle里能跑吗”这就是考察点。第二个是PreparedStatement的参数下标从1开始而Java的List下标从0开始所以ps.setObject(i 1, ...)容易写错建议自己写分页查询时把参数列表用一个List统一管理下标偏移一目了然。3.3 JSP页面用JSTL和EL表达式循环拼接是死路写完DAO之后剩下的页面展示问题就是JSP模板技术。现在再回头用% %去渲染表格行确实已经过时了。规范做法是引入JSTL标签库用c:forEach循环表格行用${student.name}取数据。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body table border1 tr th学号/th th姓名/th th宿舍/th th状态/th /tr c:forEach items${pageData.list} varstu tr td${stu.studentNo}/td td${stu.name}/td td${stu.dormRoomId}/td td c:choose c:when test${stu.status 1}在住/c:when c:otherwise退宿/c:otherwise /c:choose /td /tr /c:forEach /table /body /html核心的坑在于前缀。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %这行必须原样写对很多人从旧项目里复制过来uri写的是http://java.sun.com/jstl/core少了/jsp路径启动后页面报“Unable to find taglib”错误。另外一个容易踩的是items${pageData.list}的取值路径pageData是Servlet里request.setAttribute(pageData, data)设定的对象list是其中存放的分页列表字段。如果属性名对不上JSP页面不会直接报错只会输出空表格经验不足的同学往往会怀疑Java代码写错了。JSP个人信息展示页面在这个项目里就是一个独立的小页面显示当前登录用户的账号、姓名、角色、最近登录时间。设计上不要用session.getAttribute直接输到页面上而是通过一个jsp:useBean或者直接把用户对象塞进request里再配合EL表达式取值这样页面代码更干净。如果是Eclipse或IDEA里部署的Web项目可以查看Tomcat的工作目录jsp编译class文件保存在哪里这个热搜问题的答案是work/Catalina/localhost/项目名/org/apache/jsp/JSP页面第一次被访问时会在这里被编译成Java源文件和class文件。遇到页面显示500错误时去这个目录看一眼对应的_class文件的内容能快速定位是标签库没引对还是EL表达式取值为空。4. 数据库连接、中文乱码与浏览器离开页面提示的三连坑4.1 MySQL 8.x安装配置时先确认驱动和URL再写代码mysql安装配置教程很多但宿舍管理系统这种老牌JSP项目常见的环境组合是JDK 1.8 Tomcat 8.5 MySQL 5.7或8.0。如果你是网络安全或软件工程专业做课程设计很可能老师给的旧代码里写的是com.mysql.jdbc.Driver而你的机器装的是MySQL 8这时候启动项目会直接报ClassNotFoundException。处理办法很简单换成com.mysql.cj.jdbc.Driver并引入mysql-connector-java 8.x版本的jar包。如果是选择MySQL官网下载最新版8.x安装完成后记得在命令行工具里用mysql -u root -p验证能否正常登录并执行一次SELECT VERSION();。比较常见的问题是安装时选的是“记住密码”而不是“设置密码”或者安装过程中指定了密码但连接串里没写对。在IDEA里跑Main方法测试JDBC连接时不通过先不要急着怀疑代码直接点击IDEA右侧的Database面板用同样的url和账号密码测试一下如果那里也连不上问题就在MySQL服务端或网络配置不在你的Java代码里。4.2 中文乱码要从URL、请求、响应三个方向一起堵JSP项目里的中文乱码几乎都是“三处不一致”引起的页面本身的编码、HTTP请求的编码、数据库连接的编码。页面编码在JSP第一行用% page contentTypetext/html;charsetUTF-8 languagejava %来控制注意这个page指令必须写在JSP文件的最顶部前面不能有任何空行或HTML标签。如果页面输出正常但插入数据库后变问号那问题在JDBC连接串里的characterEncodingUTF-8参数以及建表时用的字符集。MySQL的连接url里同时存在useUnicodetrue和characterEncodingUTF-8两个参数时驱动会按后者指定的字符集处理请求和响应的编码。如果建表时某些表漏写了DEFAULT CHARSETutf8mb4用ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;临时转一下也行但如果表里已经有乱码数据转换并不会自动修复已存在的乱码必须先把乱码数据删掉或重置。如果是表单提交后Servlcet里拿到的中文参数乱码确认你的Tomcat版本。Tomcat 8及以上版本默认使用UTF-8处理POST请求体但GET请求的query string默认按ISO-8859-1解析所以中文姓名通过GET方式传参乱码时需要给tomcat的server.xml里的Connector节点加上URIEncodingUTF-8属性。这个细节很多人不知道因为POST请求在JSP实训项目里更常见GET搜索功能往往被忽略。我的习惯是写一个EncodingFilter在doFilter里统一设置request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)这样至少能覆盖表单POST提交的情况。4.3 屏蔽jsp离开页面提示是浏览器策略不是代码问题热词里有一条“屏蔽jsp离开页面提示”这个场景和宿舍管理系统里的“正在填写报修单”很接近。当JSP页面注册了beforeunload事件时浏览器会在用户点击导航链接或关闭页面时弹出“确定要离开此页面吗”的系统对话框用来防止表单内容丢失。window.addEventListener(beforeunload, function (e) { var content document.getElementById(repairContent).value; if (content.trim() ! ) { e.preventDefault(); e.returnValue ; } });这段代码的逻辑是只有当报修内容的文本框非空时才拦截离开动作空表单直接放行。e.returnValue 是兼容老版本浏览器的写法现在大多数现代浏览器已经忽略了提示文案只保留默认的固定文本。如果你的页面怎么点都弹这个提示问题通常出在beforeunload绑定后没有在表单提交时解绑比如用户已经点“提交”按钮了但事件监听器还存在导致提交按钮本身也触发了离开页面的拦截逻辑。解决办法是给form的onsubmit加一个标记位让beforeunload里检测到isSubmitting为true时直接return不弹窗。这个细节很适合在实训文档里写成“项目难点”因为老师看到你主动解决了用户体验问题而不是只会调数据库印象分会不一样。5. JSP项目里的Tomcat部署检查、断点命中与class文件的实际位置5.1 把项目挂到Tomcat的webapps下启动后先看三个日志宿舍管理系统在实训验收时要当场在浏览器里跑起来所以部署环节一定要熟练。在IDEA里点Run按钮启动Tomcat是最快捷的方式但有些机房机器没有配置IDEA的Tomcat插件或者教室电脑只有Eclipse这时候就需要手动部署。手动部署的做法把项目导出成war包放到Tomcat安装目录的webapps文件夹下然后执行bin/startup.batWindows或bin/startup.shLinux。启动后不要急着开浏览器先看三个日志文件logs/catalina.out或logs/catalina.日期.log、logs/localhost.日期.log、logs/manager.日期.log。启动报错时最常见的提示是ClassNotFoundException或NoClassDefFoundError这通常意味着项目里用到的jar包没有打进WEB-INF/lib目录。网络上很多人喜欢把mysql-connector.jar放在Tomcat的lib目录下这样做Tomcat启动不会报错但如果你之后换一台机器部署忘了把这个共享jar包一起拷过去新环境就起不来了。我把项目的数据库驱动jar包固定放在webapp/WEB-INF/lib/目录下随项目一起走。5.2 当前不会命中断点源码与部署class不一致时的自检路径用IDEA启动Tomcat调试时有时候打了断点却出现“当前不会命中断点”的提示尤其是JSP页面对应的Java类。这个问题的原因和IDEA的编译输出路径有关JSP页面在运行时才被Tomcat动态编译成class而IDEA的断点是对ide编译好的目标类生效的两者来源不同所以命不中。处理办法是改用JSP调试插件或者直接在JSP文件里打印日志到Tomcat的logs目录而不是死磕断点。对于Servlet类和DAO类来说如果也出现“当前不会命中断点”需要检查项目里是否存在两个相同路径的class文件。Tomcat的work目录下会输出JSP编译的class但Servlet和业务类的class是从WEB-INF/classes加载的。如果你的项目配置文件里有多个source directory并且编译输出到了不同位置IDEA可能调试的是其中一份而Tomcat加载的是另一份。用javap -c看下class文件路径或者直接在IDEA里CtrlShiftF9重新编译该模块一般能解决。这个知识点很适合写进数据库课程设计的答辩文档里属于真正的“实训踩坑记录”。5.3 MySQL workbench使用教程里的反向工程可以拿来生成文档如果实训报告里要画ER图用MySQL Workbench的Database菜单里的Reverse Engineer功能可以直接从现成的数据库导出ER图。操作步骤是连接数据库选择数据库然后执行逆向工程工具会生成所有表的外键关系图直接保存成图片放到课程设计文档里。这里的坑在于如果建表时没有给外键命名或者外键类型不一致逆向工程生成的连线会比预期少所以在可视化之前先确认表名是以复数还是单数命名以及外键字段和引用字段的数据类型是否完全一致比如INT UNSIGNED和INT就不能兼容。数据库脚本的管理可以顺手用一下mysqldump导出热备份命令是mysqldump -u root -p --default-character-setutf8mb4 --databases dorm dorm_backup.sql这条命令导出的是dorm整个数据库包含建库语句和所有表数据。--default-character-setutf8mb4是为了防止导出时把中文字符转义成乱码不加这个参数的话在Windows控制台下导出的SQL文件很可能在重新导入时出现“Incorrect string value”错误。导入时用mysql -u root -p dorm_backup.sql前提是先确认目标机器的MySQL版本和字段类型兼容比如utf8mb4_0900_ai_ci排序规则在MySQL 5.7里是不存在的直接导入会报“Unknown collation”错误这种情况要把SQL文件里的排序规则全局替换成utf8mb4_general_ci。5.4 追加验证用5000条测试数据压出慢查询再决定要不要加复合索引当系统基础功能和部署流程全部走通后最后一件事是验证数据库设计是否真的顶得住评分时的演示压力。我习惯往student和repair表里批量插入几千条测试数据模拟高年级学生全部入住的情况然后看几个核心查询的响应时间。INSERT INTO student (student_no, name, gender, college, major, dorm_room_id, status) SELECT CONCAT(2024, LPAD(n, 4, 0)), CONCAT(测试生, n), IF(n % 2 0, 男, 女), 信息学院, 软件工程, (n % 20) 1, 1 FROM (SELECT rownum : rownum 1 AS n FROM repair LIMIT 5000) t;上面这条SQL利用repair表的行数生成5000条学生记录学号格式是20240001到20245000宿舍号在1到20号宿舍之间循环分配。插入后打开EXPLAIN SELECT * FROM student WHERE name LIKE 测试生1% AND status 1;如果type列是ALL说明当前索引没有被命中原因是name字段虽然建了索引但LIKE 测试生1%的写法是前缀匹配可以命中可一旦status也加入查询条件MySQL可能放弃name索引而走全表扫描这时需要给(name, status)建一个联合索引并调整SQL里字段的先后顺序与索引一致。索引加完后配合Druid连接池监控页/druid/index.html看当前活跃连接数和SQL执行时间如果maxActive设置过大而MySQL的max_connections默认只有151需要把maxActive调回20以内或者在MySQL的配置里调大连接数上限。实训项目的代码里留这些可量化的优化记录远远比在文档里写“系统性能优越”这种空话更有说服力。本文还有配套的精品资源点击获取
返回列表