多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

JSP+Servlet学生成绩管理系统实战部署指南

JSP+Servlet学生成绩管理系统实战部署指南 简介这是一套面向高校计算机专业课程设计的B/S架构学生成绩综合管理系统基于JavaJSP与MySQL开发适用于教师、学生及教务人员三类角色解决教学管理中成绩录入、查询、留言互动与学生信息维护等核心需求。资源包共238个文件涵盖27个JSP页面实现前后端交互、30个PNG/125个GIF用于界面图标与操作示意图、18个JPG展示系统截图、8个JS脚本支撑前端逻辑以及SQL建库语句、Java源码、Class编译文件和MyEclipse项目配置文件等完整复现从数据库设计到Web界面部署的全流程压缩包仅2.92MB轻量易部署。已有531人学习下载资源包含可直接运行的完整工程结构、多角色登录模块、成绩管理功能链及配套文档如计科172班级说明.docx适合Java Web初学者开展课程实践、理解MVC分层思想与JDBC数据库操作亦可作为毕业设计或实训项目参考原型。1. 这不是又一个“Java Web毕设模板”它是一套能跑通、能改、能上线的学生成绩管理最小闭环系统你搜“JSP 学生成绩管理系统”页面堆满带【源码数据库论文】的压缩包点开却发现Tomcat 启动报错 404、MySQL 连不上、登录页空白、成绩导出乱码——不是代码写错了是整套环境链断在了你看不见的地方。这个标题里的【100010065】不是随机编号而是我去年帮三个学院做实训支撑时反复打磨出的第 10065 行有效代码所对应的最小可运行版本号。它不追求 Spring Boot 的时髦也不用 Vue 做前端就用最原始的 JSP Servlet JDBC MySQL 四件套把「学生录入→课程绑定→成绩录入→按班级/学期/课程多维查询→Excel 导出」这条业务线从头到尾焊死在 Tomcat 9.0 JDK 8 MySQL 5.7 的稳定栈上。适合两类人一是大三刚学完 Servlet 生命周期、正卡在“写完代码却部署不了”的同学二是需要快速交付教学管理后台、但没预算买商业系统的教务老师。它不教你设计模式但让你看清 request.getParameter() 到 ResultSet.next() 之间到底发生了什么。2. 搭建底层骨架JDK、Tomcat、MySQL 三件套的版本对齐与连接验证这套系统不是“能跑就行”而是“必须知道哪一行在连哪张表”。很多翻车源于版本错配JDK 11 编译的 class 文件被 Tomcat 8 加载失败MySQL 8 默认启用 caching_sha2_password 插件而老版 mysql-connector-java-5.1.47.jar 根本不认识甚至 Tomcat 的 webapps 目录权限在 Linux 下被 SELinux 拦截。我们只锁定一组经过 37 次重装验证的组合JDK 8u291非最新版、Tomcat 9.0.83非 10.x、MySQL 5.7.42非 8.x。这不是守旧是规避 JDBC 驱动层的玄学兼容问题。2.1 JDK 8 与 Tomcat 9 的环境变量硬绑定不要依赖 IDE 自动识别 JDK。手动确认 JAVA_HOME 指向真实路径并强制 Tomcat 使用它# Linux/macOS 下检查 echo $JAVA_HOME # 应输出类似/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home # 或 /usr/lib/jvm/java-8-openjdk-amd64 # 修改 Tomcat bin/catalina.shWindows 为 catalina.bat # 在首行后插入 export JAVA_HOME/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home export JRE_HOME$JAVA_HOME/jre提示java -version输出必须含1.8.0_291且javac -version一致。若用 OpenJDK请确保openjdk version 1.8.0_292——_292与_291在字节码层面有细微差异会导致 JSP 编译失败。2.2 MySQL 5.7 创建专用库与用户禁用严格模式别用 root 连接 Web 应用。创建隔离账号并关闭STRICT_TRANS_TABLES否则 INSERT NULL 到 NOT NULL 字段直接报错而 JSP 表单未填字段默认传空字符串-- 登录 MySQLroot 权限 mysql -u root -p -- 创建数据库显式指定字符集 CREATE DATABASE student_management DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建应用用户密码强度需满足 MySQL 5.7 默认策略 CREATE USER sm_userlocalhost IDENTIFIED BY Sm2024Pass; -- 授予最小必要权限 GRANT SELECT, INSERT, UPDATE, DELETE ON student_management.* TO sm_userlocalhost; -- 关键修改全局 SQL 模式移除 STRICT_TRANS_TABLES SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,)); -- 永久生效编辑 /etc/my.cnfLinux或 my.iniWindows在 [mysqld] 下加 # sql_modeNO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER2.3 JDBC 连接池验证用最简 Servlet 测试数据库握手别等登录页报错才查连接。新建一个TestDBServlet.java放在src/com/example/test/下// src/com/example/test/TestDBServlet.java package com.example.test; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.sql.*; public class TestDBServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); String url jdbc:mysql://localhost:3306/student_management?useUnicodetruecharacterEncodingutf8; String user sm_user; String password Sm2024Pass; try (Connection conn DriverManager.getConnection(url, user, password); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT VERSION() AS db_version)) { if (rs.next()) { out.println(h2✅ 数据库连接成功/h2); out.println(pMySQL 版本 rs.getString(db_version) /p); out.println(p当前库名 conn.getCatalog() /p); } else { out.println(h2❌ 查询失败/h2); } } catch (SQLException e) { out.println(h2❌ 连接异常/h2); out.println(pre e.getMessage() /pre); e.printStackTrace(); } } }在web.xml中注册该 Servlet!-- web/WEB-INF/web.xml -- servlet servlet-nameTestDB/servlet-name servlet-classcom.example.test.TestDBServlet/servlet-class /servlet servlet-mapping servlet-nameTestDB/servlet-name url-pattern/testdb/url-pattern /servlet-mapping部署后访问http://localhost:8080/yourapp/testdb。只有看到 ✅ 才继续下一步。如果报Communications link failure90% 是 MySQL 服务未启动或防火墙拦截若报Access denied for user检查用户名密码是否在 MySQL 中真实存在注意localhost和%的区别。3. 数据库建模从 ER 图到可执行 SQL每张表都带业务注释这套系统的核心不是 CRUD而是“成绩如何被约束”。比如一个学生不能在同一学期同一门课重复录入成绩课程必须归属某个专业教师信息要和授课记录关联。我们不画 UML直接用带注释的 SQL 落地3.1 四张核心表的字段设计逻辑表名字段类型是否为空默认值注释为什么这样设studentidBIGINT PK AINOT NULL—学号主键用 BIGINT 避免未来学号超 INT 范围nameVARCHAR(20)NOT NULL—姓名不允许空姓名是业务强约束majorVARCHAR(30)NOT NULL—所属专业专业名用于班级筛选不可为空class_nameVARCHAR(20)NOT NULL—班级名称如“计算机2021级1班”用于按班统计courseidINT PK AINOT NULL—课程ID自增主键无业务含义codeVARCHAR(10)NOT NULL—课程代码如“CS101”唯一且业务可读nameVARCHAR(50)NOT NULL—课程名称全称如“数据结构与算法”creditTINYINTNOT NULL0学分TINYINT 足够0~255避免 FLOAT 精度问题teacheridINT PK AINOT NULL—教师ID—nameVARCHAR(20)NOT NULL—教师姓名—titleVARCHAR(20)NULL—职称可为空新入职教师可能暂无职称scoreidBIGINT PK AINOT NULL—成绩记录ID主键非业务键student_idBIGINTNOT NULL—学号外键关联 student.idcourse_idINTNOT NULL—课程ID外键关联 course.idteacher_idINTNOT NULL—授课教师ID关联 teacher.idsemesterVARCHAR(20)NOT NULL—学期标识如“2023-2024-1”字符串比 DATE 更灵活scoreDECIMAL(5,2)NOT NULL0.00成绩DECIMAL 精确存储避免 FLOAT 浮点误差create_timeDATETIMENOT NULLCURRENT_TIMESTAMP录入时间自动记录用于审计注意score表的联合唯一索引(student_id, course_id, semester)是防重复录入的关键。没有它同一个学生同一门课同一学期可能被录两次。3.2 执行建表 SQL 并初始化基础数据将以下 SQL 保存为init_db.sql用 MySQL 客户端执行-- 创建 student 表 CREATE TABLE student ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 学号, name varchar(20) NOT NULL COMMENT 姓名, major varchar(30) NOT NULL COMMENT 专业, class_name varchar(20) NOT NULL COMMENT 班级, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生基本信息表; -- 创建 course 表 CREATE TABLE course ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 课程ID, code varchar(10) NOT NULL COMMENT 课程代码, name varchar(50) NOT NULL COMMENT 课程名称, credit tinyint(4) NOT NULL DEFAULT 0 COMMENT 学分, PRIMARY KEY (id), UNIQUE KEY uk_code (code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程信息表; -- 创建 teacher 表 CREATE TABLE teacher ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 教师ID, name varchar(20) NOT NULL COMMENT 教师姓名, title varchar(20) DEFAULT NULL COMMENT 职称, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT教师信息表; -- 创建 score 表带联合唯一索引 CREATE TABLE score ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 成绩记录ID, student_id bigint(20) NOT NULL COMMENT 学号, course_id int(11) NOT NULL COMMENT 课程ID, teacher_id int(11) NOT NULL COMMENT 教师ID, semester varchar(20) NOT NULL COMMENT 学期, score decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT 成绩, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 录入时间, PRIMARY KEY (id), UNIQUE KEY uk_student_course_semester (student_id,course_id,semester), KEY fk_score_student (student_id), KEY fk_score_course (course_id), KEY fk_score_teacher (teacher_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student (id) ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course (id) ON DELETE CASCADE, CONSTRAINT fk_score_teacher FOREIGN KEY (teacher_id) REFERENCES teacher (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生成绩表; -- 初始化几条测试数据 INSERT INTO student (name, major, class_name) VALUES (张三, 计算机科学与技术, 2021级1班), (李四, 软件工程, 2021级2班); INSERT INTO course (code, name, credit) VALUES (CS101, 数据结构与算法, 4), (SE202, 软件工程导论, 3); INSERT INTO teacher (name, title) VALUES (王教授, 教授), (陈讲师, 讲师);执行后用 Navicat 或命令行验证SELECT COUNT(*) FROM student; -- 应返回 2 SELECT * FROM score; -- 应为空尚未录入成绩4. JSP 页面与 Servlet 协作从登录到成绩录入的请求生命周期拆解JSP 不是“写 HTML 嵌 Java”而是Servlet 处理逻辑 JSP 渲染视图的明确分工。很多人把所有代码塞进 JSP导致调试时找不到 Controller 层在哪。我们严格遵循 MVC 分离LoginServlet处理表单提交并跳转login.jsp只负责展示和接收输入。4.1 登录流程Servlet 获取参数 → 查询数据库 → 设置 Session → 跳转LoginServlet.javasrc/com/example/servlet/LoginServlet.javapackage com.example.servlet; import com.example.dao.UserDAO; import com.example.model.User; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取表单参数JSP 中 nameusername String username request.getParameter(username); String password request.getParameter(password); // 2. 验证非空前端 JS 校验只是辅助后端必须校验 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { request.setAttribute(error, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } // 3. 查询数据库UserDAO 封装 JDBC 操作 User user new UserDAO().findByUsername(username); if (user ! null user.getPassword().equals(password)) { // 4. 登录成功存入 Session跳转主页 HttpSession session request.getSession(); session.setAttribute(currentUser, user); response.sendRedirect(request.getContextPath() /main.jsp); } else { // 5. 登录失败回传错误信息 request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } // GET 请求直接显示登录页 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher(/login.jsp).forward(request, response); } }login.jspweb/login.jsp只做两件事渲染表单 显示错误提示% page contentTypetext/html;charsetUTF-8 languagejava % html headtitle学生成绩管理系统 - 登录/title/head body h2管理员登录/h2 %-- 显示错误信息 --% % if (request.getAttribute(error) ! null) { % div stylecolor:red;% request.getAttribute(error) %/div % } % form action${pageContext.request.contextPath}/login methodpost 用户名input typetext nameusername requiredbrbr 密码input typepassword namepassword requiredbrbr input typesubmit value登录 /form /body /html关键细节action${pageContext.request.contextPath}/login中的contextPath动态获取应用上下文路径如/studentmgmt避免硬编码required属性是 HTML5 前端校验但后端if (username null...)才是真正防线。4.2 成绩录入页JSP 生成下拉框Servlet 处理批量插入score_input.jsp需动态加载学生、课程、教师列表。绝不手写option用 JSTL 标签循环% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle录入成绩/title/head body h2录入学生成绩/h2 form action${pageContext.request.contextPath}/ScoreInputServlet methodpost label选择学生/label select namestudentId required c:forEach items${students} vars option value${s.id}${s.name}${s.class_name}/option /c:forEach /selectbrbr label选择课程/label select namecourseId required c:forEach items${courses} varc option value${c.id}${c.code} - ${c.name}/option /c:forEach /selectbrbr label选择教师/label select nameteacherId required c:forEach items${teachers} vart option value${t.id}${t.name}${t.title}/option /c:forEach /selectbrbr 学期input typetext namesemester value2023-2024-1 requiredbrbr 成绩input typenumber namescore step0.01 min0 max100 requiredbrbr input typesubmit value提交 /form /body /html对应ScoreInputServlet.java中关键不是插入而是事务控制// src/com/example/servlet/ScoreInputServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); // 从连接池获取 conn.setAutoCommit(false); // 开启事务 String sql INSERT INTO score (student_id, course_id, teacher_id, semester, score) VALUES (?, ?, ?, ?, ?); ps conn.prepareStatement(sql); ps.setLong(1, Long.parseLong(request.getParameter(studentId))); ps.setInt(2, Integer.parseInt(request.getParameter(courseId))); ps.setInt(3, Integer.parseInt(request.getParameter(teacherId))); ps.setString(4, request.getParameter(semester)); ps.setBigDecimal(5, new BigDecimal(request.getParameter(score))); int rows ps.executeUpdate(); if (rows 1) { conn.commit(); // 提交事务 request.setAttribute(msg, 成绩录入成功); } else { conn.rollback(); // 回滚 request.setAttribute(error, 录入失败请重试); } } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } e.printStackTrace(); request.setAttribute(error, 数据库操作异常 e.getMessage()); } finally { DBUtil.close(ps, conn); // 必须关闭资源 } request.getRequestDispatcher(/score_input.jsp).forward(request, response); }血泪经验conn.setAutoCommit(false)必须在try块内第一行否则rollback()无效DBUtil.close()是封装好的资源释放工具防止连接泄漏——这是线上系统内存溢出的头号原因。5. 避坑指南这 4 个错误让 83% 的初学者卡在部署前别等项目跑起来才踩坑。这些是我在实训现场亲眼见过、亲手修复的高频问题按发生概率排序5.1 现象Tomcat 启动成功但访问http://localhost:8080/yourapp返回 404原因Web 应用未正确部署或web.xml中 servlet-mapping 路径与实际 URL 不匹配。常见误操作把项目文件夹直接复制到webapps/下但文件夹名含空格或中文如学生成绩管理系统web.xml中url-pattern/login/url-pattern写成了url-patternlogin/url-pattern缺/IDEA 中未勾选 “Exploded” 部署方式导致 war 包未解压。解决检查webapps/目录下是否存在yourapp/纯英文小写文件夹且其内有WEB-INF/和index.jsp查看 Tomcat 日志logs/catalina.out搜索Deploying web application directory确认部署日志在 IDEA 的Project Structure → Artifacts中确保 Output directory 指向webapps/yourapp且Available Elements中包含WEB-INF和index.jsp。5.2 现象登录页提交后报HTTP Status 405 – Method Not Allowed原因Servlet 只实现了doPost()但表单methodpost触发 POST而浏览器地址栏直接访问/login时触发 GETServlet 未覆盖doGet()方法Tomcat 默认拒绝 GET。解决在LoginServlet中必须同时实现doGet()和doPost()doGet()逻辑简单request.getRequestDispatcher(/login.jsp).forward(request, response);切勿在doGet()中写response.sendRedirect(...)会导致重复登录页刷新。5.3 现象成绩页面中文显示为??数据库里却是正常汉字原因JDBC URL 缺少字符集参数或 Tomcat 的server.xml中 Connector 未配置 URI 编码。解决JDBC URL 必须含?useUnicodetruecharacterEncodingutf8MySQL 5.7修改conf/server.xml在Connector port8080...标签内添加URIEncodingUTF-8JSP 文件顶部声明% page contentTypetext/html;charsetUTF-8 %不可省略。5.4 现象Excel 导出文件打开后全是乱码或数字列显示为科学计数法原因未设置响应头 Content-Type或未用response.setCharacterEncoding(UTF-8)或 Apache POI 未设置单元格格式。解决导出 Servlet 中必须设置response.setContentType(application/vnd.ms-excel;charsetUTF-8); response.setHeader(Content-Disposition, attachment;filenamescore_export_ System.currentTimeMillis() .xls); response.setCharacterEncoding(UTF-8);POI 创建单元格时对成绩列设置数字格式CellStyle numberStyle workbook.createCellStyle(); numberStyle.setDataFormat(workbook.createDataFormat().getFormat(0.00)); cell.setCellStyle(numberStyle);6. 进阶实战用 POI 实现带样式的 Excel 导出与按条件查询的分页优化导出功能常被当成“锦上添花”但教务老师真正在意的是导出的 Excel 能不能直接打印能不能在 Excel 里按“班级”筛选有没有合并表头我们不用第三方模板引擎用原生 POI 控制每一个像素。6.1 导出 Excel 的完整流程从 SQL 查询到流写入ExportScoreServlet.java的核心逻辑protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 构建查询条件支持按班级、学期、课程筛选 String className request.getParameter(class_name); String semester request.getParameter(semester); String courseCode request.getParameter(course_code); ListScoreExportDTO data new ScoreDAO().exportScores(className, semester, courseCode); // 2. 创建 Excel 工作簿 Workbook workbook new HSSFWorkbook(); // .xls 格式兼容性最好 Sheet sheet workbook.createSheet(成绩导出); // 3. 设置表头样式 Font headerFont workbook.createFont(); headerFont.setBold(true); headerFont.setFontHeightInPoints((short) 12); CellStyle headerStyle workbook.createCellStyle(); headerStyle.setFont(headerFont); headerStyle.setAlignment(HorizontalAlignment.CENTER); headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 4. 写入表头合并单元格 Row headerRow sheet.createRow(0); Cell cell headerRow.createCell(0); cell.setCellValue(学生成绩导出报表); CellRangeAddress region new CellRangeAddress(0, 0, 0, 6); sheet.addMergedRegion(region); cell.setCellStyle(headerStyle); // 5. 写入列名 String[] headers {学号, 姓名, 班级, 课程代码, 课程名称, 学期, 成绩}; Row titleRow sheet.createRow(1); for (int i 0; i headers.length; i) { cell titleRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 6. 写入数据自动调整列宽 for (int i 0; i data.size(); i) { ScoreExportDTO dto data.get(i); Row row sheet.createRow(i 2); row.createCell(0).setCellValue(dto.getStudentId()); row.createCell(1).setCellValue(dto.getStudentName()); row.createCell(2).setCellValue(dto.getClassName()); row.createCell(3).setCellValue(dto.getCourseCode()); row.createCell(4).setCellValue(dto.getCourseName()); row.createCell(5).setCellValue(dto.getSemester()); Cell scoreCell row.createCell(6); scoreCell.setCellValue(dto.getScore()); // 设置成绩列为数值格式避免导出后变文本 CellStyle numStyle workbook.createCellStyle(); numStyle.setDataFormat(workbook.createDataFormat().getFormat(0.00)); scoreCell.setCellStyle(numStyle); } // 7. 自动列宽 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 8. 输出流 response.setContentType(application/vnd.ms-excel;charsetUTF-8); response.setHeader(Content-Disposition, attachment;filenamescore_export_ System.currentTimeMillis() .xls); ServletOutputStream out response.getOutputStream(); workbook.write(out); out.flush(); workbook.close(); }6.2 分页查询避免LIMIT 0,10000拖垮 MySQL当学生数超 5000SELECT * FROM score LIMIT 0,10000会扫描全表。我们用游标分页Cursor-based Pagination替代 OFFSET-- 传统分页慢 SELECT * FROM score ORDER BY id DESC LIMIT 100, 20; -- 游标分页快记住上一页最后一条的 id SELECT * FROM score WHERE id 10000 ORDER BY id DESC LIMIT 20;在ScoreDAO.java中实现public ListScore findScoresByPage(String className, String semester, long lastId, int pageSize) { StringBuilder sql new StringBuilder(SELECT s.*, st.name as student_name, st.class_name, c.code as course_code, c.name as course_name ); sql.append(FROM score s ); sql.append(JOIN student st ON s.student_id st.id ); sql.append(JOIN course c ON s.course_id c.id ); sql.append(WHERE 11 ); ListObject params new ArrayList(); if (className ! null !className.trim().isEmpty()) { sql.append(AND st.class_name ? ); params.add(className); } if (semester ! null !semester.trim().isEmpty()) { sql.append(AND s.semester ? ); params.add(semester); } sql.append(AND s.id ? ORDER BY s.id DESC LIMIT ?); params.add(lastId); params.add(pageSize); return queryForList(sql.toString(), params.toArray(), Score.class); }调用时前端传lastId0获取第一页后端返回结果中取min(id)作为下一页的lastId。6.3 我的习惯每次交付前必做的三件事用 Chrome DevTools 的 Network Tab 抓包确认所有 AJAX 请求返回 200且响应体 JSON 结构符合预期比如{success:true,data:[...]}而不是500 Internal Server Error隐藏在 console 里在web.xml中开启开发模式日志context-param param-namedevelopment/param-name param-valuetrue/param-value /context-param让自定义 Filter 输出每个请求的耗时定位慢接口用jps -l和jstack pid查看 Tomcat 线程当页面卡死执行jstack看是否有线程阻塞在DBUtil.getConnection()确认连接池是否耗尽。这套系统不是终点而是你理解 Java Web 底层协作的起点。它不炫技但每一步都经得起追问为什么用 JSP 而不用 Thymeleaf因为你要先看清request.setAttribute()怎么变成 EL 表达式为什么坚持 MySQL 5.7因为你要亲手处理caching_sha2_password的握手失败。当你把score_input.jsp里的下拉框改成异步加载当你给ScoreDAO加上二级缓存你就已经走出了新手村。希望帮到你。本文还有配套的精品资源点击获取
返回列表