ARTICLE DETAIL

资讯详情

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

Servlet+JSP+MySQL宿舍管理系统开发实战:从原理到部署

Servlet+JSP+MySQL宿舍管理系统开发实战:从原理到部署 简介基于ServletJSPMySQL构建的学生宿舍管理系统主要面向JavaWeb初学者、期末大作业和毕业设计人群。源码已在本地编译运行通过评审得分98分难度适中并经助教老师审定可直接用于课程设计或项目二次开发也可作为学习Servlet与JSP协作的完整范例。系统覆盖宿舍管理常见的数据与操作流程配套使用教程说明环境配置与运行方法适合理解JavaWeb前后端交互、SQL查询及分层开发思路。资源共202个文件压缩包仅7.21MB其中JSP页面22个和Java类21个构成核心业务代码SQL脚本便于快速建库CSS/JS/PNG等资源用于前端展示与交互Bootstrap相关文件提供响应式样式支持另有jar依赖库、HTML静态页及配置文件目录结构清晰便于定位与修改。目前已有248人学习使用适合需要快速上手JavaWeb项目、完成课设或毕设的同学参考。1. 从课程设计到生产代码ServletJSPMySQL宿舍管理系统在2025年的价值很多人在看到“ServletJSPMySQL”这种组合时第一反应是“这不是Java Web课程设计的标配吗”。确实在Spring Boot一统天下的今天这套技术栈看起来像是上个时代的产物。但恰恰是这种判断的偏差让大量从业者在面试和实际维护中栽了跟头——你接手的老系统、外包交付的项目、甚至是某些政府教育机构的内部平台依然跑在Servlet容器里。学生宿舍管理系统作为Java Web最经典的业务场景正好可以完整展示这套组合的运作逻辑从请求分发到会话保持从JDBC连接到前端渲染每一步都暴露了Web框架底层的真实机制。这篇教程要解决的不是“如何用Spring Boot做个宿舍系统”而是“在ServletJSPMySQL这个技术栈约束下如何把学生管理、宿舍分配、来访登记这些事务做成一个能跑、能查、能部署的真实系统”。适合正在做课程设计的学生、需要接手传统Java Web项目的一线开发、以及想搞懂框架底层原理的工程师。文章会用完整源码结构和可执行步骤带你走通从建表到部署的全过程。2. JavaWeb技术栈选型为什么ServletJSP在宿舍管理场景依旧成立2.1 Servlet容器与JSP页面渲染的核心机制Servlet本身是Java EE规范中处理HTTP请求的组件它没有绑定任何框架。JSP在运行时会被容器翻译成Servlet源码再编译成class所以你和页面模板打交道的本质还是在写Servlet。用Eclipse创建基于Maven的Servlet项目时实际生成的pom.xml只需要引入javax.servlet-api和jsp-api两个依赖即可dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency /dependencies这里有一个关键参数scope为provided意味着这两个依赖在运行时由Tomcat提供打包时不会被打入WAR或JAR中。注意这里的版本号对应的是Servlet 4.0规范对应Tomcat 9.x如果你的运维环境用的是Tomcat 10包名会从javax.*变成jakarta.*。宿舍管理系统通常部署在旧版Tomcat上所以默认按javax命名空间来写。JSP页面在第一次被访问时会经过翻译、编译、加载、实例化四个阶段这也是为什么“Lazy Loading”模式下首屏请求会慢。你可以通过Tomcat的server.xml把reloadable设为true来开启热加载但这只建议在开发环境使用生产环境开启会导致频繁的类加载器重建和PermGen内存泄漏。2.2 请求流程中的DispatcherServlet与双WebApplicationContext概念这里需要纠正一个常见的认知混淆DispatcherServlet并不是Spring MVC独有的概念。在纯Servlet开发中web.xml中配置的Servlet映射也可以视为一种Dispatcher的雏形servlet servlet-nameStudentManage/servlet-name servlet-classcom.dorm.servlet.StudentServlet/servlet-class /servlet servlet-mapping servlet-nameStudentManage/servlet-name url-pattern/student/*/url-pattern /servlet-mapping当所有/student/*的请求都落到这个Servlet时它就是业务层面的Dispatcher。对于使用Spring框架的JavaWeb项目容器启动时会创建两个上下文ContextLoaderListener初始化的RootWebApplicationContext负责加载数据源、事务等全局BeanDispatcherServlet初始化时创建的ServletWebApplicationContext负责加载控制器、视图解析器等Web组件。这个双上下文机制是面试中高频考察点你可以通过ContextLoader.getCurrentWebApplicationContext()来获取根上下文但实际操作中更常见的是在applicationContext.xml中用context:component-scan扫描Service和DAO层在spring-servlet.xml中专门扫描Controller层。如果你在启动时看到类似NoClassDefFoundError: org/springframework/boot/web/servlet/support/SpringBootServletInitializer的错误说明你的项目虽然用了Servlet API但依赖里混入了Spring Boot的嵌入容器类。宿舍管理系统如果用Maven构建检查是否在pom.xml错误引入了spring-boot-starter-web移除后清理Maven仓库缓存即可。2.3 JSP页面渲染的组件化思路JSP页面在宿舍系统里承担三重角色数据录入、列表展示、操作反馈。纯粹用scriptlet写Java代码在JSP里会快速变成灾难一个合格的页面应该把Java代码量控制在最小范围。常见的做法是使用JSTL标签库% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title学生列表/title /head body table border1 thead tr th学号/th th姓名/th th宿舍号/th th操作/th /tr /thead tbody c:forEach items${students} varstu tr td${stu.studentNo}/td td${stu.name}/td td${stu.dormName}/td td a href${pageContext.request.contextPath}/student?actioneditid${stu.id}编辑/a a href${pageContext.request.contextPath}/student?actiondeleteid${stu.id} onclickreturn confirm(确定删除?)删除/a /td /tr /c:forEach /tbody /table /body /html逻辑说明${students}是Servlet在请求作用域里setAttribute(students, list)设置的数据集合c:forEach遍历这个集合并把当前迭代项赋值给stu变量。${pageContext.request.contextPath}动态获取项目部署路径这样做的好处是无论最后打成WAR放到Tomcat的webapps下以什么名称部署页面里的超链接都不会因为上下文路径变化而失效。JSP页面有个很实际的坑是个人信息的展示页面需要屏蔽离开页面时的提示。浏览器默认会在页面有未提交表单数据时弹出离开确认框。你可以在页面的body标签上挂载onbeforeunload事件来定制行为window.onbeforeunload function(e) { // 页面发生导航时触发, 如果不做任何事, 大多数浏览器会直接放行 // 如果需要拦截, 需要设置returnValue // e.returnValue 确定离开吗?; // 旧式写法 };但注意这个事件在现代浏览器中不能自定义提示文案只能通过赋值e.returnValue触发默认弹窗。如果只是想静默离开直接用空函数覆盖即可不要在函数体里放任何return语句。3. 数据库设计与JDBC访问层宿舍管理系统的数据地基3.1 MySQL建库建表与索引设计宿舍管理系统的核心表有四张student学生、dormitory宿舍、visit_log来访登记、repair报修。建表时需要注意字符集和排序规则MySQL 5.7和8.0的默认字符集分别是latin1和utf8mb4如果你的mysql安装教程里没有特别指定在8.0环境里建表显式声明字符集仍然是最稳妥的做法CREATE DATABASE dorm_management DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dorm_management; CREATE TABLE dormitory ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 宿舍ID, dorm_name VARCHAR(20) NOT NULL UNIQUE COMMENT 宿舍楼栋房号, capacity INT NOT NULL DEFAULT 4 COMMENT 额定人数, current_count INT NOT NULL DEFAULT 0 COMMENT 当前入住人数, manager_name VARCHAR(50) COMMENT 楼管员姓名, manager_phone VARCHAR(20) COMMENT 楼管员电话 ) ENGINEInnoDB COMMENT宿舍信息表; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学生ID, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender ENUM(男,女) NOT NULL COMMENT 性别, phone VARCHAR(20) COMMENT 联系电话, dorm_id INT COMMENT 宿舍ID, entry_date DATE COMMENT 入住日期, status TINYINT DEFAULT 1 COMMENT 1-在住 0-退宿, CONSTRAINT fk_student_dorm FOREIGN KEY (dorm_id) REFERENCES dormitory(id) ) ENGINEInnoDB COMMENT学生信息表; CREATE INDEX idx_student_dorm_id ON student(dorm_id); CREATE INDEX idx_student_name ON student(name);参数说明idx_student_dorm_id是外键字段的索引这个索引的主要作用是加速“查询某间宿舍居住的所有学生”这种高频操作idx_student_name索引是针对“按名字模糊搜索学生”场景若你的系统有WHERE name LIKE 张%这种前缀匹配查询这个索引会生效。如果业务上经常按“性别宿舍号”组合查询可以考虑建联合索引(gender, dorm_id)但联合索引的字段顺序必须按查询条件的最左前缀原则来排。MySQL 8.0中的ENUM类型在某些场景下可能引起UPDATE语句的隐式转换问题实际业务中更推荐使用TINYINT加comment标注含义。status字段的设计是为了保留学生历史数据删除操作只做逻辑删除这样宿舍楼栋的入住统计才不会因为退宿学生而丢失记录。3.2 JDBC连接池与PreparedStatement防注入JDBC连接是重量级资源每次请求都创建物理连接的性能损耗不可接受。最常见的做法是用C3P0或Druid连接池初始化数据库连接在web.xml的listener中配置DruidDataSourcecontext-param param-namejdbcUrl/param-name param-valuejdbc:mysql://localhost:3306/dorm_management?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8/param-value /context-param context-param param-nameusername/param-name param-valueroot/param-value /context-param context-param param-namepassword/param-name param-value123456/param-value /context-param在代码里读取以上参数并创建连接池import com.alibaba.druid.pool.DruidDataSource; public class DbUtil { private static DruidDataSource dataSource; static { dataSource new DruidDataSource(); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/dorm_management?useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); // 连接池初始连接数 dataSource.setMaxActive(20); // 最大活跃连接数 dataSource.setMinIdle(3); // 最小空闲连接数 dataSource.setMaxWait(3000); // 获取连接的超时时间, 单位毫秒 } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } // 此处不是真正关闭物理连接, 而是归还到连接池 } }关键逻辑DruidDataSource的连接关闭不会销毁物理连接而是把连接对象恢复到池中。setMaxWait(3000)的数值含义是当连接池无空闲连接且已到达setMaxActive(20)上限时线程等待获取连接的最长毫秒数超过后直接抛出SQLTimeoutException。在宿舍管理这种并发量不高的系统里initialSize设为5、maxActive设为20足够应对。查询学生列表时必须使用PreparedStatement而不能直接用Statement拼接SQLpublic ListStudent findStudentsByDorm(String dormName) { ListStudent list new ArrayList(); String sql SELECT s.*, d.dorm_name FROM student s JOIN dormitory d ON s.dorm_id d.id WHERE d.dorm_name LIKE ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % dormName %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student stu new Student(); stu.setId(rs.getInt(id)); stu.setStudentNo(rs.getString(student_no)); stu.setName(rs.getString(name)); stu.setGender(rs.getString(gender)); stu.setDormName(rs.getString(dorm_name)); list.add(stu); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码里ps.setString(1, % dormName %)的参数绑定机制是把用户输入当作字符串字面量传给MySQL驱动驱动会对单引号、反斜杠等特殊字符做转义处理从根本上堵住 OR 11这类SQL注入。参数说明try-with-resources语法会在代码块结束后自动关闭Connection、PreparedStatement和ResultSet这样即使业务代码抛异常也能正确释放数据库连接。3.3 MySQL的UPDATE与子查询更新语法差异宿舍管理中最为常见的操作是分配宿舍把学生入住信息写入student表同时把宿舍的current_count加1。两条UPDATE语句如果按先后顺序执行可能在中间状态崩溃时导致数据不一致。这时可以使用MySQL的UPDATE ... JOIN语法实现原子更新UPDATE dormitory d JOIN student s ON s.dorm_id d.id SET d.current_count d.current_count 1 WHERE s.student_no 20250001;它的逻辑是把符合条件的s.student_no对应的宿舍行直接做current_count自增。另一种场景是“退宿时把某宿舍的学生搬走”更新子查询的常规写法是-- 将学号为20250001的学生从原宿舍迁出, 同时原宿舍人数减1 UPDATE dormitory SET current_count current_count - 1 WHERE id (SELECT dorm_id FROM student WHERE student_no 20250001);这里有个MySQL经典报错不允许在UPDATE语句的WHERE子查询中直接引用同一张被更新的表。如果上述SQL中student和dormitory换成同一张表就会出现You cant specify target table for update in FROM clause错误。解决手段是把子查询二次包装UPDATE dormitory SET current_count current_count - 1 WHERE id (SELECT dorm_id FROM (SELECT dorm_id FROM student WHERE student_no 20250001) AS tmp);外层多包的一层SELECT只是让MySQL产生一个临时派生表从而绕过“不能在同一语句中同时查询和更新同一张表”的限制。在宿舍管理系统中这种写法常用于学生调换宿舍时的两个宿舍人数变更。4. 业务代码分层与会话管理从Servlet到JSP的完整链条4.1 建立MVC分层结构一个基于Servlet的JavaWeb项目不需要引入Spring就能实践清晰的分层entity包放实体类dao包放JDBC数据库访问service包放业务逻辑servlet包放控制器filter包放编码和权限过滤。宿舍管理系统的典型包结构如下com.dorm.entity.Student com.dorm.entity.Dormitory com.dorm.entity.VisitLog com.dorm.dao.StudentDao com.dorm.dao.DormitoryDao com.dorm.service.StudentService com.dorm.service.impl.StudentServiceImpl com.dorm.servlet.StudentServlet com.dorm.servlet.DormitoryServlet com.dorm.filter.EncodingFilter com.dorm.filter.LoginFilter实体类Student把数据库表的字段映射为Java属性其中宿舍名称是跨表冗余字段可以额外加一个dormName属性而不需要对应数据库列public class Student { private int id; private String studentNo; private String name; private String gender; private String phone; private int dormId; private Date entryDate; private int status; private String dormName; // 非表字段, 用于页面展示 // 省略getter和setter }DAO层每个方法的入参和出参尽量使用实体类或int、String这种简单类型避免把ResultSet泄露到上层。Service层聚焦业务规则例如“分配宿舍”必须同时执行学生信息和宿舍人数两个变更不满足条件时抛出业务异常public boolean assignDorm(Student student) { Dormitory dorm dormitoryDao.findById(student.getDormId()); if (dorm.getCurrentCount() dorm.getCapacity()) { throw new BusinessException(宿舍已满员, 当前容量 dorm.getCapacity()); } boolean flag studentDao.insert(student); if (flag) { dormitoryDao.increaseCurrentCount(dorm.getId()); } return flag; }上面的BusinessException是自定义运行时异常抛出后由Servlet统一捕获并跳转到带错误提示的JSP页面。4.2 学生信息展示页面的Servlet路由设计在StudentServlet的doGet和doPost里通过action参数分发具体操作。一个简单的操作分发写法如下WebServlet(/student) public class StudentServlet extends HttpServlet { private StudentService studentService new StudentServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (action null) action list; switch (action) { case list: listStudents(req, resp); break; case edit: toEditPage(req, resp); break; case delete: deleteStudent(req, resp); break; default: listStudents(req, resp); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String action req.getParameter(action); if (save.equals(action)) { saveStudent(req, resp); } else { doGet(req, resp); } } private void listStudents(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListStudent studentList studentService.findAll(); req.setAttribute(students, studentList); req.getRequestDispatcher(/student_list.jsp).forward(req, resp); } }参数说明WebServlet(/student)注解等价于在web.xml中配置的url-pattern/student/url-pattern。doGet和doPost各自处理不同类型的请求POST专门处理表单提交避免在浏览器的地址栏直接暴露敏感操作。req.getRequestDispatcher(/student_list.jsp).forward(req, resp)是服务端跳转浏览器URL不会变化req作用域里的students属性可以在JSP里直接用${students}访问。切勿使用resp.sendRedirect(student_list.jsp)来跳转因为重定向是两次请求第一次请求中setAttribute的数据会丢失。toEditPage方法除了把学生的现有数据查出回显到编辑页面外还需要把所有宿舍的列表一并放进请求作用域供JSP里的select下拉框渲染。4.3 防止JSP页面在失去焦点时弹出离开提示宿舍管理系统中的编辑页面通常时间较长用户不小心点击其他标签页时浏览器的beforeunload提示会干扰体验。要精准屏蔽这种提示在主页面中绑定事件即可// 系统主框架页面, 屏蔽所有子页面的beforeunload提示 window.onbeforeunload null;如果仍然弹窗问题可能出在JSP里引入了某些第三方组件比如富文本编辑器TinyMCE自带的beforeunload监听。常见的做法是把onbeforeunload事件处理函数显式赋为空这样即使框架内部设置过拦截也会被覆盖。但注意如果是通过addEventListener(beforeunload, handler)注册的监听直接给window.onbeforeunload赋值是覆盖不掉的需要用removeEventListener解绑window.removeEventListener(beforeunload, handler);这行代码需要放在handler函数定义之后调用并且保证handler不是匿名函数。在JSP中推荐把这段逻辑放在body onloadinit()里执行确保页面资源全部加载完成后再解除提示拦截。4.4 基于Session的登录状态与权限控制宿舍管理系统的角色通常分为管理员和宿管员。登录成功后把用户信息写入HttpSessionUser loginUser userService.login(username, password); if (loginUser ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, loginUser); session.setMaxInactiveInterval(30 * 60); // 会话有效期30分钟 resp.sendRedirect(index.jsp); } else { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); }session.setMaxInactiveInterval(30 * 60)中的数值单位是秒意味着用户30分钟内没有动作服务器会自动销毁这个会话。这个参数在Tomcat的web.xml中有默认值30分钟显式设置的好处是可以在不同模块中差异化控制比如管理后台超时时间短一些操作页面超时时间长一些。再写一个LoginFilter拦截所有需要登录的页面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; HttpSession session req.getSession(false); String uri req.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(/login) || uri.contains(/static/) || (session ! null session.getAttribute(loginUser) ! null)) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } } }注意req.getSession(false)和req.getSession()的区别前者不会主动创建新会话在用户未登录时返回null后者会强制创建一个会话。加了false能避免为每个静态资源都新建无用会话降低服务器内存负担。uri.contains(/static/)是放行CSS、JS、图片等静态资源的常用手段防止因未登录导致全站样式丢失。5. 页面交互增强与国际化乱码处理5.1 JSP个人信息展示页面的模板复用宿舍系统的“个人信息展示页面”通常需要展示学号、姓名、宿舍号、联系方式、入住时间等字段。可以抽取一个通用的student_info.jsp模板通过EL表达式和JSTL组合渲染% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title学生个人信息/title /head body div classinfo-card h3学生个人信息/h3 table trtd学号/tdtd${student.studentNo}/td/tr trtd姓名/tdtd${student.name}/td/tr trtd性别/tdtd${student.gender}/td/tr trtd宿舍/tdtd${student.dormName}/td/tr trtd入住日期/tdtdfmt:formatDate value${student.entryDate} patternyyyy-MM-dd//td/tr /table /div /body /htmlfmt:formatDate标签来自JSTL的格式化库它接收一个java.util.Date对象并按指定pattern输出字符串。如果没有这个格式化标签直接输出${student.entryDate}会打印Wed Mar 05 10:30:00 CST 2025这种不友好的默认格式。5.2 全站编码过滤器与POST提交乱码Servlet规范中POST请求的表单数据的字符编码不会自动跟随页面编码。必须在最前面的Filter中强制设置UTF-8WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }chain.doFilter(req, resp)必须放在设置编码之后保证后续所有Servlet和JSP拿到的请求参数已经是解码后的正确字符。如果你在web.xml里配置了多个FilterWebFilter(/*)的拦截顺序是不确定的。你可以在web.xml中显式声明Filter的映射顺序filter-mapping出现的先后顺序就是执行顺序。编码Filter必须放在最前面否则如果权限Filter先执行了sendRedirect编码设置就不再生效。GET请求的乱码问题和POST不同Tomcat 8及以上版本默认对GET请求使用UTF-8解码而老代码里如果使用了new String(req.getParameter(name).getBytes(ISO-8859-1), UTF-8)这类回退写法在Tomcat 9中反而会得到错误结果。正确的做法是修改Tomcat的conf/server.xml中Connector标签增加URIEncodingUTF-8属性而代码层不要做任何二次转码。5.3 前端调用Servlet接口并解析JSON数据学生宿舍管理系统中选择宿舍时通常需要联动显示宿舍的剩余床位。这个功能用传统的表单提交加整页刷新也能实现但体验较差。更常见的做法是用Ajax请求一个专门输出JSON的ServletWebServlet(/dorm/beds) public class DormBedsServlet extends HttpServlet { DormitoryDao dormitoryDao new DormitoryDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String dormIdStr req.getParameter(dormId); int dormId Integer.parseInt(dormIdStr); Dormitory dorm dormitoryDao.findById(dormId); int remaining dorm.getCapacity() - dorm.getCurrentCount(); resp.setContentType(application/json;charsetUTF-8); PrintWriter out resp.getWriter(); out.write({\dormId\: dormId ,\remaining\: remaining ,\success\:true}); out.flush(); } }前端JSP页面中通过JQuery的$.getJSON读取这个接口的数据$(function(){ $(#dormSelect).change(function(){ var dormId this.value; if(dormId ) return; $.getJSON(${pageContext.request.contextPath}/dorm/beds, {dormId: dormId}, function(data){ if(data.success){ $(#remainingBeds).text(data.remaining 个床位); }else{ $(#remainingBeds).text(查询失败); } }); }); });需要提醒的是Servlet中自己拼接JSON字符串在字段值包含引号时会有转义问题。如果你的宿舍名称里出现了双引号这段代码输出的JSON会损坏。更安全的方式是用Jackson库的ObjectMapper来序列化实体的Map对象ObjectMapper mapper new ObjectMapper(); mapper.writeValue(resp.getWriter(), resultMap);前端JSP JSONArray import的场景也类似在JSP中使用Java原生JSONArray时需要在pom.xml引入依赖dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.83/version /dependency用来在服务端把取到的ListStudent直接转成JSON字符串返回前端去渲染表格。6. 部署Troubleshooting三件套编译路径、404排查与MySQL连接6.1 修改eclipse和IDEA的JSP编译输出位置很多初学者困惑于“JSP编译的class文件保存在哪里”。Tomcat 9之前JSP编译后的class文件位于Tomcat安装目录的work/Catalina/localhost/contextPath/org/apache/jsp下。每一次JSP修改后重新访问Tomcat会对比时间戳来决定是否重新编译。使用Eclipse开发时项目部署到Tomcat默认的wtpwebapps目录JSP编译后的类就放在此目录对应的work文件夹里。如果你在IDEA使用Artifact部署输出的位置在out/artifacts/projectName/文件夹下。排查NoClassDefFoundError: org/springframework/boot/web/servlet/support/SpringBootServletInitializer这类问题时务必先检查编译产物目录里是否存在WEB-INF/lib中的jar包以及是否错误地把Spring Boot的依赖打进了lib。常见原因是在pom.xml里把spring-boot-starter-web声明为compile作用域而正确做法应该是dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId scopeprovided/scope /dependency6.2 404错误排查顺序表宿舍管理系统部署后最常见的返回结果是404。排查时按以下顺序逐层定位配合这个顺序表可以快速缩小范围检查项验证命令或动作错误案例URL路径是否正确查看浏览器地址栏是否带上项目上下文访问localhost:8080/student而不是localhost:8080/dorm/studentServlet映射是否命中在WebServlet注解的URL后加一个不存在的方法测试url-pattern/student/*/url-pattern和WebServlet(/student)混用JSP物理路径是否存在查看webapp目录下的JSP文件名大小写访问student_list.jsp但实际文件名为StudentList.jsp部署是否成功查看IDEA控制台或Tomcat日志里的Deploying web application archiveTomcat的webapps下没有对应WAR目录在Eclipse创建基于Maven的Servlet项目时还需要检查项目的Deployment Assembly中是否把src/main/webapp映射到了/路径。如果没有启动后访问任何JSP都会404。为应对这个问题我一般会在项目的web.xml中增加一个转发页作为默认页兜底welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list这样即使你没有显式输入页面名访问项目根URL也能跳转到登录页方便排查到底是映射问题还是文件缺失。6.3 docker运行MySQL与Navicat连接参数在本地用免安装版MySQL时最常见的问题是用Navicat连接报1130 Host not allowed to connect错误。这是因为MySQL默认只允许localhost连接。执行以下SQL开启远程访问USE mysql; UPDATE user SET host % WHERE user root; FLUSH PRIVILEGES;如果你使用Docker安装MySQL需要同时做好数据目录挂载和端口映射docker run -d \ --name dorm-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEdorm_management \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_general_ci参数说明-p 3306:3306把容器内的3306端口暴露到宿主机第一个3306是宿主机端口第二个是容器内MySQL端口两者可以不同MYSQL_DATABASE让MySQL首次启动时自动创建dorm_management数据库--character-set-serverutf8mb4和--collation-serverutf8mb4_general_ci覆盖容器默认的字符集设置确保表和字段默认就是中文友好的utf8mb4编码。连接时Navicat的URL字符串中必须加上useSSLfalseserverTimezoneAsia/Shanghai两个参数。useSSLfalse是避免MySQL 8.0默认开启SSL而导致连接握手阶段证书校验报错serverTimezoneAsia/Shanghai是让JDBC驱动知道当前会话使用的时区不设置时会直接抛The server time zone value EDT is unrecognized异常。如果你想做定时备份用Windows批处理脚本调用mysqldump是最省事的方式脚本中务必把mysqldump.exe的全路径写正确。6.4 使用mysqlbinlog判断数据异常更新宿舍系统上线后如果发现某天某个宿舍的人数不符除了看应用日志还可以直接查MySQL的binlog。首先确认当前是否开启binlogSHOW VARIABLES LIKE log_bin;ON代表开启。然后通过mysqlbinlog工具解析最近的日志按时间范围过滤出针对dormitory表的UPDATEmysqlbinlog --start-datetime2025-03-01 00:00:00 --stop-datetime2025-03-02 00:00:00 /var/lib/mysql/binlog.000014 | grep -A 20 UPDATE dormitory通过对比日志中显示的SET current_count current_count 1和实际应用里是否有对应的update操作可以确认是否有人绕过Service层直接改库。在Docker部署时mysqlbinlog工具在容器内部宿主机的/var/lib/mysql是挂载目录需要先进入容器再执行docker exec -it dorm-mysql mysqlbinlog --start-datetime2025-03-01 00:00:00 /var/lib/mysql/binlog.000014grep出来的日志块会包含事务的原始SQL和执行时刻这是定位脏数据最直接的手段。你不需要把这个操作养成习惯但至少要知道宿舍管理系统出现数据问题时的排查链条是从这里开始的。本文还有配套的精品资源点击获取
返回列表