简介:这套基于JSP+Servlet+MySQL的实验室设备管理系统,面向Java Web初学者与课程设计人群,完整演示了传统Servlet技术栈下的MVC分层实现:JSP负责视图展示,Servlet处理请求与控制逻辑,MySQL存储设备、申请、用户等数据。压缩包共182个文件、约6.21MB,涵盖34个JSP页面、32个Java源文件及对应class文件,同时包含数据库SQL脚本、前端CSS/JS与图片素材、项目配置文件等,基本开箱可导入Eclipse配合Tomcat运行。系统覆盖设备管理、申请审批、课程与用户管理、分类维护等模块,并预置了针对增删改查、权限控制及SQL注入防护的设计思路,适合作为毕业设计、课设模板或练手项目参考。目前已有454人学习下载,对想快速理解Servlet+JSP协作机制、补齐项目完整度的开发者有直接的借鉴价值。
1. 这个打包好的JavaWeb项目,值不值得你花一个周末跑通
拿到一个名为"基于jsp+servlet+mysql的实验室设备管理系统.zip"的项目包时,最常见的开局是:解压、启动、浏览器打开、页面404,然后你怀疑人生。这套技术栈是JavaWeb课程设计和毕设里最稳的组合——JSP负责把数据渲染成页面,Servlet接收请求并控制跳转,MySQL把设备台账、借用记录、维修状态全部落库。它解决的问题很具体:实验室设备多、借还乱、故障无记录,这套系统用最朴素的方式把这些流程搬上线。适合刚学完JavaWeb想找个完整案例练手的人,也适合拿现成代码当毕设底子的人。但前提是你得先把环境对齐,JDK、Tomcat、MySQL的版本有一处不匹配,后面全是在跟报错搏斗。
2. 部署前的环境底子:JDK、Tomcat、MySQL 怎么配才不打架
2.1 版本匹配是第一道门槛
这类基于jsp+servlet+mysql的老项目,对运行环境没那么宽容。最常见的组合是JDK 8配上Tomcat 8.5或9.0,MySQL用5.7或8.0。先说JDK:如果你机器上装的是JDK 17甚至更高,直接跑老代码大概率会在编译阶段翻车,因为Tomcat 10把javax.servlet包改成了jakarta.servlet,很多老项目里的import javax.servlet.*根本过不了编译。同理,Tomcat 10配上老war包,运行时会直接给你报ClassNotFoundException: javax.servlet.http.HttpServlet。这不是项目坏了,是环境没对上。
所以拿到zip包之后第一步不是打开代码,而是先看web.xml里的web-app版本、看源码里import的是javax.servlet还是jakarta.servlet,再决定装哪个JDK和Tomcat。我一般建议:老项目一律JDK 8 + Tomcat 9,这是兼容面最广的一组。如果你机器上已经装了新版JDK,优先去配置环境变量而不是卸载重装,IDEA里可以单独给项目指定JDK版本。
MySQL这边相对宽松,5.7和8.0对这张项目的大多数SQL都能兼容,但连接驱动要选对。MySQL 5.7配mysql-connector-java 5.1.x没问题,MySQL 8.0则要用mysql-connector-java 8.0.x,否则启动时大概率遇到Communications link failure。判断手里驱动版本的方法很简单,看lib下的jar包文件名,8.x开头的就是新驱动。
2.2 MySQL安装与初始化:Windows、Linux 两条路
Windows下按mysql安装教程走就行,下载MSI安装包,选Developer Default就可以。有一点容易忽略:安装时选认证方式,MySQL 8.0默认用caching_sha2_password,这对老项目不友好,很多老版本连接池不认这个插件,建议选Use Legacy Authentication,或者安装完了再用ALTER USER改回mysql_native_password。设置root密码时别绕行,后面所有配置文件都要写它。
Linux下通常是apt或yum安装:
# Ubuntu/Debian sudo apt update sudo apt install mysql-server sudo systemctl start mysql sudo systemctl status mysql # CentOS/RHEL 7+ sudo yum install -y mysql-community-server sudo systemctl start mysqld sudo systemctl status mysqld说明:systemctl start mysqld启动的是MySQL服务端进程,单独跑一个JavaWeb项目时,MySQL必须以服务形式常驻,不能只在终端里敲一个mysql命令就以为数据库可用了——那个命令只是客户端,进去还得有服务在背后监听3306端口。装完先确认端口起来:netstat -tlnp | grep 3306。如果看到报错ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock',十有八九是mysqld根本没拉起来,先去查/var/log/mysql/error.log和mysqld.service的运行状态,而不是去检查代码。
初始化表结构和数据推荐用MySQL Workbench,它的使用教程在刚开始看有点繁琐,但比命令行直观很多。connector JDBC的URL里主机就填localhost:3306,数据库名按项目里的jdbc.properties来填。注意:Workbench自带的默认端口就是3306,如果你手改过/etc/mysql/mysql.conf.d/mysqld.cnf,这里也要同步改。
2.3 Tomcat 启动:startup.bat 不弹窗才是真问题
Tomcat解压后放在无中文、无空格的路径下,比如D:\apache-tomcat-9.0.xx。Windows下直接双击bin/startup.bat的一般都没好下场——窗口一闪而过,你根本不知道后台发生了什么。正确做法是:先配置好JAVA_HOME环境变量,再打开命令行手动执行:
# Windows 命令行 set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202 cd D:\apache-tomcat-9.0.xx\bin startup.bat# Linux export JAVA_HOME=/usr/lib/jvm/java-1.8.0 $CATALINA_HOME/bin/startup.sh # 看实时日志 tail -f $CATALINA_HOME/logs/catalina.out说明:Tomcat本身是一个Servlet容器,不解析JSP以外的脚本。很多人问nginx支持jsp吗,答案是否定的——nginx只管静态资源转发,动态请求必须转发给Tomcat。这套实验室设备管理系统在单机部署时根本不需要nginx,直接访问http://localhost:8080/项目名/即可。启动后验证两件事:一是后台没打印Exception,二是浏览器能打开Tomcat默认首页。如果8080被占用,去conf/server.xml里把Connector端口改成8081,同时改掉Server标签的8005端口,避免冲突。
Tomcat启动失败时第一件事是看logs/catalina.out,最末端几百行里一定写着真实原因。JAVA_HOME配错、项目里缺类、端口被占,全在这里现形。别去改代码,先把日志读明白。
3. 数据库先行:建库建表与初始化数据的完整过程
3.1 实验室设备管理系统的六张表,各自管什么
拿到项目后,第一步别急着启动,先看SQL文件。这类系统的表结构大同小异,核心是围绕"设备"展开的。常见做法是建这几张表:sys_user存登录用户和管理员,category存设备分类,lab_info存实验室信息,equipment存设备台账,borrow_record存借用记录,repair_record存维修记录。设备状态是这套系统的灵魂字段,一般用TINYINT表示,0在用、1已借出、2维修中、3报废,后面所有列表和统计都是基于这个值。
表与表之间的关系也简单:equipment外键关联category和lab_info,borrow_record外键关联equipment和sys_user。外键在毕设答辩时是加分项,但在实际导入时也是麻烦来源——有外键的表必须按依赖顺序创建,否则直接报错。
3.2 建库建表SQL:默认值、字符集、引擎的细节
下面这套SQL是我调整过的通用版本,兼容MySQL 5.7和8.0,默认值都用得很保守,避免导入时报错:
-- 建库,utf8mb4 必须指定,否则中文全乱码 CREATE DATABASE IF NOT EXISTS lab_dev DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE lab_dev; -- 用户表 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '密码,建议存MD5', real_name VARCHAR(50) DEFAULT '' COMMENT '真实姓名', role TINYINT NOT NULL DEFAULT 1 COMMENT '角色:1管理员 2教师 3学生', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 设备分类表 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备分类表'; -- 实验室表 CREATE TABLE lab_info ( id INT PRIMARY KEY AUTO_INCREMENT, lab_name VARCHAR(100) NOT NULL COMMENT '实验室名称', location VARCHAR(200) DEFAULT '' COMMENT '位置', manager_id INT DEFAULT NULL, FOREIGN KEY (manager_id) REFERENCES sys_user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='实验室表'; -- 设备台账表 CREATE TABLE equipment ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '设备编号', equip_name VARCHAR(100) NOT NULL COMMENT '设备名称', model VARCHAR(50) DEFAULT '' COMMENT '型号', category_id INT DEFAULT NULL, lab_id INT DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0在用 1已借出 2维修中 3报废', buy_date DATE DEFAULT NULL COMMENT '购置日期', price DECIMAL(10,2) DEFAULT 0.00 COMMENT '单价', FOREIGN KEY (category_id) REFERENCES category(id), FOREIGN KEY (lab_id) REFERENCES lab_info(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备台账表'; -- 借用记录表 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, equip_id INT NOT NULL, user_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, expect_return_time DATETIME DEFAULT NULL, actual_return_time DATETIME DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT '0借用中 1已归还', remark VARCHAR(255) DEFAULT '', FOREIGN KEY (equip_id) REFERENCES equipment(id), FOREIGN KEY (user_id) REFERENCES sys_user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='借用记录表'; -- 维修记录表 CREATE TABLE repair_record ( id INT PRIMARY KEY AUTO_INCREMENT, equip_id INT NOT NULL, report_user_id INT DEFAULT NULL, repair_result VARCHAR(255) DEFAULT '', status TINYINT DEFAULT 0 COMMENT '0待维修 1维修中 2已完成', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME DEFAULT NULL, FOREIGN KEY (equip_id) REFERENCES equipment(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修记录表';逻辑说明:DEFAULT 0是刻意写的,很多老代码里status字段靠默认值兜底,你不给默认值,Java代码里一旦漏传这个字段,MySQL直接报错。DEFAULT CHARSET=utf8mb4也是血的教训,老版本很多用utf8,但utf8在MySQL里并不是真正的全量Unicode,插入生僻字或Emoji时会变成问号。CURRENT_TIMESTAMP是MySQL 5.6以后通用的默认值写法,不用考虑写NOW()。
参数说明:AUTO_INCREMENT自增主键每张表都要有,这是Java代码里rs.getInt("id")能工作的前提;DECIMAL(10,2)价格字段用定点数而不是FLOAT,浮点算钱会出精度问题,设备管理系统虽然不涉及交易,但答辩时能说清楚这一条是加分项。
初始化数据时顺手插入管理员账号,密码建议用MD5加密后的值。前端拿用户输入,传进来前先MD5一下再比对,这是这套系统里最容易被老师追问的点。
3.3 导入SQL的三种方式,以及最常踩的坑
方式一:命令行source导入
mysql -u root -p lab_dev < lab_dev.sql这种方式要求SQL文件里已经写了CREATE DATABASE,不然得先手动建库。导入时看到ERROR 1064,通常是SQL文件编码不对,用记事本另存为UTF-8再试。
方式二:Navicat、MySQL Workbench 导入
Navicat连接mysql后,右键数据库运行SQL文件。这类GUI工具的坑在于:如果你的SQL文件里有USE lab_dev;,但当前选中了别的库,执行结果会跑到另一个库去。我建议每次导入前都先双击选中目标库,再执行整个文件,人肉确认左上角库名。
方式三:IDEA Database插件直接跑
适合在开发环境里调试,选中lab_dev后执行整个脚本,错误信息直接在控制台打印,排查效率最高。
导入成功与否的验证不是看日志显示"成功",而是执行几条查询:
USE lab_dev; SHOW TABLES; SELECT * FROM sys_user; SELECT COUNT(*) FROM equipment;sys_user里能看到刚插入的管理员,说明表结构没问题;equipment是空表不奇怪,你可以手动插一条假设备用来测试列表页,正常系统启动后应该在页面上有"新增设备"的入口。
3.4 导入SQL时的三个边界坑
第一个坑是外键顺序。如果你的SQL文件里先建equipment再建category,而equipment外键指向category,导入时直接报Cannot add foreign key constraint。解决办法是调整建表顺序,先建被依赖的表。这也是为什么我上面的SQL按用户、分类、实验室、设备、借用、维修的顺序排列。
第二个坑是重复导入。同一份SQL跑两遍,第二遍报Table 'sys_user' already exists。有些人的处理方式是先DROP TABLE IF EXISTS再CREATE TABLE,这会导致数据被清空。正确姿势是有意识地管理SQL文件,把它当成"初始版本",后续的增量改动写进单独的update_xxx.sql,不要回头改初版。
第三个坑是历史遗留数据。从zip里打开的是别人导出来的SQL,里面可能带着他们的测试数据。你本地装的是MySQL 8.0,数据里如果有utf8mb4_0900_ai_ci排序规则而你的SQL文件用的utf8mb4_general_ci,不会报错,但编码方式不一致会让后期排查乱码时多绕一圈。统一把CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci写清楚,省得后面玄学问题频出。
4. Servlet生命周期与登录链路:代码在跑什么
4.1 项目目录结构与三层职责
这类zip项目解压后,典型结构是标准的IDEA/Eclipse Web工程:
src/ com.lab.entity/ 实体类,对应每张表 com.lab.dao/ 数据访问层,JDBC操作 com.lab.servlet/ 控制层,接收HTTP请求 com.lab.util/ 工具类,DBUtil、MD5等 web/ WEB-INF/web.xml Servlet映射配置 login.jsp 登录页 index.jsp 主框架页 equipment/list.jsp 设备列表页 ... lib/ 依赖jar包(含mysql驱动)这套结构其实就是经典的Servlet+JSP三层:DAO管数据库,Servlet管控制流转,JSP管展示。没有Spring,没有MyBatis,所有对象都手动new。在一个毕设项目里这是优点而不是缺点——代码逻辑全在明面上,每个new都能看到,老师问起来你也讲得清。vscode编写servlet代码需要怎么配置环境这个问题,本质上是给vscode装Java插件、Tomcat插件,再把lib下的jar包加进classpath,但说实话做这种项目用IDEA Community版或Eclipse更顺,没必要在vscode上折磨自己。
4.2 Servlet生命周期与请求流转
Servlet生命周期是JavaWeb的必考题,也是这个系统能跑起来的地基。一个Servlet从加载到销毁经历init() -> service() -> destroy()三个阶段。实例化后init()只执行一次,适合做初始化配置;每次请求都经过service(),由它根据HTTP方法分发到doGet()或doPost();应用卸载时执行destroy()释放资源。
这套系统里最常见的流转是登录:login.jsp表单提交到LoginServlet,doPost()里取参数、调DAO查库、结果写session、forward或redirect。要说明的是req.getRequestDispatcher("/xxx").forward()和resp.sendRedirect()的区别——forward是服务器内部跳转,浏览器地址栏不变,request里的属性还在;redirect是浏览器重新发一次请求,地址栏变化,request里的东西全丢。登录成功后存了session的用户信息,所以两种跳转都不影响后续取用户,但登录失败时用forward把错误提示带回页面,否则用户什么都看不到,只能干瞪眼。
4.3 登录功能的JSP + Servlet + DAO 三段式实现
按Model2思想实现用户注册和登录功能,核心原则是JSP里尽量不写Java逻辑,只做展示和参数收集。登录页的JSP关键片段:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>实验室设备管理系统 - 登录</title> </head> <body> <form action="${pageContext.request.contextPath}/login" method="post"> <input type="text" name="username" placeholder="账号"/> <input type="password" name="password" placeholder="密码"/> <button type="submit">登录</button> </form> <span style="color:red">${msg}</span> </body> </html>说明:${pageContext.request.contextPath}是EL表达式,动态获取项目根路径,避免上下文路径写死。表单没写method会默认GET,登录信息出现在URL上,所以必须显式method="post"。${msg}是Servlet里setAttribute进去的错误提示,直接用EL取出,页面里干干净净没有一行Java代码,这就是Model2中view该有的样子。
对应的LoginServlet:
package com.lab.servlet; import com.lab.dao.UserDao; import com.lab.entity.User; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; public class LoginServlet extends HttpServlet { private UserDao userDao = new UserDao(); @Override public void init() throws ServletException { // 生命周期第一步,实例化后只执行一次,适合加载配置 super.init(); System.out.println("LoginServlet init ..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 取参数,先做校验 String username = req.getParameter("username"); String password = req.getParameter("password"); if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) { req.setAttribute("msg", "账号和密码不能为空"); req.getRequestDispatcher("/login.jsp").forward(req, resp); return; } // 2. 查库比对,密码应该MD5后再拼入 User user = userDao.findByUsernameAndPwd(username, password); // 3. 根据查询结果决定去向 if (user != null) { HttpSession session = req.getSession(); session.setAttribute("loginUser", user); resp.sendRedirect(req.getContextPath() + "/equipment/list"); } else { req.setAttribute("msg", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } @Override public void destroy() { super.destroy(); System.out.println("LoginServlet destroy ..."); } }逻辑说明:doPost里先取参数做非空校验,再查库,最后按结果分流。这里有个细节值得注意:密码比对是在SQL里做还是查出来在Java里做。上面的写法是findByUsernameAndPwd把密码也拼进SQL,这种方式简单但理论上不够好——SQL日志里会打印完整SQL,密码就暴露了。更稳的做法是先按用户名查出User对象,再在Java里比对MD5值,但这个度你自己权衡,毕设追求的是讲清原理,两种都能自圆其说。
UserDao的实现:
package com.lab.dao; import com.lab.entity.User; import com.lab.util.DBUtil; import java.sql.*; public class UserDao { public User findByUsernameAndPwd(String username, String password) { String sql = "SELECT * FROM sys_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而不是Statement,这是这条代码里最值得讲的一点。前者预编译占位符,?由参数传入,天然防SQL注入;后者拼字符串,用户名里输入' or '1'='1就能绕过登录。你要是敢在DAO层写字符串拼接的SQL,答辩时基本属于送人头。JDK 7之后Connection、PreparedStatement、ResultSet都实现了AutoCloseable,try-with-resources写法能把三个资源一次性关掉,别再用老代码里那种finally里套finally的写法。
Servlet的映射配在web.xml里:
<servlet> <servlet-name>loginServlet</servlet-name> <servlet-class>com.lab.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>参数说明:url-pattern填/login,那么表单的action路径结尾就得是/login,两者必须完全一致。表单上我写了${pageContext.request.contextPath}/login,这是带上下文路径的写法,因为部署后的地址是http://localhost:8080/lab_dev/login,lab_dev就是上下文路径。如果web.xml里的url-pattern是/servlet/login,那页面里的路径也要对应改成/servlet/login。这类404里十个有八个是这里对不上。
5. 部署与避坑检修清单:启动失败、乱码、连接超时的5个现场
5.1 部署到Tomcat的两种方式
第一种是war包发布。IDEA里Build Artifacts生成war包,扔到Tomcat的webapps目录下,启动Tomcat自动解压。这种方式适合部署到服务器,目录结构干净,但开发时每改一行代码都要重新打war包,效率极低。
第二种是IDEA里直接配置Tomcat运行。Run/Debug Configurations里选Tomcat Server,Deployment页签里把web目录和exploded war关联上,开Update resources热更新。改JSP不用重启就生效,改Java代码要用Debug模式配合JRebel或IDEA自带的HotSwap,普通改动重启一次也就三五秒。
生产服务器上没有IDEA,war包才是正路。但对你现在这个阶段,先把第二种跑通,所有排错都发生在IDEA的Run窗口里,日志可读性比catalina.out好不少。
5.2 MySQL 8连接串:useSSL、serverTimezone、allowPublicKeyRetrieval
DBUtil里的JDBC连接串,是这套系统连接MySQL的核心配置,也是报错重灾区:
public class DBUtil { private static String url = "jdbc:mysql://localhost:3306/lab_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"; private static String user = "root"; private static String password = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }参数说明:useUnicode=true&characterEncoding=utf8保证中文字节流正确解码,这条丢了就是页面问号;useSSL=false是MySQL 8的硬要求,新版驱动默认要加密连接,本地开发根本用不上SSL,不关就会报Communications link failure或者让你配证书;serverTimezone=Asia/Shanghai解决时区差8小时的问题,不设的话日期字段查出来比实际早8小时;allowPublicKeyRetrieval=true专门解决MySQL 8的caching_sha2_password认证时公钥获取问题,不设会报Public Key Retrieval is not allowed。这四个参数在MySQL 8.0+mysql-connector-java 8.x的组合下缺一不可,缺一个就是一套报错。
驱动类名也注意:老驱动是com.mysql.jdbc.Driver,8.x换成了com.mysql.cj.jdbc.Driver。如果你用的jar是8.x却写老类名,Class.forName会直接抛ClassNotFoundException。
5.3 五个现场:现象、原因、解决
现场一:浏览器访问项目路径直接404
现象:Tomcat首页能开,输入http://localhost:8080/项目名/却404。
原因:war包没解压成功,或者IDEA里部署的Application context和实际访问路径不一致。
解决:先看webapps目录下有没有对应的文件夹;再开logs/catalina.out看解压日志,绝大多数情况是war包没生成完整。开发环境检查IDEA配置里的Deployment -> Application context,它决定URL里的项目名段,改完重启Tomcat。
现场二:控制台报ClassNotFoundException: com.mysql.jdbc.Driver
现象:Tomcat能启动,登录页能打开,一点"登录"就500,控制台找不到驱动类。
原因:mysql驱动jar包没放在正确位置。
解决:检查WEB-INF/lib下有没有mysql-connector-java-8.0.x.jar。IDEA里还要确认File -> Project Structure -> Artifacts里lib有没有被打进artifact。这事很玄学,IDEA里明明引了jar,打包时就是不带,手动在WEB-INF/lib里放一份再重新构建,最稳妥。
现场三:Access denied for user 'root'@'localhost'
现象:应用启动时数据库连接就失败,日志末尾是这句话。
原因:DBUtil里的密码写错,或者MySQL 8的root用户认证插件和驱动不兼容。
解决:先命令行验证密码是否对:mysql -u root -p,能进去说明密码没错,那就是认证插件的问题。MySQL 8执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;把认证方式改回mysql_native_password,最省事。要是这条命令提示Unknown plugin或者不想动root,那就新建一个专用账号:
CREATE USER 'lab'@'%' IDENTIFIED BY 'lab123456'; GRANT ALL PRIVILEGES ON lab_dev.* TO 'lab'@'%'; FLUSH PRIVILEGES;然后把DBUtil里的用户名密码换成lab / lab123456,这比动root的认证方式更安全也更像真实项目里的做法。
现场四:页面数据全是问号或乱码
现象:登录成功进去,设备名称、实验室名称显示???或者æµè¯。
原因:三层编码不一致。数据库是utf8mb4,页面是GBK,连接串没指定字符集,三个环节只要有一个不对就乱。
解决:从上到下统一。数据库表已经建好的情况下,先ALTER TABLE equipment CONVERT TO CHARACTER SET utf8mb4;,再把连接串里的characterEncoding=utf8加上,最后确认每个JSP头部都有pageEncoding="UTF-8"。Tomcat 8以上GET请求默认UTF-8,但POST还是要靠统一编码的Filter兜底,或者在每个doPost开头写req.setCharacterEncoding("UTF-8")。哪个环节漏了,乱码就在哪个环节出现。
现场五:启动Tomcat报端口被占用
现象:日志里Port 8080 required by Tomcat v9.0 Server at localhost is already in use。
原因:上次Tomcat没关干净,或者有其他程序占了8080。
解决:Linux执行netstat -tlnp | grep 8080,Windows执行netstat -ano | findstr 8080,找到PID后杀掉进程。顺手改端口的话,编辑conf/server.xml里<Connector port="8080"为8081,同时避坑:如果你开了多个Tomcat实例,server.xml里的<Server port="8005"也会冲突,两个都要改。这条属于典型的"参数没对上",不是代码问题。
6. 验证路径与优化方向:从能跑到像一个真正的系统
部署成功不代表系统"能用",你要按业务流程走一遍完整链路才算真正跑通。我的验证顺序是:用初始化SQL里的管理员账号登录,添加一个设备分类,再添加一台设备,把它的状态改一次,创建一条借用记录再归还,最后在维修模块发起一条报修。每一步都去MySQL里查一下对应表,确认页面显示和数据落库一致。这套流程走完,说明JSP页面、Servlet控制、JDBC访问三层是通的。
这里的检查要点是设备状态流转:设备从"在用"变成"已借出",是由借用记录的插入和状态更新两个SQL共同完成的。源码里如果只插记录不更新equipment.status,那么借还链路就是半残的。我看代码时会特意搜UPDATE equipment SET status,确认它在借出和归还两个动作里都被调用,如果没有,那这就是你系统里需要自己补的逻辑。
进阶方向上,先把数据库连接从DriverManager.getConnection换成Druid连接池,这是成本最低、收益最明显的优化。老代码每次请求都新建物理连接,数据库压力一大就卡死,换成连接池后连接复用,性能会有质变。再往后是加一个统一的编码Filter,把每个Servlet里的setCharacterEncoding删掉,集中管理。Servlet配置也可以用注解@WebServlet替代web.xml,少写一堆XML。等你哪天觉得JSP里的EL表达式不够用了,或想换前后端分离,再去了解springboot2集成jsp的目录约束——SpringBoot官方不建议用jsp,真到了那一步,大概率是你转向新架构的时候。
允许我以一个踩过的坑收尾:有一次部署到现场服务器,页面一直报500,查了一天发现是服务器MySQL的lower_case_table_names参数和本地不一致,表名大小写敏感度不同,sys_user在本地能查到,在线上直接Table doesn't exist。后来我把所有表名和SQL里的表名统一成小写,这个坑再没出现过。环境差异是最难排查的,遇到诡异问题先怀疑参数配置,别急着改代码。希望这套排查思路和部署路径,能帮你把这台"设备管理系统"稳稳跑起来——也让你真正看懂了它,而不是只会双击启动。
本文还有配套的精品资源,点击获取