ARTICLE DETAIL

资讯详情

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

基于JSP+Servlet的宿舍管理系统:从数据库设计到部署排错全解析

基于JSP+Servlet的宿舍管理系统:从数据库设计到部署排错全解析 简介基于JSPServlet的宿舍管理系统完整项目适合计算机、软件工程及相关专业学生作为JavaWeb课程设计、期末大作业或毕业设计参考。资源包涵盖完整源码、SQL数据库脚本与设计说明书共281个文件核心包含44个Java源文件及对应的class文件、16个JSP页面、32个HTML页面另有JS、CSS样式表与JAR依赖库等压缩包仅4.66MB。目前已有165人学习下载。项目覆盖学生入住登记、宿舍分配、退宿管理、楼栋信息维护及用户登录等典型功能代码采用DAO、Service、Servlet三层结构清晰展示MVC开发流程通过JDBC实现数据持久化JSP负责动态页面渲染。压缩包内设计说明书对数据库表结构、模块设计与运行环境做了说明可直接对照学习或写入课设报告。代码注释完整运行环境要求明确导入数据库并部署至Tomcat即可运行便于读者快速掌握JavaWeb项目的完整开发脉络是一份高质量的高分课设参考资源。1. 为什么 JSPServlet 的宿舍管理系统值得你亲手做一遍每年 JavaWeb 课设选题里基于 jspservlet 的宿舍管理系统几乎是最常出现的项目之一原因很直白它的业务范围刚好覆盖了学生管理、宿舍分配、访客登记、卫生评比这些高校里真实存在的场景技术栈又卡在 JSP、Servlet、JDBC 和 MySQL 这几样最核心的东西上没有框架帮你包办一切最能检验一个人对请求-响应链路、Session 维持、数据库读写这些基本功有没有真懂。这套东西适合两类人一是马上要交课设、需要一套完整可运行案例做参照的在校生二是想通过拆解一个真实项目把 Servlet 生命周期、过滤器、JDBC 事务这些散概念串成一条线的人。我见过太多人拿到源码直接改名上交答辩时连登录流程都说不清最后被老师问住。把这个项目当作骨架亲手把每一层代码走一遍才是这个课设真正的价值所在。2. 需求先行宿舍管理系统的功能边界与数据库表设计2.1 角色权限与核心流程管理员、宿管和学生各管哪一段宿舍管理系统表面上只是一个“增删改查”项目但它的业务权限其实比想象中要细。完整的角色划分通常是三类系统管理员、宿管员、学生。管理员负责楼栋和房间信息的维护、宿管账号的分配、全局数据的统计宿管员负责具体的入住登记、迁出办理、访客登记和卫生检查学生则只能查看自己的宿舍信息、提交报修或留言。权限不一样能访问的 JSP 页面也就不一样这决定了项目里必须有一套登录校验和角色拦截的机制而不是所有页面都能直接打开。从流程上看核心闭环是“学生入住”管理员先维护好宿舍信息宿管员选择空余床位办理入住学生信息落到 student 表里并关联宿舍房间之后所有围绕这个学生的操作都通过关联字段找到对应的 dorm。另一个高频流程是访客登记访客来访时宿管员记录访客姓名、访问的房间号、来访时间和离开时间这类数据单独建表避免和学生主表耦合。做课设时我一般建议把流程控制在两到三个闭环以内比如宿舍分配、访客登记、卫生评分过多反而会让代码量失控。角色与功能对应关系可以用一张简单的表来澄清角色核心功能对应入口管理员宿舍楼栋/房间维护、宿管账号分配、数据统计admin/ 下页面宿管员入住/迁出办理、访客登记、卫生评分manager/ 下页面学生查看个人宿舍、提交报修、查看通知student/ 下页面2.2 核心表结构学生、宿舍、访客日志与账号表的关联方式数据库设计是这个项目里最值得较真的部分因为后期所有 Servlet 代码都在围着表结构转。常见的表划分是用户账号表统一管理三类角色的登录账号学生表和宿舍表分别描述两个核心实体访客登记表记录临时事件报修表承载学生提出的维护请求。学生表通过 dorm_id 关联宿舍表账号表通过 user_id 和 role 字段关联到具体的学生或宿管这样登录之后跳转到哪个角色主页就能由当前账号的角色字段直接决定。字段设计上有几个地方容易踩坑。一是学号、房间号这类业务编号要单独建字段并且加唯一索引而不是直接用数据库自增主键对外展示因为自增主键一旦被学生看到别人很容易遍历出全部数据。二是宿舍表的 bed_count床位数和 current_count已住人数要拆开存每次入住和迁出都更新 current_count避免每次查空床位都去 count 一遍 student 表。三是所有表建议统一加上 create_time 字段后面做按时间排序或者答辩时展示数据新增记录都会省很多事。如果拆成最小可用的六张表关系是这样组织的user 表存放所有账号与角色student 表通过 user_id 关联 user通过 dorm_id 关联 dormdorm 表通过 building 和 room_no 唯一确定一个房间visit_log 表记录访客事件并关联 dormrepair 表记录报修并关联 student。卫生评分如果要做可以单独建 inspection 表但课设阶段把它合并到 visit_log 旁边做成一张记录表也可以接受。2.3 建库建表 SQL一份能直接执行的初始化脚本拿到课设源码后第一件事就是把数据库建出来。下面是按 MySQL 5.7/8.0 兼容写法整理的初始化脚本包含建库、建表和插入初始管理员账号。字符集统一用 utf8mb4不然插入生僻的姓氏或表情符号时容易报 Incorrect string value 错误。CREATE DATABASE IF NOT EXISTS dorm_manage DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dorm_manage; -- 账号表管理员/宿管/学生统一走这张表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL COMMENT admin/manager/student, user_id INT COMMENT 关联student表或宿管表的ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 宿舍表 CREATE TABLE dorm ( id INT PRIMARY KEY AUTO_INCREMENT, building VARCHAR(20) NOT NULL COMMENT 楼栋号如A栋, room_no VARCHAR(20) NOT NULL, bed_count INT NOT NULL DEFAULT 4, current_count INT NOT NULL DEFAULT 0, tel VARCHAR(20), UNIQUE KEY uk_building_room (building, room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL, gender VARCHAR(4) NOT NULL, phone VARCHAR(20), dorm_id INT, user_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_dorm (dorm_id), CONSTRAINT fk_student_dorm FOREIGN KEY (dorm_id) REFERENCES dorm(id) ON DELETE SET NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 访客登记表 CREATE TABLE visit_log ( id INT PRIMARY KEY AUTO_INCREMENT, dorm_id INT NOT NULL, visitor_name VARCHAR(50) NOT NULL, id_card VARCHAR(30), visit_time DATETIME NOT NULL, leave_time DATETIME, reason VARCHAR(200), KEY idx_dorm (dorm_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 报修表 CREATE TABLE repair ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, content VARCHAR(500) NOT NULL, status VARCHAR(20) DEFAULT 待处理, report_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 初始账号管理员 admin / 123456 INSERT INTO user (username, password, role) VALUES (admin, 123456, admin);这段脚本里有几个细节值得注意。外键约束 fk_student_dorm 用了 ON DELETE SET NULL这样删除宿舍记录时不会把学生数据连带删掉只把学生的 dorm_id 置空符合现实逻辑。user 表单独存密码而不是在 student 表里加密码字段是因为三类角色的登录凭证应该统一管理这也是答辩时老师常问的点。如果你手头项目里密码是明文存 123456那至少加一层 MD5 处理别直接暴露到页面上。建完表以后我建议顺手执行几条 INSERT 语句插入两个宿舍、一名宿管账号和两名学生确保 SELECT 联表查询时有数据可查。很多新手在写 Servlet 时因为没有测试数据反复在空结果集上排错浪费时间不说还不能确定到底是 SQL 写错还是页面取值写错。3. 环境与部署用 IDEA 把源码跑起来需要配置哪些东西3.1 版本搭配JDK、Tomcat、MySQL 与 Servlet API 的兼容关系JavaWeb 课设最怕版本不匹配导致项目起不来。老课设源码大多是几年前写的常见搭配是 JDK 8 Tomcat 8 MySQL 5.7这套组合最稳Servlet 用的还是 javax.servlet 包。如果你的机器装的是 JDK 11 以上Tomcat 建议换成 9.x再往上到 Tomcat 10 就要注意包名变成了 jakarta.servlet老代码里所有 import javax.servlet 的类都会直接编译不过。所以拿到源码第一步不是点运行而是先看 import 语句和 web.xml 的版本声明。我用过的稳妥搭配是JDK 8、Tomcat 8.5、MySQL 5.7 或 8.0、Maven 3.6。这个组合下既有老课设常见的 WebContent 目录结构也能兼容新版 MySQL 驱动。如果项目压缩包里自带 .idea 或者 pom.xml优先利用它来确认作者原本的版本如果只有 lib 目录下的 jar 包就看 servlet-api.jar 和 mysql-connector 的版本前者决定编译级别后者决定连接数据库时用哪一套驱动类。驱动版本是个容易翻车的点。MySQL 5.7 时期用的 com.mysql.jdbc.Driver 在 MySQL 8.0 驱动里已经改名成 com.mysql.cj.jdbc.Driver而且 8.x 驱动连接还会强制校验时区。很多同学数据库明明没什么问题启动后一直报 Failed to determine a suitable driver十有八九就是驱动包和驱动类名没对齐。3.2 数据库连接从 JDBC 工具类到连接池的参数配置课设项目里最常见的数据库连接方式是写一个 JDBC 工具类用一个静态代码块加载驱动、读取配置文件、对外提供 getConnection 方法。这种写法虽然简单但每次请求都创建连接并发一高就卡。另一种稍微正式的做法是用连接池Tomcat 自带的 DBCP 或者第三方的 C3P0 都可以。如果源码里已经写了 DbUtil 工具类我建议保留原结构只把里面的连接参数改成你本地数据库的环境值改动最小、最不容易引入新问题。import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.util.Properties; public class DbUtil { private static String url; private static String username; private static String password; static { try (InputStream in DbUtil.class.getClassLoader().getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws Exception { return DriverManager.getConnection(url, username, password); } }上面这段静态块在类加载时执行一次driver 只注册一次后面的 getConnection 每次都是新建连接。配套的 db.properties 放 src 目录下里面的 jdbc.url 要看 MySQL 版本区别对待。MySQL 5.7 可以写 jdbc:mysql://localhost:3306/dorm_manage但连接 MySQL 8.0 时至少要加 serverTimezoneAsia/Shanghai 和 useSSLfalse否则会报时区错误或 SSL 握手警告同时建议加上 allowPublicKeyRetrievaltrue这个参数不写的话很多时候会看到 Public Key Retrieval is not allowed 的诡异报错。配置文件的写法可以顺手一起给出来jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/dorm_manage?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你自己的数据库密码注意 db.properties 里不要加多余的空格username 和 password 后面也不要带注释否则解析出来的字符串尾部会带上不可见字符连接时根本看不出来问题在哪。把配置外置到 properties 文件还有一个好处就是换一台电脑运行项目时不用重新编译代码只需要改配置文件里的密码这个细节在答辩演示换机器时非常有用。3.3 IDEA 中的部署方式配置 Tomcat、设置 Deployment 与启动验证在 IntelliJ IDEA 里跑 JavaWeb 项目最忌讳的就是直接点绿色的运行按钮却发现根本没有 Tomcat 选项。正确路径是打开 Run - Edit Configurations左上角加号选择 Tomcat Server - Local然后在 Application server 一栏选你本地装好的 Tomcat 路径。第二件必须做的事是在 Deployment 标签页加 Artifact把项目打成 war exploded 形式Application context 里填根路径比如空的 / 或者 /dorm这一步漏掉的话 Tomcat 启动了但访问 localhost:8080 永远是 404。配置完成后的标准启动动作是先确认 MySQL 已经启动、数据库已导入再启动 Tomcat。控制台里看到 Server startup in 6000 ms 之类的日志就说明容器起来了接着在浏览器里访问 http://localhost:8080/context 为 / 时或 http://localhost:8080/dorm/看到登录页就说明部署成功。如果页面是中文乱码先检查浏览器编码和 JSP 页面的 pageEncoding如果 Tomcat 起都没起来优先看控制台最底部的 Caused by 信息而不是第一行那一大堆异常堆栈真正的错误原因往往在后面。还有一个常见操作是修改数据库密码后必须重启 Tomcat因为静态代码块里连接参数只在类加载时读取一次。很多人改了 db.properties 以后只刷新了页面连接还是旧参数就会反复看到 Access denied for user 的报错以为是密码没改对其实 Tomcat 里跑的还是旧 classpath。我一般习惯在修改配置后同时做一次 Clean 操作把 target 或 out 目录清掉再重启避免读到编译缓存里的旧配置。4. Servlet 与 JSP 的核心实现登录校验、增删改查、分页4.1 登录流程与 Session 控制为什么每个页面都要拦截登录是整个项目安全模型的入口也是老师最可能动手测试的功能点。常见实现方式是 LoginServlet 接收用户名和密码调用 DAO 查 user 表比对成功以后把用户信息放进 Session再按角色重定向到不同的主页。这段代码本身不复杂难点在于如何保证未登录的人不能直接输入 URL 绕过登录页跳到内部页面所以必须配合 Filter 做全局拦截。WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); User loginUser userDao.findByUsernameAndPassword(username, password); if (loginUser ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, loginUser); session.setMaxInactiveInterval(30 * 60); resp.sendRedirect(index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }这段代码里的几个选择值得解释。测完用户名密码后用的是 sendRedirect 而不是 forward是为了避免登录成功后刷新页面时重复提交表单Session 设置了 30 分钟有效期是模拟真实系统的超时机制。findByUsernameAndPassword 在 DAO 层用 PreparedStatement 实现返回 null 代表账号不存在或密码错误这种做法不会把具体哪一种错误暴露给用户安全性更好。过滤器拦截的优先级和范围是真正容易漏的地方。如果 Filter 的拦截路径配置成 /admin/* 和 /student/*那用户没登录直接访问这些路径会被拦下来但如果项目里还有 manager 目录、或者部分页面用了 .jsp 后缀直接访问拦截范围不够就会出现绕过登录的漏洞。所以我一般会在 Filter 里放行登录页、CSS、JS 和图片资源其余请求全部走 Session 校验。WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); // 放行登录相关资源和静态文件 if (uri.contains(/login) || uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.jpg) || uri.endsWith(.png)) { chain.doFilter(request, response); return; } HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }注意这里的 getSession(false) 和 getSession() 的区别传 false 表示如果当前没有 Session 就返回 null而不是强行创建一个新 Session。很多人在拦截器里用 getSession() 导致每次被拦截的请求都会新建一个空 Session本来想拦没登录的用户结果每个请求都“被登录”了黑匣子问题就出在这里。4.2 学生管理模块用一个 Servlet 处理多种操作的请求分发课设里学生管理模块会有列表、新增、编辑、删除四个操作如果为每个操作单独建一个 Servlet类会越来越多配置也麻烦。更实用的做法是建一个 StudentServlet通过请求参数 action 的值来分发到不同的处理方法。分发逻辑可以写在 service 方法里也可以重写 doGet 和 doPost 让它们都调用统一入口。WebServlet(/student) public class StudentServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String action req.getParameter(action); if (list.equals(action)) { list(req, resp); } else if (add.equals(action)) { add(req, resp); } else if (update.equals(action)) { update(req, resp); } else if (delete.equals(action)) { delete(req, resp); } else { list(req, resp); } } }之所以用 service 方法接收所有请求是因为列表和删除通常用 GET而新增和编辑表单用 POST统一入口以后前端只需在链接里带上 action 参数Servlet 这边不用纠结方法类型。分发的目标方法里list 负责查数据后转发到 list.jspadd 处理完表单后重定向回列表delete 执行完删除后同样重定向。这里有个小小的设计习惯执行完修改类的操作后用重定向避免用户按 F5 刷新时表单被再次提交。在 delete 方法里业务上要考虑一个问题删学生是彻底删除还是把状态改成“已迁出”真实宿舍管理里学生迁出是有记录的不应该物理删除。课设里可以在 student 表加一个 status 字段删记录时改成“已迁出”而不是 delete 一行这样后续做统计时历史数据还在而且答辩时能多讲一个业务思考点。4.3 JSP 页面取值EL 表达式配合 JSTL 替代 Java 脚本很多老课设的 JSP 页面里满屏都是 % % 脚本片段能跑但非常难看答辩时老师翻到这种页面第一印象就是“代码习惯不好”。用 EL 表达式和 JSTL 标签可以把页面里的 Java 代码清干净让 JSP 只负责展示。Servlet 里把数据放到 request 或 session 后转发到 JSP页面里直接用 ${} 取值。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle学生列表/title/head body table border1 tr th学号/thth姓名/thth性别/thth宿舍/thth操作/th /tr c:forEach items${studentList} varstu tr td${stu.studentNo}/td td${stu.name}/td td${stu.gender}/td td${stu.dorm.building}-${stu.dorm.roomNo}/td td a hrefstudent?actioneditid${stu.id}编辑/a a hrefstudent?actiondeleteid${stu.id} onclickreturn confirm(确认删除该学生吗)删除/a /td /tr /c:forEach /table /body /html页面上 ${stu.dorm.building} 这种写法依赖于 Student 类里有一个类型为 Dorm 的属性 dorm如果实体类里没有这个对象而是只存了一个 dormId 字段那这里就要先查出宿舍名再放回 map麻烦很多。所以我建议实体类走正规的关联设计Student 里有 Dorm dorm 属性DAO 在做查询时把关联的宿舍一起查出来填进去。这样的好处是 JSP 取值直观也符合 JavaBean 的规范。用 JSTL 的 forEach 遍历集合时要注意 items 里的值必须已经在 Servlet 里 setAttribute 了。常见的新手错误是在 Servlet 里把数据放到 request 后没有调用 forward而是直接重定向到 JSP数据全部丢失页面上什么都没显示。因为重定向是浏览器重新发起一次请求原来的 request 属性早就没了。4.4 分页查询limit 参数、页码计算与总页数推导数据一旦超过几十条不分页的列表页就会又长又难用。分页的实现思路是Servlet 从请求参数里拿到当前页码计算偏移量调用 DAO 的 findByPage(offset, pageSize) 查出当前页数据同时查出总记录数算出总页数后一并转发给 JSP。int page 1; int pageSize 10; String pageStr req.getParameter(page); if (pageStr ! null !pageStr.isEmpty()) { page Integer.parseInt(pageStr); } int offset (page - 1) * pageSize; ListStudent list studentDao.findByPage(offset, pageSize); int total studentDao.count(); int totalPages (total % pageSize 0) ? (total / pageSize) : (total / pageSize 1); req.setAttribute(studentList, list); req.setAttribute(currentPage, page); req.setAttribute(totalPages, totalPages); req.getRequestDispatcher(student_list.jsp).forward(req, resp);对应的 SQL 在 MySQL 里是 SELECT * FROM student LIMIT ?, ?第一个 ? 是偏移量第二个 ? 是每页条数。计算总页数时用 (total pageSize - 1) / pageSize 这个公式更简洁但直接写三目运算在课设代码里更容易让别人看懂。pageSize 一般固定成 10但也可以把它做成页面上可调的下拉框传参后 Servlet 接受新的 pageSize这个扩展点在答辩时能加分。分页最容易出 bug 的边界是当前页超出总页数、总记录数为 0、以及删除最后一页的最后一条记录后页码没回退。我一般会在代码里做一层保护比如 page 小于 1 时强制置 1page 大于 totalPages 时强制置为 totalPages。这种健壮性处理不需要多复杂的代码但在演示时能避免很多尴尬。5. 课设避坑指南部署失败、中文乱码与数据库连接问题排查5.1 现象Tomcat 能启动访问项目一直 404原因项目没有打包进 Tomcat 的部署列表或者 Application context 和实际访问路径不一致。IDEA 里很多同学直接运行了 Tomcat 却忘记在 Deployment 标签页添加 ArtifactTomcat 起来后加载的还是默认 ROOT 项目还有人是 war exploded 加了但 context 填了 /dorm自己却用 localhost:8080 访问路径对不上自然 404。解决打开 Run - Edit Configurations在 Deployment 里添加 Artifact选择 Web Application: ExplodedApplication context 统一填 /。改完后重启 Tomcat在控制台的 Starting ProtocolHandler 日志之后看是否打印了当前项目的 context 路径。如果你手头项目用的是注解 Servlet而且 WEB-INF 下没有 web.xml也请保留一个空的 web.xml 文件有些老版本 Tomcat 对完全无 web.xml 的项目会有部署兼容问题。5.2 现象数据库连接时报 Public Key Retrieval is not allowed原因MySQL 8.0 默认使用 caching_sha2_password 认证插件首次连接时需要通过 RSA 公钥传输密码而 JDBC 驱动默认不允许从服务器获取公钥导致连接被拒绝。这个错换了连接池或者驱动版本也不会消失因为它本质上是账号认证方式决定的。解决在 jdbc.url 末尾加上 allowPublicKeyRetrievaltrueuseSSLfalse如果仍然报错就把 MySQL 连接账号的认证方式改成 mysql_native_passwordSQL 是 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码。注意改完以后要 FLUSH PRIVILEGES 并重启项目。另外驱动类名必须用 com.mysql.cj.jdbc.Driver老驱动类在新版驱动里已经不适用。5.3 现象JSP 页面中文全部显示成问号或乱码原因乱码是编码链条上某一环没对齐常见的有三处JSP 文件本身的保存编码不是 UTF-8、数据库连接串里没有 characterEncodingutf8、Tomcat 处理请求参数时的解码编码不对。如果只有前端页面正常而数据里的中文乱码多半是数据库连接串的问题如果连页面上的静态中文都是乱码那就是文件编码或 pageEncoding 的问题。解决统一把 JSP 文件在 IDEA 右下角改编码为 UTF-8页面头部的 pageEncoding 也写 UTF-8连接串加上 useUnicodetruecharacterEncodingutf8MySQL 的 character_set_server 设置为 utf8mb4Tomcat 8 及以上版本在 server.xml 的 Connector 上设置 URIEncodingUTF-8。改完以后把 IDEA 的 Settings - File Encodings 三处都设置成 UTF-8清理缓存重启乱码基本就绝迹了。5.4 现象Servlet 明明写了注解访问时仍然 404 或者 500原因404 时先看访问路径里有没有带上项目 context 路径比如注解是 WebServlet(/student)项目 context 是 /dorm那完整路径是 /dorm/student不是 /student。500 则要翻控制台堆栈最常见的是 ClassNotFoundException说明 lib 下的 jar 包没部署到 Tomcat 的 WEB-INF/lib 里IDEA 里如果用的是 Maven 项目还要检查依赖是否已经同步到本地仓库。解决访问路径统一用相对当前应用的写法JSP 里用 ${pageContext.request.contextPath} 拼前缀避免硬编码 /dorm 导致换环境就废。jar 没加载时打开 Project Structure - Artifacts在 WEB-INF/lib 下 Add Copy of Library把项目依赖加进去重新构建后再启动。改完这些还报错就把 Tomcat 的 work 目录清理掉让 JSP 重新编译一次很多“改完不生效”的怪现象其实是缓存。6. 从“能跑”到“答辩高分”5 个低成本的优化点与验证方法6.1 先做分层Servlet 里不要再写 JDBC拿到的源码如果所有 JDBC 都写在 Servlet 里我的第一个优化动作是抽出 StudentDao 类把连接获取、SQL 执行、结果集封装全部收进 DAO。Servlet 里只保留参数接收、调用 DAO、设置返回结果。这一步改动不需要设计模式知识只是把代码搬个家但答辩时能明确说出“表现层与数据访问层分离”比堆一堆框架词汇有用得多。6.2 密码加密与 SQL 注入防护PreparedStatement 是底线源码里如果用 Statement 字符串拼接 SQL必须改成 PreparedStatement。对比很直观Statement 里用户输入的内容可能改变 SQL 语义而 PreparedStatement 的 SQL 结构在编译时就固定了参数只作为值传入。密码明文存储至少要加一层 MD5 或 SHA-256不需要太复杂但这一步能在答辩时展示你的安全意识。PreparedStatement ps conn.prepareStatement( SELECT * FROM user WHERE username ? AND password md5(?)); ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery();6.3 答辩路径从设计说明书到功能演示的讲法设计说明书一般包含需求分析、数据库设计、功能实现和测试四块答辩时按这个结构讲十分钟最稳。先花两分钟讲清楚系统解决什么问题、有哪些角色再用数据库 E-R 图和表结构说明关系接着演示登录和各模块操作最后主动提一个已知的不足和你想好的改进方案。演示时手机关网数据库提前导入数据浏览器标签页预先打开登录页这些细节让你在台上更从容。我自己每次答辩前会至少完整重启十次项目把常见的启动报错、乱码和连接错误截图放进设计说明书的附录里作为“问题记录”反而比堆一堆技术背景更让老师相信这部分工作是真实做出来的。把课设当工程做而不只是交作业收获会比分数更多。希望这些经验能帮到你的宿舍管理系统顺利跑通、高分过关。本文还有配套的精品资源点击获取
返回列表