简介基于Java Web的学生信息管理系统源码包面向计算机相关专业学生、Java Web初学者及课程设计/毕业设计者使用ServletJSPJDBC完成学生、课程、成绩、用户登录与管理等功能解决传统手写管理流程不便的问题。压缩包共35个文件以23个Java源文件为主另含5个JSP页面、3个Jar依赖包、1个SQL脚本、1个XML配置及说明文档整体约2.52MB。Java文件承载业务逻辑与数据库操作JSP负责页面交互SQL脚本用于初始化数据表Jar包提供JDBC驱动等依赖目录结构清晰。已有107人学习/下载。读者可获得可直接部署运行的完整工程参考登录验证、增删改查、成绩与课程联动管理等典型模块实现配合README与建表脚本能快速复现并二次扩展是理解Java Web经典分层架构的实用素材。1. 基于Java Web的学生信息管理系统一套能跑、能改、能答辩的经典骨架拿到一套基于Java Web的学生信息管理系统源码包百分之八十的人第一反应是同一个这玩意儿能不能跑起来。这套源码走的是 Servlet JSP JDBC 的经典老路没有 Spring、没有 MyBatis但也正因为老国内高校的 Java Web 课程设计和毕业设计至今还在用这套技术栈。系统做的事很集中用户登录验证、学生信息增删改查、课程管理、成绩管理附带 studenttest.sql 数据库脚本和 README 说明属于典型的“导入数据库、改个连接串、部署到 Tomcat 就能跑”的课设工程。它适合正在赶课设、需要快速复现完整 Servlet 项目的人也适合想回看传统 JSP 架构到底怎么组织代码的从业者。你不需要从零搭台源码已经把最耗时间的那部分做完了。2. 源码拆解从 web.xml 到 score.jsp先看清请求流转和四张表2.1 项目结构与 web.xml入口先看 welcome-file 和 url-pattern先把压缩包解压不要急着扔进 IDEA 让它自动建项目先看目录骨架。这套源码的布局非常标准几乎所有课设项目都是这个套路StudentManagementSystem/ ├── src/ │ └── com/ │ └── studentManagementSystem/ │ └── web/ # Servlet 类包 │ ├── LoginServlet.java │ ├── StudentServlet.java │ └── ScoreServlet.java ├── web/ │ ├── WEB-INF/ │ │ ├── lib/ # mysql-connector 等 jar 包 │ │ └── web.xml # Servlet 注册与欢迎页配置 │ ├── index.jsp # 入口判断页 │ ├── login.jsp # 登录页 │ ├── mainPage.jsp # 登录后主页面 │ ├── score.jsp # 成绩展示页 │ └── update.jsp # 信息更新页 ├── studenttest.sql # 数据库初始化脚本 └── README.md # 部署说明具体类名以你解压后的为准这里按最典型的命名展开讨论结构本身是大同小异的。src 下面是 Java 源码web 下面是 JSP 页面和 WEB-INF 配置studenttest.sql 是数据库脚本README 一般写了部署步骤和默认账号。如果 README 存在我建议先打开它很多坑作者已经在里面标出来了不用自己再趟一遍。web.xml 里有两个东西决定了系统入口。第一个是 welcome-file-list它告诉 Tomcat当访问应用根路径时默认渲染哪个 JSP第二个是 servlet-mapping它把表单请求里的 action 路径映射到具体的 Servlet 类。很多新手第一次跑不起来就是因为 login.jsp 里写的 action/login而 web.xml 里根本没有对应的 url-pattern或者是类名的大小写对不上。?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 display-nameStudentManagementSystem/display-name welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list servlet servlet-nameLoginServlet/servlet-name servlet-classcom.studentManagementSystem.web.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping /web-app逻辑说明servlet-name 只是逻辑别名真正干活的是 servlet-class 指向的全限定类名url-pattern 里的 /login 必须在 JSP 表单的 action 里出现两边对不上就 404。这里还要注意一点如果 Servlet 类上已经加了 WebServlet(/login) 注解web.xml 里就不要重复注册同一个类否则 Tomcat 启动时直接报冲突异常。参数说明XML 头部的 version 决定 Servlet API 版本。Tomcat 8.5 对应 Servlet 3.1Tomcat 9 对应 4.0但那是 jakarta.servlet 开头的包名了课设项目基本都是 3.x 这一套。你拿到源码后如果里面写的是 javax.servlet那 Tomcat 8.5 就是最省事的选择别硬上 Tomcat 10。2.2 studenttest.sql用户、学生、课程、成绩四张表怎么关联studenttest.sql 是整包的心脏没有它所有 Servlet 里的 SQL 语句都跑不动。打开这个文件我一般先确认库名、表名和字段名再看有哪些初始数据。这类学生信息管理系统最常见的设计是四张表表名用途关键字段t_user登录账号username, password, rolet_student学生基本信息student_no, name, gender, class_namet_course课程信息course_no, course_name, creditt_score成绩信息student_id, course_id, scoret_score 通过 student_id 关联 t_student通过 course_id 关联 t_course本质是学生与课程之间的多对多中间表。很多课设会把这三张表的数据分开查但成绩查询页面想要展示“学号 姓名 课程名 分数”就必须 JOIN这个后面说。CREATE DATABASE studenttest DEFAULT CHARACTER SET utf8; USE studenttest; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, role VARCHAR(20) DEFAULT student ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender VARCHAR(10), class_name VARCHAR(50) ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, course_name VARCHAR(50) NOT NULL, credit INT ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2), FOREIGN KEY (student_id) REFERENCES t_student(id), FOREIGN KEY (course_id) REFERENCES t_course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8; INSERT INTO t_user (username, password, role) VALUES (admin, 123456, admin);逻辑说明AUTO_INCREMENT 让主键自增UNIQUE 约束保证学号和用户名不重复FOREIGN KEY 保证成绩表里的学生和课程必须真实存在。ENGINEInnoDB 是外键生效的前提如果脚本里写的是 MyISAM外键约束会被 MySQL 静默忽略这一点很容易被忽略。CHARSETutf8 是中文显示正常的底线少了它后面乱码的概率极高。参数说明password 字段用 VARCHAR(50) 是课设里的常见做法。如果脚本里的密码是明文存储的说明登录验证就是明文比对演示场景没问题但如果你把它改造到真实环境必须换成 MD5 或 BCrypt 加密存储。另外 DECIMAL(5,2) 表示成绩最多 5 位数字、保留 2 位小数100.00 是可以存下的但 1000.00 会报 out of range字段宽度设计时要留意。2.3 登录验证链路JDBC 查询、Session 写入与页面跳转登录是这个系统的第一个关键链路读懂了它整个系统的骨架就清晰了一半。一次完整的登录请求要经过login.jsp 提交表单 → LoginServlet 的 doPost() → 通过 JDBC 查 t_user 表 → 查到了就把用户名写进 Session → sendRedirect 到 mainPage.jsp查不到就 forward 回 login.jsp 并携带错误信息。WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 第一步统一编码防止表单里的中文参数乱码 request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String sql SELECT * FROM t_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()) { // 登录成功写 Session设置过期时间 HttpSession session request.getSession(); session.setAttribute(loginUser, username); session.setMaxInactiveInterval(30 * 60); response.sendRedirect(mainPage.jsp); } else { // 登录失败转发回登录页携带错误提示 request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明注意两个跳转方式的区别。sendRedirect 是让浏览器重新发起一次新请求地址栏变成 mainPage.jspSession 通过响应头把会话 ID 写回浏览器forward 是服务端内部转发地址栏保持 login.jsprequest 属性里的 error 能直接带到 JSP 页面上。很多新手把这两个混着用导致登录成功后 Session 莫名其妙丢失或者错误提示一直显示不出来。参数说明setMaxInactiveInterval(30 * 60) 的单位是秒这里表示 Session 30 分钟过期。如果源码里没有这行建议补回去否则用户关掉浏览器再回来旧会话依然可能是活的这是答辩时老师最喜欢追问的隐患点之一。getSession() 不带参数时会自动创建一个新 Session如果只是想取已有的会话用 getSession(false) 更安全返回 null 也不会误建新会话。3. 部署排雷JDK 8 Tomcat 8.5 MySQL 5.7 的三方匹配3.1 版本怎么配为什么是这三个版本这套技术栈的版本匹配是部署的第一道门槛。我多次强调一个观点课设源码的部署不是版本越新越好而是兼容性越稳越好。最容易跑通的组合是 JDK 1.8、Tomcat 8.5、MySQL 5.7、mysql-connector-java 5.1.x。组件推荐版本匹配理由JDK1.8javax.servlet 时代的标准编译环境绝大多数课设源码以 JDK 8 编译Tomcat8.5对应 Servlet 3.1web.xml 写法兼容性最好MySQL5.7驱动类 com.mysql.jdbc.Driver 直接匹配无需额外参数驱动mysql-connector-java 5.1.x与 MySQL 5.7 配套class name 不需要 cj 前缀如果你本机装的是 JDK 17 硬跑这套代码大概率会遇到 javax.servlet 缺失或者模块化报错如果用的是 Tomcat 10web.xml 头部的 javax.servlet 直接不认识。这些不是不能解决但课设阶段不值得把时间花在这上面。MySQL 8 也不是不能用但伴随两个翻车点驱动类从 com.mysql.jdbc.Driver 变成了 com.mysql.cj.jdbc.DriverJDBC 连接串里还要追加 serverTimezoneAsia/Shanghai否则启动后查询直接报时区错误。这些差异化改动放在排查章节里细说部署时优先按经典组合来。3.2 数据库导入与连接串修改先跑通 studenttest.sql数据库导入顺序有讲究。先确保数据库本身存在再往里面导表和数据。打开命令行在 studenttest.sql 所在目录执行mysql -u root -p -e CREATE DATABASE IF NOT EXISTS studenttest DEFAULT CHARACTER SET utf8; mysql -u root -p studenttest studenttest.sql逻辑说明第一行先建库指定 utf8 字符集第二行把 studenttest.sql 里的建表语句和 INSERT 语句导入到 studenttest 库。如果脚本开头已经带了 CREATE DATABASE 语句第一行可以省略但多数课设脚本只包含表和数据的操作先建库更稳妥。执行完后可以用 mysql -u root -p -e USE studenttest; SHOW TABLES; 验证是否成功导入了四张表。接下来改连接串。项目里负责数据库连接的工具类一般叫 DBUtil.java 或 DBConnection.java你只需要全局搜 jdbc:mysql 就能定位。把它改成你本地数据库的真实账号public class DBUtil { // 驱动类名MySQL 5.7 用 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.jdbc.Driver; // 连接串useUnicode 和 characterEncoding 决定中文能否正常读写 private static final String URL jdbc:mysql://localhost:3306/studenttest?useUnicodetruecharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(数据库驱动加载失败请确认 jar 已放入 WEB-INF/lib, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }逻辑说明Class.forName(DRIVER) 在类加载时注册 JDBC 驱动这是 JDBC 4.0 之前的标准写法课设代码里基本都是这种。URL 里的 useUnicodetrue 和 characterEncodingutf8 负责让 JDBC 层按 UTF-8 编码读写缺了它即使数据库表建成了 utf8中文到 Java 字符串里也会变成乱码。参数说明PASSWORD 改成你本地 MySQL 的密码不改的话所有查询都会报 Access denied。URL 中的 localhost:3306 也要对照你的 MySQL 实际端口如果你装的是 MySQL 8 且用了不同端口这里要同步改。改完之后用 IDE 或命令行跑一个 main 方法调用 DBUtil.getConnection()能拿到 Connection 对象说明数据库层已经通了。3.3 发布到 TomcatIDEA 配置与浏览器访问路径数据库通了以后把项目发布到 Tomcat。IDEA 里的操作路径其实很固定按下面顺序做一遍基本不会出问题打开解压后的项目目录确认 src 目录被 IDEA 标记为 Sources Rootweb 目录被标记为 Web ResourceFile → Project Structure → Artifacts → 新建 Web Application: Exploded把 src 和 web 都加进去Run → Edit Configurations → 新增 Tomcat Server Local在 Deployment 标签里加上刚建好的 artifact设置 Application context 为 /StudentManagementSystem或者直接保持根路径启动 Tomcat控制台出现 Server startup in 字样后浏览器访问对应地址这里有个习惯问题课设阶段不建议用 war 包部署IDEA 的 exploded 方式改完 JSP 刷新浏览器就能生效不需要反复重启 Tomcat迭代效率高很多。改 Java 代码时需要重启但改 JSP 和配置文件不用。访问路径要和 artifact 名称对应。如果 IDEA 里的 Application context 是 /StudentManagementSystem那么登录页地址就是 http://localhost:8080/StudentManagementSystem/login.jsp。如果 web.xml 里配置了 welcome-file 为 login.jsp直接访问 http://localhost:8080/StudentManagementSystem/ 也会自动跳到登录页。注意区分这两个概念welcome-file 只在访问根路径时生效手动输入 login.jsp 同样能打开页面。4. 动手改造登录拦截、CRUD 防注入与成绩条件查询4.1 登录拦截一个 Filter 补上最容易被扣分的环节绝大部分课设源码有一个共同问题登录只校验一次登录成功后页面直接暴露在 URL 下。你把地址改成 mainPage.jsp 绕过 login.jsp系统照样放行。原因在于 Servlet 只负责登录那一次的 Session 校验并没有在每个受保护页面背后都校验一遍。答辩时老师最喜欢问的就是你怎么证明没登录的人进不来答案通常是一个 Filter 拦截全部请求白名单之外的路径强制校验 SessionWebFilter(/*) 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.endsWith(login.jsp) || uri.endsWith(/login) || uri.contains(/static/)) { chain.doFilter(request, response); return; } // 取已有 Session避免每次请求都新建 HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明req.getSession(false) 是这里的关键参数false 表示如果当前没有 Session 就返回 null而不是自动创建一个新的避免每个请求都无谓地生成会话对象。白名单里必须包含 login.jsp 和 /login否则你连登录页都进不去这是最蠢也最常见的翻车现场。参数说明req.getContextPath() 会拼接上应用的上下文路径这样即使项目改了部署名重定向的地址也是对的。如果你把 Filter 加到了 WebFilter(/*)注意它会把 JSP 和静态资源全部拦进来所以白名单条件要写全。更精细的做法是直接放行 .css、.js、.jpg 等静态资源后缀避免资源被挡影响页面渲染。4.2 增删改查的防注入写法从 Statement 到 PreparedStatement课设源码里最常见的安全隐患是 SQL 拼接。比如有人写这种查询String sql SELECT * FROM t_student WHERE name name ;当 name 参数传进一个 admin OR 11整条 SQL 就变成了恒真条件可以直接把全表数据拉出来这就是 SQL 注入的雏形。数据不敏感不代表可以留着这个漏洞而且答辩时老师一眼就能看出来。改成 PreparedStatement 是最低成本的补救方案String sql SELECT * FROM t_student WHERE name ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, name); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { String studentNo rs.getString(student_no); String studentName rs.getString(name); // 取出其他字段并渲染 } } }逻辑说明? 是占位符setString(1, name) 会把这些值交给驱动做转义处理单引号、反斜杠等特殊字符不再具备拼接 SQL 的能力。PreparedStatement 还有一层优势是预编译SQL 结构固定时数据库只编译一次多次执行性能更好。新增和修改操作的套路完全一致// 新增学生 String insertSql INSERT INTO t_student (student_no, name, gender, class_name) VALUES (?, ?, ?, ?); // 更新学生 String updateSql UPDATE t_student SET name ?, gender ?, class_name ? WHERE student_no ?;参数说明PreparedStatement 的下标从 1 开始顺序必须和 SQL 里的占位符从左到右一一对应。写新增语句时最容易犯的错是字段顺序和 VALUES 顺序不一致比如先写了 gender 后写 name但代码里 setString 顺序没跟上最后库里存的字段值是错位的。我自己的习惯是每写完一条 SQL先对着字段列表数一遍占位符序号再写 set 语句基本能杜绝这类问题。4.3 成绩查询改造把 score.jsp 从全表展示改成按条件筛选score.jsp 默认实现通常是把 t_score 全表查出来再用循环渲染成表格。数据量小的时候看着还行一旦学生和课程数量上来这种全量展示基本没法用。改成带条件的组合查询是耗时最少、见效最明显的改造点。String studentName request.getParameter(studentName); String courseId request.getParameter(courseId); StringBuilder sql new StringBuilder( SELECT s.student_no, st.name, c.course_name, s.score FROM t_score s JOIN t_student st ON s.student_id st.id JOIN t_course c ON s.course_id c.id WHERE 11); if (studentName ! null !studentName.trim().isEmpty()) { sql.append( AND st.name LIKE ?); } if (courseId ! null !courseId.trim().isEmpty()) { sql.append( AND c.id ?); }逻辑说明JOIN 联查把三张表拼成一张带文字描述的成绩列表比直接查 t_score 直观得多前端 JSP 里可以直接输出 st.name 和 c.course_name。WHERE 11 是一个拼接技巧——后面所有 AND 条件都不需要再判断是否是第一个条件代码结构清爽很多也不会拼出 WHERE 和 AND 相邻这种语法错误。参数说明LIKE 查询的参数要自己拼接通配符代码里传值时用 % studentName % 才能实现模糊匹配。courseId 参数是字符串类型如果前端下拉框传的是空串必须靠 !courseId.trim().isEmpty() 这个判断把它过滤掉否则空串会拼出一个没意义的 AND 条件查出来的结果和全量一样但 SQL 里多了一段垃圾条件。5. 避坑清单部署期间最容易翻车的 5 个问题5.1 中文乱码JSP、Servlet、MySQL 三层编码不一致现象登录管理员账号后界面上的中文用户名变成问号或者乱码学生信息里的中文姓名显示成一堆无法辨认的字符。原因中文乱码从来不是单个点的故障而是三层编码不一致叠加出来的结果。JSP 页面的 pageEncoding 不是 UTF-8Servlet 里没有调用 request.setCharacterEncoding(UTF-8)JDBC 连接串少了 characterEncodingutf8MySQL 表本身是 latin1 字符集。四个环节里任何一个不对中文都会出问题。解决从前往后统一成 UTF-8。每个 JSP 页面头部加 % page pageEncodingUTF-8 %doPost 方法第一行写 request.setCharacterEncoding(UTF-8)DBUtil 的 URL 里带上 useUnicodetruecharacterEncodingutf8studenttest.sql 里的建表语句改成 DEFAULT CHARSETutf8。改完重启 Tomcat浏览器用 CtrlF5 强刷一次因为旧的页面可能已经被浏览器缓存了。5.2 驱动类找不到ClassNotFoundException 的两种现场现象Tomcat 启动后控制台报 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或者启动没报错、但第一次点登录按钮时页面 500堆栈里同样指向这句。原因第一种是 mysql-connector 的 jar 包根本没放到 WEB-INF/lib 目录下第二种是 jar 放到了 src 下的普通目录IDEA 没有把它识别为库。两者本质都是 ClassLoader 在运行时的 classpath 里找不到驱动类。解决把 mysql-connector-java-5.1.x.jar 复制到 web/WEB-INF/lib 下在 IDEA 里右键这个 jar选择 Add as Library确认作用范围覆盖到项目。如果这样做了还报错检查是不是项目里存在多份 web.xml 或者 build 输出把 lib 目录排除了。课设项目通常不是 Maven驱动就是靠 WEB-INF/lib 这唯一的加载路径路径别放错。5.3 404 与 500URL 映射和 Servlet 注册的核对路径现象点登录按钮后页面 404或者跳转到 mainPage.jsp 后整页报 500。404 多半是请求根本没找到对应的 Servlet500 多半是 Servlet 找到了但内部执行出错。原因404 的根源绝大多数是 login.jsp 表单里的 action 和 web.xml 里的 url-pattern 不一致。比如表单写 actionlogin.doweb.xml 配的 /loginTomcat 找不到自然 404。500 则通常是 SQL 语句里的字段名写错了或者 score.jsp 里访问了 ResultSet 中不存在的列。还有一个常见情况Servlet 类上同时有 WebServlet 注解和 web.xml 里的 servlet 注册Tomcat 启动时直接报部署异常。解决排查顺序按三层走。第一层浏览器 F12 打开开发者工具看 Network 里请求的真实 URL 路径确认到底发到了哪里第二层核对 web.xml 和 WebServlet 注解的 url-pattern 是否一致注意同时注册的冲突第三层看 IDE 控制台的异常堆栈绝大多数 500 的根因都写在第一行异常信息里。5.4 8080 端口被占最不起眼的启动失败原因现象Tomcat 启动时报 Address already in use: JVM_Bind或者控制台一直刷日志但始终看不到 Server startup in 开头的成功提示。原因本机已经有一个 Tomcat 服务或者某个程序占用了 8080 端口。这个错误不报在代码层面所以很多人盯着日志找了半天最后才发现根本是端口被占了。解决Windows 上先执行 netstat -ano | findstr 8080 找到占用端口的 PID然后用 taskkill /PID 对应进程号 /F 强制结束也可以直接改 Tomcat 的 conf/server.xml 里的 port8080 改成 8090顺手把 8005 这个关闭 Tomcat 的端口也改掉避免两个端口号码都被人占了。实操中我一般选择改端口因为不确定是谁在占用改端口比查进程快得多。6. 验证与调试用 Debug 和浏览器开发者工具把黑匣子打开6.1 用 IDEA Debug 跟踪一次登录请求这套系统的关键逻辑都在后端最有效的验证方式不是靠 System.out.println 打日志而是直接在关键位置打断点。我会在 LoginServlet 的 doPost 方法第一行打断点再在 ps.executeQuery() 那行打一个然后以 Debug 模式启动 Tomcat在登录页输入账号密码。断点停住后Step Over 逐行看 request.getParameter 取到的值对不对判断 PreparedStatement 里的 SQL 有没有被正确拼接然后 Step Into 进入 executeQuery 内部看 ResultSet 里到底有没有查出行数据。这一步能直接区分出三类问题参数没传过来、SQL 有问题、数据库里确实没这条记录。定位速度比猜快一个量级。6.2 用浏览器开发者工具核对参数提交很多前端和后端之间的对接问题其实不用看 Java 代码就能定位。在 Chrome 里按 F12 打开开发者工具切到 Network 标签勾选 Preserve log重新提交一次登录表单。点击 login 请求看 Headers 里的 Form Data——如果 username 和 password 根本不在其中说明表单控件 name 属性写错了如果在但值是空的说明控件没有正确绑定。同理点登录后的跳转路径也能在这里看到。请求本身返回 302 就代表 sendRedirect 成功了返回 200 但页面还是 login.jsp说明走的是 forward 且 Session 没写进去。这些信息在代码排查之前就能帮你缩小范围。6.3 部署新环境后的一份自测清单我从一次又一次的部署经历里总结出一条规律每次拿到这类 Servlet JSP 老项目并配置到新环境时强制按同一条线走一遍——先导入 SQL 脚本并确认四张表存在再改 DBUtil 的连接串和密码再核对 web.xml 或注解里的 URL 映射最后启动时盯住端口占用的输出。四步顺序不乱任何一步有异常都按对应章节的排查路径处理乱序操作时经常会出现“数据库没导就开始改代码、改了代码发现是驱动问题”这种低效循环。希望这套排查链路对你有用省下你在部署和答辩前夜摸黑踩坑的时间。本文还有配套的精品资源点击获取