简介基于JSPServletMySQL的实验室设备管理系统项目源码包面向Java Web课程设计、毕业设计及初学者进阶场景可帮助理解MVC分层架构、Servlet请求流转与MySQL数据持久化。压缩包共182个文件包括34个JSP页面、32个Java源文件及对应class文件、15个Jar依赖库、17个CSS样式、12个JS脚本和2个SQL初始化脚本另有JPG图片、XML配置及project工程描述文件整体仅6.21MB。已有454人学习下载。从核心类可看出完整实现了设备管理、申请审批、分类维护与用户权限控制等典型业务如AdminDeviceServlet、DeviceDao、ApplyDao、UserServlet等预览中还可见AdminCategoryServlet和CourseServlet覆盖分类管理与课程关联扩展。资源包内自带SQL脚本导入数据库即可生成数据表省去手动建表环节。Java工程可导入Eclipse或IDEA并部署到Tomcat运行项目按MVC分包便于边运行边对照源码也能帮助梳理Servlet生命周期、JDBC预编译防注入以及连接池配置等Web开发常见问题适合作为实训课题或二次开发底座。1. 拿 jspservletmysql 的实验室设备管理系统做毕设先搞懂它再决定改哪里这套系统不是现在流行的 Spring Boot 前后端分离项目而是典型的 JavaWeb 课程设计形态浏览器页面用 JSP 渲染请求交给 Servlet 处理数据落在 MySQL容器用 Tomcat。很多软件工程、信息管理专业的毕设题目还停留在这个技术栈上所以这份资源里你能看到 AdminDeviceServlet、ApplyDao、CategoryDao 一类以类名直白命名业务功能的文件学生拿它改一改就能答辩。它解决的问题很具体实验室设备分类、设备入库、学生提交使用申请、管理员审批、按名称/状态检索设备外加用户和课程的简单管理。适合两类人——一类是刚学完 JSP 和 Servlet、还没做过完整项目的新手另一类是毕设题目恰好是「XX 管理系统」、需要一个可运行底座的在校生。下面按我拆项目的习惯从 Servlet 层、JSP 层、MySQL 层一路拆到部署避坑最后给一套可以在验收时讲清楚的功能验证路径。2. Servlet 层拆解从请求分发到设备增删改查的完整工作流2.1 先从 .class 列表反推项目模块划分拿到压缩包先看文件名这比看文档更能快速建立项目地图。包里出现的是编译后的 .class 文件说明资源里包含了编译输出目录但不影响我们用类名反推模块边界。AdminDeviceServlet 负责后台设备管理入口AdminAddDeviceServlet 是新增设备AdminCategoryServlet 是设备分类维护ApplyItemServlet 处理设备使用申请UserServlet 管用户CourseServlet 管课程关联DeviceDao、ApplyDao、CategoryDao 是对应三块业务的数据访问层。Class 文件推断模块典型 URL 映射AdminDeviceServlet设备列表/删除/修改/admin/deviceAdminAddDeviceServlet新增设备/admin/device/addAdminCategoryServlet设备分类管理/admin/categoryApplyItemServlet提交/审核设备申请/applyItemUserServlet用户登录与信息维护/userCourseServlet课程-设备关联/course这种命名习惯在老式 JavaWeb 项目中很常见Servlet 按页面功能拆Dao 按数据表拆。新手不要急着改代码先把每个 Servlet 的类头注解或者 web.xml 里的servlet-mapping抄下来你就拿到了整个系统的路由表。2.2 请求从 JSP 到 Servlet 再到 DAO 的转发链路先梳理一次完整的请求生命周期。用户在设备列表页点「申请使用」浏览器向服务器发一个 GET 请求Tomcat 根据 url-pattern 找到 ApplyItemServletServlet 调用 ApplyDao 查询申请列表把结果放进 request 作用域再 forward 到 apply_list.jspJSP 渲染成 HTML 返回浏览器。Servlet 的 service 方法里doGet 和 doPost 各司其职。管理员的审批操作走 doPost因为会修改数据库状态单纯的列表查询走 doGet方便直接通过 URL 访问。下面这段是 ApplyItemServlet 双向处理的标准写法WebServlet(/applyItem) public class ApplyItemServlet extends HttpServlet { private ApplyDao applyDao new ApplyDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 列表页和详情页统一走 GET只读操作 ListApply list applyDao.listAll(); request.setAttribute(applyList, list); request.getRequestDispatcher(/apply_list.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 先处理编码再取参数否则中文必乱 request.setCharacterEncoding(UTF-8); int deviceId Integer.parseInt(request.getParameter(device_id)); User loginUser (User) request.getSession().getAttribute(loginUser); String reason request.getParameter(reason); boolean ok applyDao.addApply(deviceId, loginUser.getId(), reason); // 用重定向避免刷新页面时重复提交 response.sendRedirect(request.getContextPath() /applyItem?flag (ok ? success : fail)); } }这里有几个参数细节要注意。request.setCharacterEncoding(UTF-8)必须写在第一个getParameter之前才生效我见过大量乱码是因为这句放到了参数获取之后。device_id通常来自列表页里的隐藏字段input typehidden namedevice_id如果表单里 name 拼错这里 parseInt 直接抛 NumberFormatException。loginUser从 session 取而不是从表单取目的是防止普通用户伪造他人身份提交申请这是权限校验的基本姿态。2.3 Servlet 生命周期里最容易忽略的初始化时机热搜里经常出现「servlet 生命周期」这个词说明很多人面试和考试都栽在这。Servlet 生命周期有五个阶段加载、实例化、初始化init、服务service、销毁destroy。在这样的小型管理系统中init最多用来预加载分类数据到 application 作用域destroy基本不用管。真正影响系统运行的是 init 里的异常处理。如果数据库连接池初始化写在init里数据库没启动时 Tomcat 会报LifecycleException整个 Web 应用启动失败而不是等访问页面才报错。我一般把连接池初始化放在 Dao 的静态块里让异常延迟到第一次数据库访问时暴露这样 Tomcat 至少能起来静态页面还能访问。另外如果这个项目用的是注解WebServlet注意看类上有没有loadOnStartup 1没写的话 Servlet 是第一次请求才实例化启动日志里看不到初始化信息是正常的不是出错了。3. JSP 视图层表单提交、数据展示与 request 转发乱码问题3.1 JSP 页面的本质和内置对象JSP 页面第一次被访问时Tomcat 会把它翻译成 Java 源文件再编译成 Servlet所以 JSP 里能直接用request、response、session、application这四个内置对象。这个机制决定了两个实践结论第一JSP 页面尽量只放展示逻辑业务处理放 Servlet第二页面里如果写了大量% %脚本片段翻译出来的 Servlet 会非常难读这也是一份代码质量好坏最直观的判断标准。基本的使用方式是这样的Servlet 里request.setAttribute(deviceList, list)存入数据JSP 页面通过 EL 表达式${deviceList}取出来。保持这种单向数据流页面不会反向调用 Servlet 业务方法系统结构就清晰了。3.2 把设备列表渲染成表格EL JSTL 才是正规姿势老项目里常见% for (...) { %的 scriptlet 写法能用但可读性差。更推荐 JSTL 标签下面这段是设备列表页的核心片段% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body table border1 cellpadding6 cellspacing0 tr th设备编号/thth设备名称/thth状态/thth所在位置/thth操作/th /tr c:forEach items${deviceList} vard tr td${d.deviceCode}/td td${d.deviceName}/td td c:choose c:when test${d.status 0}在库/c:when c:when test${d.status 1}外借/c:when c:when test${d.status 2}维修/c:when c:otherwise报废/c:otherwise /c:choose /td td${d.location}/td tda href${pageContext.request.contextPath}/applyItem?device_id${d.id}申请使用/a/td /tr /c:forEach /table /body /html重点看${pageContext.request.contextPath}它动态获取应用上下文路径。开发时项目名是lab_manager_war_exploded部署后可能改成lab_manager写死/lab_manager/applyItem的话换环境必炸用这个表达式就不怕。${d.status}这里的d是循环变量el 表达式会调用Device类的getStatus()方法所以实体类属性名要跟 getter 对应改字段名的时候容易漏这是 JavaBean 规范的隐形约束。3.3 表单提交时的中文乱码三处处理JSP 请求中文乱码是出现频率最高的翻车点而且经常同时出现在三个位置。第一处是页面本身% page contentTypetext/html;charsetUTF-8 %必须写在 JSP 第一行第二处是 POST 请求体需要request.setCharacterEncoding(UTF-8)第三处是数据库连接JDBC URL 要带characterEncodingutf8。三处缺一处效果各不相同页面乱是浏览器解析的问题请求乱是提交数据变成问号数据库乱是存入表后再查出来是乱码。有个细节容易被忽略GET 请求的查询参数编码取的是 Tomcat 的 URIEncoding在server.xml里配Connector URIEncodingUTF-8/。如果你只在 doPost 里设置了编码GET 提交的中文依然乱这个坑排查起来特别费时间先在浏览器开发者工具里看请求头再决定改哪里。3.4 btn.bmp 这类资源要当心路径引用包里有个btn.bmp一看就是早期项目里用作按钮背景的位图。老项目经常把这类静态资源放在webroot/images/或WebContent/下页面里用相对路径引用。部署后如果页面 404多半是图片路径没带${pageContext.request.contextPath}导致上下文路径丢失。处理方法很简单把位图转成 PNG 或直接用 CSS 替代然后在所有静态资源引用前加上contextPath。4. MySQL 数据层表结构设计、JDBC 连接与设备状态流转4.1 五张核心表结构与字段设计从 Dao 类名可以推断数据库至少有设备表、分类表、申请单表、用户表四张表加上课程关联可能另有一张。建表脚本一般在sql/目录下如果资源里没带用下面这套结构也能跑通核心业务CREATE TABLE device_category ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL, remark VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE lab_device ( id INT PRIMARY KEY AUTO_INCREMENT, device_code VARCHAR(30) NOT NULL UNIQUE, device_name VARCHAR(100) NOT NULL, category_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0在库 1外借 2维修 3报废, location VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_device_category FOREIGN KEY (category_id) REFERENCES device_category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE device_apply ( id INT PRIMARY KEY AUTO_INCREMENT, device_id INT NOT NULL, user_id INT NOT NULL, reason VARCHAR(200), apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, audit_status TINYINT DEFAULT 0 COMMENT 0待审核 1通过 2驳回, FOREIGN KEY (device_id) REFERENCES lab_device(id), FOREIGN KEY (user_id) REFERENCES lab_user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设备状态用一个 TINYINT 字段表达比多个布尔字段好扩展。新增「维修中」状态时不需要改表结构只要在代码和 JSP 的c:choose里加一个分支即可。device_code加了 UNIQUE 约束防止管理员重复录入相同编号设备这个约束在 DAO 插入时会抛DuplicateKeyException要做好异常捕获并提示用户。申请单表里的audit_status是业务核心字段整个借还流程都在改这一个值。管理员审批通过时代码里要同时做两件事把device_apply.audit_status改为 1把lab_device.status改为 1外借。这两个更新要么都成功要么都失败必须放在一个事务里。老项目里如果直接在 Service 里写两个 update 不包事务就会出现设备显示在库、但申请记录已通过的脏状态。4.2 连接池与 JDBC URL 参数这个项目是纯 Servlet JDBC没有 Spring 管数据源。常见做法是写一个 DBUtil 类用静态方法返回 Connection然后在 web.xml 里配置 Tomcat 的 JNDI 数据源或者直接在 DBUtil 里用 DriverManager。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/lab_manager?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) { throw new ExceptionInInitializerError(MySQL 驱动加载失败检查 WEB-INF/lib 下的 jar 包); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }连接参数里useSSLfalse和serverTimezoneAsia/Shanghai是 MySQL 8 时代的必修课。MySQL 8 默认使用 caching_sha2_password 认证JDBC 驱动版本低于 8.0 会报认证插件错误建议直接用mysql-connector-java-8.0.x并把useSSL关掉否则本机连接时会因为 SSL 证书校验失败抛一堆握手异常。serverTimezone不设置的话连接成功但执行时间字段相关 SQL 时报时区错误这是热搜里「mysql ssl 连接错误」「mysql 连接池」最集中的两个来源。真正的生产环境应该配 C3P0 或 HikariCP 连接池但毕设项目用 DriverManager 也说得通——只要记得在 finally 里关连接。JDBC 的 Connection、Statement、ResultSet 都要关而且关闭顺序和打开顺序相反。我用 try-with-resources 写法来杜绝漏关public ListDevice searchByName(String keyword) { String sql SELECT * FROM lab_device WHERE device_name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { ListDevice list new ArrayList(); while (rs.next()) { Device d new Device(); d.setId(rs.getInt(id)); d.setDeviceCode(rs.getString(device_code)); d.setDeviceName(rs.getString(device_name)); d.setStatus(rs.getInt(status)); d.setLocation(rs.getString(location)); list.add(d); } return list; } } catch (SQLException e) { throw new RuntimeException(查询设备失败, e); } }这里最关键的是PreparedStatement的参数占位符?传参用setString、setInt等方法。用字符串拼接 SQL 的写法WHERE device_name LIKE % keyword %是绝对要避免的用户输入 OR 11 --就能把查询条件注掉轻则绕过滤条件重则拖库。PreparedStatement 除了防注入还会预编译同一条 SQL 执行多次时性能更好这部分在答辩时能讲清楚就是加分项。4.3 LIKE 查询和状态统计的边界热搜「mysql 排序」「mysql 创建索引」也能用在这个系统里。设备列表页如果要按创建时间倒序对应 SQL 是ORDER BY create_time DESC当设备量过几千条device_name的模糊查询会变慢常见做法是给device_name加普通索引ALTER TABLE lab_device ADD INDEX idx_device_name (device_name);注意LIKE %关键词%这种前置通配符写法用不上索引只能全表扫数据量大时改成LIKE 关键词%才能走索引。这个边界答不上来的话面试官基本默认你没真正做过优化。5. 部署与运行避坑从 IDEA 到 Tomcat 的五个高频问题5.1 页面 404先分清楚 Tomcat 和应用两层原因现象IDEA 里启动 Tomcat 成功浏览器访问http://localhost:8080/applyItem报 404。原因分三种。第一URL 少了应用名正确地址应该是http://localhost:8080/你的项目名/applyItem直接写端口访问的是 ROOT 应用第二注解WebServlet(/applyItem)没被扫描到检查类上是否误加WebServlet后又在 web.xml 里重复配置第三IDEA 的 Artifact 没配好部署到 Tomcat 的是空包。解决的排查顺序是先看 Tomcat 启动日志里有没有Deploying web application archive这行再访问http://localhost:8080/项目名/看根路径能不能出页面最后在浏览器地址栏手动敲 Servlet 完整 URL。我遇到最多的是第三种IDEA 里要在Run Configuration - Deployment里把 Artifact 选成war exploded不然每次启动部署的空目录。5.2 页面 500堆栈信息里找第一行 Caused by现象访问设备列表页报 500Tomcat 日志里一长串异常页面只显示java.lang.NullPointerException。原因绝大多数是数据库连接失败、表不存在、字段名拼错三类。NullPointerException 的堆栈会精确到行号比如DeviceDao.java:47打开源码看这一行用的哪个 DAO 方法。解决如果是Cannot create PoolableConnectionFactory说明数据库没启动或者 URL 配错如果是Table lab_manager.lab_device doesnt exist说明建表脚本没执行如果是Unknown column device_code说明实体类字段和表字段不一致。把日志里的Caused by第一行拿出来说事不要被后面的长堆栈带偏。5.3 中文乱码三个入口三套解决方案现象设备名称带中文时列表页显示问号或者录入「显微镜」存进去变成「???」。原因请求编码、响应编码、数据库连接编码三处任一处不是 UTF-8 就乱。最常见的是 JDBC URL 没带characterEncodingutf8MySQL 默认 latin1存中文直接变问号。解决JSP 顶部加contentTypetext/html;charsetUTF-8doPost 第一行加request.setCharacterEncoding(UTF-8)JDBC URL 加useUnicodetruecharacterEncodingutf8。如果数据库里数据已经是问号ALTER TABLE 改 charset 没用只能删掉该行重新录入。另外确认 MySQL 表本身是utf8mb4utf8 在 MySQL 里是 3 字节编码存 emoji 会报错utf8mb4 才是完整版。5.4 空指针session 里的用户对象不一定是非空的现象管理员点审批按钮后台报空指针位置在loginUser.getId()。原因session 超时或者用户没经过登录页直接访问了受保护 URL。老项目里没有过滤器拦截的话任何人直接访问/applyItem都能触发这个错。解决在 web.xml 里配置一个最基础的权限过滤器或者更简单的做法是在每个 doPost 入口加判断User loginUser (User) request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login.jsp); return; }这个判断必须放在getParameter和业务操作之前。数据库连不上、表单参数缺失这些问题都会在到达业务代码前暴露提前拦截能省很多排查时间。5.5 数据库连接不上本机能连换到服务器就连失败现象项目在自己电脑上跑得好好的部署到服务器后报Cant connect to MySQL server through socket /tmp/mysql.sock。原因这是 MySQL 客户端连本地服务时的经典报错说明服务器上 MySQL 服务没起或者 JDBC URL 里的 host 写成了 localhost 但 MySQL 只监听了 127.0.0.1。jsp、servlet、mysql 这套组合常见部署方式是先service mysql start再把 URL 改成实际 IP。解决服务器上先执行mysql -u root -p确认 MySQL 能进再执行show variables like port;确认端口然后确认项目里 URL 不是localhost而是服务器内网 IP且防火墙放行 3306 端口。监听地址问题用bind-address配置解决但要注意这个配置只在 MySQL 5.7 及以下版本直接生效MySQL 8 的配置路径在/etc/mysql/mysql.conf.d/mysqld.cnf。6. 从 IDEA 到服务器部署war 包检验功能闭环的三个验证点跑通本地开发环境后至少要验证三个闭环答辩时才能说清楚系统真正可用。验证点一登录权限闭环。用管理员账号登录在地址栏手动输入普通用户的后台地址应该被踢回登录页。如果直接访问能进说明权限过滤器没配需要补一个简单的 Filter 检查 session 里的角色字段。这个验证最直观也最容易被提问。验证点二设备申请-审批-状态联动闭环。创建一个测试用户提交设备申请管理员审核通过回到设备列表确认状态从「在库」变成「外借」。同时查数据库确认device_apply.audit_status和lab_device.status两个字段都变了只变一个是事务没做好。验证点三打包部署闭环。IDEA 里Build - Build Artifacts - All Artifacts - Rebuild生成 war 包丢到 Tomcat 的webapps目录用startup.sh或startup.bat启动访问http://ip:8080/包名/。这一步能暴露 IDEA 运行环境掩盖的问题——比如 jar 包没打进去、配置文件路径用了绝对路径。我拆过不少类似的 JavaWeb 毕设项目最大的教训是拿到压缩包第一件事不是打开 IDEA 点运行而是先看WEB-INF/lib下有几个 jar 包、web.xml里注册了几个 Servlet、sql目录下有没有建表脚本。这三样齐了项目基本能跑缺一样后面全是破事。从那以后我每次接手这类老项目都强制自己先写一张「路由表 表结构 jar 列表」三合一速查卡再动手改代码。这招对 jspservletmysql 这套技术栈尤其管用希望帮到你。本文还有配套的精品资源点击获取