简介这是一套基于原生Servlet与JDBC实现的设备维修管理系统JavaWeb源码适用于计算机、电子信息等专业课程设计、期末大作业或毕设参考。项目围绕设备报修、维修记录、零件与客户管理等典型业务展开包含项目说明文档和可直接部署运行的完整工程可帮助读者理解JavaWeb后端请求处理、数据库交互及分层开发思路。压缩包共248个文件核心为54个Java源文件与对应class文件涵盖Dao、Servlet等层次同时包含50个JS脚本、17个CSS样式及图片资源用于前端界面另有SQL/DB文件便于初始化数据库。整包仅2.84MB结构清晰便于快速下载与本地搭建。资源已有346人学习下载。除源码外还附带项目说明特别适合具备一定Java基础、希望参考真实业务场景完成课设或深入钻研ServletJDBC机制的开发者。1. 设备维修管理系统源码项目说明为什么原生 Servlet JDBC 这套老配方还值得你从头敲一遍设备维修管理系统源码项目说明JavaWeb 项目使用原生 servlet 和 JDBC.zip——这套东西在很多人眼里是毕业设计急救包我更愿意把它当成一次完整的 JavaWeb 技术体检。项目说小也小不过是设备台账、维修工单、用户登录几张表来回查说大也大原生 Servlet 的路由分发、JDBC 手写 SQL、Session 认证、分页查询全串在了一起比照着 Spring Boot 的黑盒要清楚得多。适合两类人正在做 JavaWeb 课程设计、需要一份讲得清原理的参考平时用框架写惯了、想回头补基础链路开发的。按功能拆解 → 数据库设计 → 核心代码 → 排错 → 改造这条线往下讲。2. 这套设备维修管理系统到底做了什么功能边界、技术选型与三层架构的对应关系拿到源码先别急着部署第一步是搞清楚这系统管到哪、不管到哪。设备维修和点检、保养是两套业务很多教学项目容易把范围扯大页面做了一堆逻辑全是重复的。这套系统的核心就一件事设备坏了以后报修工单怎么登记、怎么流转、怎么闭环。2.1 功能清单与角色权限维修单流转的五个核心场景设备维修管理系统里设备一般指企业固定资产比如机床、叉车、打印机维修管理就是记录这台设备坏了 → 谁报修 → 谁受理 → 修没修好 → 换了什么配件这一串事件。常见的教学版功能边界我用一张表给你捋清楚功能模块角色对应表关键动作登录认证管理员 / 维修工 / 报修人user 表用户名密码校验登录态写入 Session设备台账管理员 / 报修人device 表设备新增、编辑、删除、分页列表报修登记报修人repair 表选择设备、填故障描述、设定紧急程度派工与处理管理员 / 维修工repair 表指派接单人、修改维修状态、填处理结果统计看板可选管理员聚合查询本月维修数、按状态分组统计上面这张表是这套 JavaWeb 教学案例最常见的功能切分。手上源码如果缺了统计看板不用慌它本来就是加分项不是及格项——答辩时能把前四个功能讲透加上事务处理得当分数不会差。我见过不少项目把设备点检和设备维修混在一起做表里既有计划表又有工单表页面数量翻倍但实际上点检强调按时巡检、提前发现隐患维修强调坏了走流程两个需求放一起只会让数据库设计变得臃肿。翻源码时先看 SQL 脚本如果只有维修相关的表说明范围控制得干净。2.2 原生 Servlet 和 JDBC 在这套系统里各管哪一段很多刚接触 JavaWeb 的同学以为 JavaWeb 就等于 JSP这是第一个误区。JSP 是视图层技术真正的控制中枢是 Servlet。在这套设备维修管理系统里一次完整的请求链路是这样的浏览器发起请求 → Tomcat 按 web.xml 里的映射规则找到对应 Servlet → Servlet 调用 Service 层方法 → Service 调用 DAO 层的 JDBC 代码 → 拿到 ResultSet 后封装成实体对象 → 塞进 request 或 Session → 转发到 JSP 渲染页面。JDBC 在这里只干一件事用 Java 连上 MySQL执行 SQL把结果一行行读出来。原生 JDBC 最烦人的地方是样板代码太多一个查询往往要写六七行固定套路加载驱动、获取连接、创建语句、执行、处理结果集、关闭资源这也是后来 MyBatis 能流行的根本原因。但反过来想正是这六七行样板代码让你真正想明白数据库连接从哪来、用完为什么要还回去、连接池到底解决了什么问题。要是你拿到源码后的第一反应是怎么没有 application.yml说明你习惯的是 Spring Boot 的思路。原生 Servlet JDBC 项目里配置是分散的数据库连接信息写在 JDBCUtil 的静态代码块里Servlet 映射写在 web.xml 里编码过滤器是一个 Filter 类再在 web.xml 注册。这份分散本身就是教学价值跟着黑马 JavaWeb 笔记敲过一遍的同学对这个结构应该有印象。2.3 源码项目说明的目录结构从解压到看懂包名拿到 zip 解压后按这套项目的常见编排方式目录结构长这样equipment-repair/ ├── src/ │ ├── com.xxx.entity // 实体类User, Device, Repair │ ├── com.xxx.dao // 数据访问层接口 │ ├── com.xxx.dao.impl // 数据访问层实现 │ ├── com.xxx.service // 业务层接口 │ ├── com.xxx.service.impl // 业务层实现 │ ├── com.xxx.servlet // 所有 HttpServlet 子类 │ ├── com.xxx.filter // 登录校验、编码过滤器 │ ├── com.xxx.util // DBUtil、DateUtil 等工具类 │ └── jdbc.properties // 数据库连接配置 ├── WebContent/ 或 web/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ // mysql-connector-java.jar 等 │ ├── static/ // css、js、images │ ├── login.jsp │ ├── device_list.jsp │ └── repair_list.jsp ├── sql/ │ └── equipment_repair.sql // 建库建表和初始化数据 └── 项目说明.docx 或 README.md一个判断源码质量的技巧看 DAO 是不是接口 实现类两层。教学项目为了展示三层架构往往会把接口和实现分开但不少 Demo 图省事只有一个 DAO 类。后者不是错只是扩展起来耦合重。拿到源码先找 util 包下的 DBUtil这段代码决定你能不能在一分钟之内把数据库连上。项目说明文档一般会写明 JDK、Tomcat、MySQL 的版本要求和导入步骤如果跟你本机对不上优先选 JDK 8 Tomcat 8.5/9 这套组合这是原生 Servlet 项目测试覆盖最广的环境。JDK 17 往上跑老项目不是不行但会遇到模块化导致的各种反射告警没有折腾的必要。3. 数据库是这套系统跑起来的地基设备表、维修单表、用户表的字段设计与初始化 SQL很多同学拿着源码第一件事是配 Tomcat我却建议先看 SQL 脚本。设备维修管理系统的核心逻辑全在表结构里表设计对了Servlet 代码就是机械劳动表设计错了后面每一层都在打补丁。3.1 设备台账表与维修单表一对多关系里的三个关键字段设备表是整个系统的源头维修单表围绕它做挂载。常见的字段设计是CREATE TABLE device ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 设备ID, device_no VARCHAR(32) NOT NULL UNIQUE COMMENT 设备编号如 SB-2024-001, device_name VARCHAR(64) NOT NULL COMMENT 设备名称, model VARCHAR(64) COMMENT 规格型号, location VARCHAR(128) COMMENT 安装位置/使用车间, purchase_date DATE COMMENT 购置日期, status TINYINT DEFAULT 1 COMMENT 1 正常 2 待维修 3 报废, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT设备台账表;三个关键字段值得多说几句。device_no 设成 UNIQUE是为了避免两条一模一样的设备编号在系统里打架真实企业里设备编号是固定资产标牌上的号码必须唯一。status 字段别用 VARCHAR 存正常待维修用 TINYINT 存数字页面展示时再映射成中文既省空间又方便条件查询。create_time 建议交给数据库默认值而不是在 Java 代码里 new Date() 塞进去数据库时钟比应用服务器时间更可靠。维修单表是重头戏设计时注意外键的落法CREATE TABLE repair ( id INT PRIMARY KEY AUTO_INCREMENT, device_id INT NOT NULL COMMENT 所属设备外键, report_user_id INT NOT NULL COMMENT 报修人来自 user 表, fault_desc VARCHAR(500) NOT NULL COMMENT 故障现象描述, level TINYINT DEFAULT 2 COMMENT 紧急程度 1紧急 2普通 3低, status TINYINT DEFAULT 1 COMMENT 1待受理 2维修中 3已完成 4已关闭, assign_user_id INT COMMENT 接单维修工, repair_result VARCHAR(500) COMMENT 维修结果/更换配件说明, report_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 报修时间, finish_time DATETIME COMMENT 完成时间, CONSTRAINT fk_repair_device FOREIGN KEY (device_id) REFERENCES device(id), CONSTRAINT fk_repair_report_user FOREIGN KEY (report_user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT维修工单表;注意这里没有 repair_cost 字段。很多教学版源码会加一列维修费用但真实场景里费用由财务结算不属于维修工单课程设计加一列也无妨但如果页面上没有录入和统计费用这列就是个半截功能答辩时容易被问住。宁可不要也别做半截。另外字段命名建议统一用下划线风格和 Java 实体类里的驼峰属性做映射时用 mapUnderscoreToCamelCase 或手动 rs.getXxx() 都很顺手。3.2 用户表与角色字段登录态是怎么在 JDBC 查询里落地的用户表结构简单但角色字段的设计直接决定权限拦截的复杂度CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 明文或MD5后的密文, real_name VARCHAR(32) COMMENT 姓名, role TINYINT DEFAULT 3 COMMENT 1 管理员 2 维修工 3 普通报修人, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;教学项目的登录密码基本都是 MD5 加密存的这点需要和答辩老师坦诚MD5 现在已经被认为不适合做密码哈希了碰撞攻击成熟正确做法是 BCrypt 加盐。如果整套源码用的是 MD5你只要在项目说明里注明教学演示用生产环境请替换为 BCrypt这个缺陷反而成为你懂行的加分项。登录态在主流程里的 JDBC 查询长这样public User findByUsernameAndPassword(String username, String password) { String sql SELECT id, username, real_name, role FROM user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRealName(rs.getString(real_name)); u.setRole(rs.getInt(role)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }代码说明这里用 ? 占位符配合 PreparedStatement而不是把用户名密码拼进 SQL 字符串这是防 SQL 注入的最低底线也是 JavaWeb 课程设计的考核点之一。参数说明setString 的索引从 1 开始和 SQL 里 ? 的先后顺序一一对应顺序写反是刚上手 JDBC 时最经典的问题报错信息往往还是模糊的Parameter index out of range。登录成功后把 user 对象放进 Session 是最关键的一步request.getSession().setAttribute(loginUser, user);之后每个受保护页面都能从 Session 里取 role 做角色判断。三层角色用 TINYINT 1/2/3 表示比用字符串到处 equals 干净也方便扩展新角色。遇到权限拦截的需求在 Filter 里读 Session 比在每个 Servlet 里重复判断要优雅得多。3.3 初始化 SQL 怎么写测试数据量与常用查询拿到项目的 sql 文件后先别急着执行看三样东西字符集、存储引擎、数据量。字符集必须 utf8mb4不能是 latin1否则中文全变问号存储引擎统一 InnoDB外键才能生效数据量的话设备表至少 20~30 条维修单至少 50 条数据太少分页效果根本看不出来页面翻两页就到底分页查询功能的演示效果大打折扣。初始化数据建议用批量 INSERT 或存储过程生成比如-- 生成 30 条设备数据MySQL 8 的递归 CTE 写法 INSERT INTO device (device_no, device_name, model, location, status) WITH RECURSIVE seq(n) AS ( SELECT 1 UNION ALL SELECT n 1 FROM seq WHERE n 30 ) SELECT CONCAT(SB-2024-, LPAD(n, 3, 0)), CONCAT(设备, n), CONCAT(型号-, n % 5), CONCAT(车间, n % 3 1), 1 (n % 3) FROM seq;生成后的数据应该能支撑一个目标明确的分页查询按状态、按车间、按设备名称模糊搜索一页 10 条正好三页。如果 SQL 脚本里只塞了三条数据说明作者自己都没拿它认真演示过遇到答辩追问会露怯。4. 核心代码走读从 LoginServlet 到 RepairServlet原生 JDBC 的完整调用链这章是整套系统的骨架。Servlet 负责接收请求JDBC 负责和数据库对话中间夹着 Service 层组织业务规则。看懂这条调用链后面所有框架都是它的包装。4.1 登录与 Session 校验Filter 里的三个判断条件Servlet 编程最朴素的路径是每个功能写一个 Servlet 类重写 doGet/doPost在 web.xml 或注解里注册路径。登录功能的完整代码大致长这样WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserServiceImpl(); 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 user userService.login(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, user); if (user.getRole() 1) { resp.sendRedirect(req.getContextPath() /admin/index.jsp); } else { resp.sendRedirect(req.getContextPath() /device/list); } } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }代码说明WebServlet 注解是 Servlet 3.0 提供的Tomcat 7 起支持能少写一行 web.xml 配置。参数说明req.getParameter 拿的是表单里 name 属性对应的值如果 JSP 里 input 的 name 是account代码里却取username拿回来就是 null这种低级错误靠打印日志才能发现。登录校验 Filter 是整套系统的安全底线三个判断条件一个都不能省WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 条件1: 放行登录页、静态资源和登录接口 if (uri.endsWith(/login.jsp) || uri.contains(/static/) || uri.endsWith(/login) || uri.endsWith(/register)) { chain.doFilter(request, response); return; } // 条件2: Session 里有用户就放行 HttpSession session request.getSession(false); if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(request, response); return; } // 条件3: 都不满足,跳回登录页 response.sendRedirect(request.getContextPath() /login.jsp); } }getSession(false) 和 getSession() 的区别值得记牢不带参数的方法会强制创建一个 Session带 false 则没有就返回 null。在 Filter 里必须用 false否则每个匿名请求都白白多出一个 Session服务器内存里塞满垃圾这是很多 JavaWeb 项目内存飙高的隐形原因。4.2 设备列表与分页用原生 JDBC 拼 LIMIT 的两种写法分页查询是这套系统最能体现原生 JDBC 功力的地方。页面需要两个东西当前页的数据列表、总记录数。于是 DAO 里通常有两个方法一个查列表一个 count 总数public ListDevice findByPage(String keyword, Integer status, int pageNo, int pageSize) { StringBuilder sql new StringBuilder( SELECT id, device_no, device_name, model, location, status FROM device WHERE 11); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append( AND (device_name LIKE ? OR device_no LIKE ?)); params.add(% keyword %); params.add(% keyword %); } if (status ! null status ! 0) { sql.append( AND status ?); params.add(status); } sql.append( ORDER BY id DESC LIMIT ?, ?); params.add((pageNo - 1) * pageSize); params.add(pageSize); // 已省略: 获取连接、循环 setObject、执行查询、封装 List }代码说明动态 SQL 用 StringBuilder 拼是原生 JDBC 项目最常见的写法。WHERE 11 不是偷懒是为了让后面每个 AND 不用判断是不是第一个条件老派但有效。参数说明LIMIT 的两个参数第一个是偏移量 (pageNo-1)*pageSize第二个是每页条数params 列表的顺序必须和 SQL 里 ? 的出现顺序完全一致keyword 为空时 status 参数就顶上顺序乱掉数据全错。count 总数的方法和上面几乎一样只是把 SELECT 字段换成 COUNT(*)去掉 ORDER BY 和 LIMIT。Service 层拿到总数后算出总页数totalPage (totalCount pageSize - 1) / pageSize这个上取整公式避免了整除丢页的问题。分页还有一种写法是直接在 JSP 里用 JSTL 的 c:forEach 循环渲染表格页面上上一页/下一页两个按钮的链接就是 list?pageNo当前页-1keywordxxx。这里有个最容易翻车的细节keyword 必须手动跟着翻页链接走不然翻到第二页搜索条件就丢了。部分源码会用隐藏域保存查询条件逻辑等价但更容易被忽略的是 status 这个下拉框的选中状态也要回显。4.3 维修单的提交与状态流转事务应该加在哪一层设备维修最核心的流程是报修人提单 → 管理员派工 → 维修工处理 → 关闭单据。每一步都涉及 update 语句而维修完成这一步涉及两张表的联动device 状态要从待维修改回正常repair 表的状态要改成已完成。这两条 update 必须在一个数据库事务里要么都成功要么都失败。原生 JDBC 的事务控制代码是很多人从来没写对过的public boolean finishRepair(int repairId, int deviceId, String result) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,事务从这里开始 String sql1 UPDATE repair SET status3, repair_result?, finish_timeNOW() WHERE id?; String sql2 UPDATE device SET status1 WHERE id?; try (PreparedStatement ps1 conn.prepareStatement(sql1); PreparedStatement ps2 conn.prepareStatement(sql2)) { ps1.setString(1, result); ps1.setInt(2, repairId); ps1.executeUpdate(); ps2.setInt(1, deviceId); ps2.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }事务必须放在 Service 层而不是 DAO 层。原因很直接DAO 方法各自拿着自己的 Connection 做单条 SQL如果把事务写在 DAO 里两个 DAO 方法各提交各的无法形成原子操作。上面这段代码把连接从 DBUtil 拿出来后手动控制 setAutoCommit(false)、commit、rollback最后在 finally 里恢复自动提交并关闭连接这才是完整的收尾。这里有个教学项目典型的坑用 try-with-resources 把 Connection 也包进去然后在 conn.commit() 之前连接已经被关闭事务根本没机会提交。正确的做法是 Connection 用传统 try-catch-finally 管理PreparedStatement 和 ResultSet 才能放心交给 try-with-resources。没有连接池的原生 JDBC 在这里也会暴露问题每次执行业务都要新建物理连接性能差是其次连接一多 MySQL 的 max_connections 就顶不住了这个我们到排错部分细说。5. 跑通与排错设备维修管理系统在 Tomcat MySQL 下的避坑记录下面这几条全是这套 JavaWeb 项目最常见的翻车现场每一条我都按现象 → 原因 → 解决来讲。你在 IDEA 里跑这套系统时遇到任何一个直接按对应条目处理。5.1 现象启动 Tomcat 时控制台报 ClassNotFoundException: com.mysql.cj.jdbc.Driver原因mysql-connector-java.jar 没有放到 WEB-INF/lib 目录下。很多初学者把 jar 拖到 IDE 的 Libraries 里编译能过但 Tomcat 运行时用的是 WEB-INF/lib 下的 jar编译期和运行期类路径是两回事。解决把 mysql-connector-java-x.x.x.jar 复制到 WebContent/WEB-INF/libMaven 结构则是 src/main/webapp/WEB-INF/lib下右键 Add as Library重启 Tomcat。判断是不是这个问题最快的方法是去 Tomcat 实际部署目录看一眼有没有这个 jar。另一个变体是驱动类名写错。老版本驱动类是 com.mysql.jdbc.DriverMySQL Connector/J 8.x 换成了 com.mysql.cj.jdbc.Driver。如果项目说明里的连接配置是 5.x 写法、你本地装的是 8.x 驱动ClassNotFoundException 照样来把驱动类名换成 8.x 即可。5.2 现象数据库连接失败控制台报 Communications link failure 或 Access deniedAccess denied 是账号密码或权限问题。到 MySQL 里执行 SELECT user, host FROM mysql.user确认连接时用的用户和 host 是否匹配如果用户名密码都对但依然拒绝大概率是 % 和 localhost 的匹配规则问题新建一个 host 为 % 的账号或者把连接串改成 127.0.0.1 再试。Communications link failure 集中在三处MySQL 服务没启动、连接串里 IP 或端口不对、SSL 或时区参数导致握手失败。在 JDBC URL 后面加上这几个参数能解决八成问题jdbc:mysql://localhost:3306/equipment_repair?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8useSSLfalse 关掉 SSL 握手恢复老驱动的行为serverTimezoneAsia/Shanghai 解决 8.x 驱动要求显式指定时区的问题characterEncodingutf8 保证中文不乱。注意这里有个容易混淆的点mysql jdbc useSSL 与 sslMode 实际上是同一种配置在不同驱动版本里的演进MySQL 8.x 驱动有时需要写 sslModeDISABLED 才能彻底关掉 SSL 处理具体看异常栈里提示的是哪个参数。顺带一提不同数据库驱动的 URL 参数并不通用比如 PostgreSQL 驱动里的 targetServerType 是控制主备路由的别把那一套配置照搬到 MySQL 项目里。5.3 现象页面上的中文全是问号或者写入数据库后再查出来全是问号原因是一整条编码链路断了。Tomcat 请求端、响应端、JSP 页面、MySQL 表结构、连接串五处至少要统一成 UTF-8。按这个顺序排查第一步JSP 页面头部的 pageEncoding 和 contentType 要同时写 UTF-8第二步POST 请求在 Servlet 里先调用 req.setCharacterEncoding(UTF-8)GET 请求需要在 Tomcat 的 server.xml 里给 Connector 加 URIEncodingUTF-8第三步连接串里 characterEncodingutf8 不能省第四步建表语句的 CHARSET 必须显式写 utf8mb4而不是依赖 MySQL 默认值。第五步是最容易忽略的MySQL 服务器本身的默认字符集。命令行执行 SHOW VARIABLES LIKE character_set_server;如果返回 latin1哪怕建表时指定了 utf8mb4部分导入工具在导入过程中还是会乱。改配置文件 my.cnf 的 character-set-serverutf8mb4重启 MySQL 服务一劳永逸。5.4 现象页面能打开但点几次查询后越来越慢最后报 Too many connections这是典型的 JDBC 资源泄漏。查询方法里的 Connection、PreparedStatement、ResultSet 任何一个没关闭都会把数据库连接占住。教学项目里最常见的写法是只关了 ResultSet 忘了关 Connection或者 Connection 在 return 之后就再也没人管了。解决思路是把三个资源全部放进 try-with-resourcestry (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 业务处理 }注意 try-with-resources 的关闭顺序和声明顺序是反的但代码不关心这个问题三个资源都会被自动关闭。修复后可以用 SHOW PROCESSLIST 查看当前连接数如果还有大量 Sleep 状态连接挂着那可能是连接池或事务没提交的问题和资源泄漏不是一回事别混在一起排查。还有一个隐蔽的坑Connection 放进 try-with-resources 之后你在里面手动 setAutoCommit(false) 并且没 commit 就退出资源虽然关闭了但事务被隐式回滚数据没写进去页面上还没有任何报错。事务和连接池的配合我建议按第 4 章那段代码来写Connection 不要进 try-with-resources。5.5 现象JSP 改了内容刷新页面还是老样子像是有缓存原因Tomcat 会把 JSP 编译成 Java 类缓存在 work 目录里。JSP 文件本身没有编译错误时Tomcat 会复用旧的 class不重新编译。解决在 IDEA 里 Build → Rebuild Project或者手动删除 Tomcat 的 work/Catalina 目录后重启。开发阶段更彻底的办法是在 web.xml 里把 JSP 的 development 参数设为 true让 Tomcat 每次检测到 JSP 修改就重新编译。这类问题在教材里常被归为玄学其实就是编译缓存没清理。另一个类似的坑是浏览器缓存静态资源 css/js 被浏览器强缓存刷新看不到新样式开发时按 CtrlF5 强制刷新或者给静态资源的引用加上 ?v2 版本参数屡试不爽。6. 进阶改造把这套源码升级到能讲出价值的四个优先项设备维修管理系统作为 JavaWeb 课程设计能跑通只是及格线答辩时讲出我做了哪些改进才是拉开差距的地方。按性价比排序推荐按这张表动刀优先级改造项改动量答辩价值P0用 Druid 连接池替换 DBUtil 里的 DriverManager.getConnection改 DBUtil 一处讲清楚连接复用与资源控制P0密码从 MD5 换成 BCrypt改登录、用户新增两处讲清楚密码存储的演进P1把多个 Servlet 改造成一个 BaseServlet 按方法分发改一个父类加子类体现代码设计能力P1给 repair 表加索引优化查询改 SQL 脚本建立性能意识P2把 JSP 里混写的 Java 代码抽成 EL 和 JSTL改动页面较多体现视图层规范其中 P0 两项改动量小、说服力强。Druid 连接池的引入大致是这样public class DBUtil { private static DruidDataSource dataSource; static { dataSource new DruidDataSource(); dataSource.setUrl(jdbc:mysql://localhost:3306/equipment_repair?useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxActive(20); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }代码说明setInitialSize 是启动时预创建的连接数setMaxActive 是连接池最大上限。换成连接池后原来 Service 层里手动 setAutoCommit(false)、commit、rollback 的事务代码一行都不用动连接池负责连接的发放和回收。这个改造能让你顺带把数据库连接池的申请与归还这个高频考点吃透。关于在 IDEA 里运行这套原生 Servlet 项目配置步骤是固定套路导入项目后选择 Web 应用模板配置好 Tomcat Server把 Artifact 选成 exploded 解压部署模式在 Run/Debug Configurations 里配好 Application Server 端口就能跑。用 VSCode 写 Servlet 再配环境的人也有但 JavaWeb 的断点调试还是 IDEA 顺手建议用 IDEA 跑通之后再考虑其他编辑器。我最后的习惯是每次改完代码部署前先扫一眼 IDEA 的 Build 输出和 Tomcat 的 catalina.out把异常定位到具体行号再动手。很多问题不是逻辑复杂而是环境不一致导致的把 JDK、Tomcat、MySQL、驱动的版本固定下来写进项目说明能帮后来接手的人省下大量时间。设备维修系统这套源码不算惊艳但只要你把每条链路走通、每个报错都亲手解决过再去看 MyBatis、Spring MVC 时心里会多一份别人没有的底牌知道这些框架底层在替你做什么、不做什么。希望帮到你。本文还有配套的精品资源点击获取
