☰
JavaWeb+JSP+MySQL成绩管理系统:从源码部署到答辩加分实战
2026/10/5 6:08:35 网站建设 项目流程

简介:这是一份基于JavaWeb学生成绩管理系统的完整项目源码,采用Jsp+MySQL技术栈,面向计算机专业正在完成课程设计、期末大作业的学生,也适合需要JavaWeb项目实战练习的学习者。项目曾作为大三期末大作业提交,经导师指导并获98分评审,整体完成度与规范程度较高。压缩包共353个文件,大小约9.85MB,包含24个Jsp页面、53个Java源文件及对应Class文件,并配有SQL脚本和db数据库文件,可直接导入MySQL运行;同时提供CSS、JS、图片等前端资源及项目配置文件,目录结构清晰。核心业务覆盖老师、学生、成绩、考试、班级等模块,涉及Servlet控制器、Service业务层、DAO数据层与工具类实现,便于理解JavaWeb分层开发思路。目前已有198人学习下载,学习者可通过源码掌握从数据库设计到前后端交互的完整流程,参考登录验证、成绩录入与统计等功能实现,也能直接复用于相似课题的快速搭建。

1. 拿到这套 JavaWeb + JSP + MySQL 成绩管理系统,聪明人的第一步不是解压源码

在课程设计资源包或学长共享的网盘链接里,看到「基于 JavaWeb 学生成绩管理系统源码(Jsp + MySQL)含数据库文件(98分项目)」这个标题时,大多数人的第一反应是解压、导入、启动,然后十分钟内被环境问题劝退。这个项目的技术栈其实非常典型:浏览器访问 JSP 页面,JSP 把请求交给 Servlet,Servlet 调用 DAO,DAO 用 JDBC 读写 MySQL。它同时是 JavaWeb 课程设计里出现频率最高的完整案例,覆盖了登录拦截、学生信息维护、成绩录入与查询这几条核心业务线。如果你正在找 javaweb 项目完整案例来应付课程设计,或者想找一份能逐行讲清楚的 Java 课程设计案例源码,这套项目值得你花一个晚上把它跑顺。下文我会按「先看懂骨架 → 再在 IDEA 里跑起来 → 安全导入数据库 → 排掉高频坑 → 最后加一个能拿得出手的功能」的顺序,把整个落地过程讲透。

2. 从 JSP 页面到 MySQL 表:成绩管理系统的分层骨架与查询链路

拿到一个标着「源码 + 笔记」的 JavaWeb 项目,先别急着把文件夹往 IDEA 里拖。打开压缩包后,优先找两样东西:一是 README 或笔记文档,二是工程目录结构。很多课程设计资源都会在文档里写清楚 JDK、Tomcat、MySQL 版本要求,这一行字能帮你避开后面一整晚的版本折腾。如果文档里还附带学生成绩管理系统用例图,那说明原作者至少是认真画过图才去写代码的,这类项目通常分层更清楚,答辩时也更好讲。

确认完版本要求之后,再看目录结构。一个能拿 98 分的 JavaWeb 课程设计,一定不是把所有 Java 代码堆在同一个包下,而是按职责拆成 entity、dao、servlet、util 这些包,页面单独放在 web 或 WebRoot 目录里。下面这个结构是我见过最常见的:

src/ ├─ com/example/entity/ Student.java、Course.java、Score.java ├─ com/example/dao/ StudentDao.java、ScoreDao.java ├─ com/example/servlet/ LoginServlet.java、StudentServlet.java、ScoreServlet.java ├─ com/example/util/ DBUtil.java、EncodingFilter.java web/ ├─ login.jsp ├─ index.jsp ├─ student_list.jsp ├─ score_list.jsp └─ WEB-INF/web.xml

逻辑说明:entity 包对应数据库表,dao 包负责 JDBC 查询,servlet 包负责接收 HTTP 请求并做跳转,util 包里通常放着数据库连接工具和过滤器。JSP 页面只负责展示,不在页面里写大段 Java 代码,这样做的好处是答辩时你能明确说出「页面、控制、数据访问」三层各自干了什么。

参数说明:如果你下载的源码里多了一个 service 包,说明作者在 Servlet 和 DAO 之间又加了一层业务逻辑,这通常是加分项;如果源码把 SQL 直接写在 Servlet 里,项目能跑,但老师追问几句就会露馅。我的习惯是先数一下每个类的代码行数,超过三百行的类基本都有坏味道,改起来要格外小心。

2.1 一个 98 分 JavaWeb 项目的典型文件结构:Model、DAO、Servlet、JSP 谁管谁

先从实体类看起。实体类的作用是给数据库表做对象映射,比如成绩表里有 id、student_id、course_id、score 四个字段,代码里就对应一个 Score 类。常见写法是私有属性加 getter/setter,构造方法一般空参和全参都给,方便后续封装:

public class Score { private Integer id; private Integer studentId; private Integer courseId; private Double score; public Score() {} // getter / setter 省略 }

逻辑说明:这个类本身不干活,只负责装数据。JSP 页面里能直接通过${score.score}读取值,靠的就是 getter 方法。如果你的实体类还写了 toString 方法,调试时能少踩很多坑——打印对象时不会只出现一串内存地址。

接着是 DAO 层。DAO 是直接面对 MySQL 的一层,负责把 SQL 查出来的结果集转换成 Java 对象列表。这里最容易看出一个项目功底深浅的地方是:有没有用 PreparedStatement,是不是把连接、查询、释放资源都处理干净了。一个合格的成绩查询方法长这样:

public List<Score> findScoresByStudentId(int studentId) throws SQLException { String sql = "SELECT id, student_id, course_id, score FROM score WHERE student_id = ? ORDER BY course_id"; List<Score> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, studentId); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Score s = new Score(); s.setId(rs.getInt("id")); s.setScore(rs.getDouble("score")); list.add(s); } } } return list; }

逻辑说明:查询语句里的ORDER BY course_id是给 MySQL 排序留下的位置,对应了「mysql排序」这个高频使用场景。try-with-resources写法保证 Connection、PreparedStatement、ResultSet 在使用后自动关闭,避免连接泄漏。

参数说明:ps.setInt(1, studentId)把外部传入的 studentId 绑定到第一个问号占位符。这里不能用字符串拼接把参数塞进 SQL,一个是防 SQL 注入,另一个是防止成绩、学号里带单引号时直接报语法错误。这个知识点几乎是 JavaWeb 答辩必问。

2.2 成绩查询这条主线:JSP 表单、Servlet 接收、DAO 回查的代码怎么咬合

把成绩查询这条业务线串起来看,整个请求流转是这样的:用户在 JSP 页面输入学号,点击查询,表单把 studentId 提交给 ScoreListServlet,Servlet 调 DAO 查出 List 放到 request 里,最后转发回 score_list.jsp,用 JSTL 标签循环输出。Servlet 端最常见的写法如下:

@WebServlet("/score/list") public class ScoreListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int studentId = Integer.parseInt(req.getParameter("studentId")); ScoreDao dao = new ScoreDao(); try { req.setAttribute("scoreList", dao.findScoresByStudentId(studentId)); } catch (SQLException e) { req.setAttribute("error", "查询成绩失败:" + e.getMessage()); } req.getRequestDispatcher("/score_list.jsp").forward(req, resp); } }

逻辑说明:req.setAttribute把结果放进 request 作用域,forward把请求转发给 JSP。注意这里用的是 forward 不是 sendRedirect,因为我们要把查询结果带给页面展示,重定向会导致数据丢失。

参数说明:@WebServlet("/score/list")是 Servlet 3.0 之后的注解映射,如果源码里用的是老式 web.xml 配置,效果一样但写法不同。查询参数 studentId 来自 JSP 表单的 name 属性,两边名字对不上,页面就会报空指针。

JSP 拿到数据后,用 JSTL 循环渲染:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table border="1"> <tr><th>课程</th><th>成绩</th></tr> <c:forEach items="${scoreList}" var="score"> <tr> <td>${score.courseId}</td> <td>${score.score}</td> </tr> </c:forEach> </table>

逻辑说明:${score.score}是 EL 表达式,等价于调用score.getScore()。如果页面上一行数据都不显示,优先检查 Servlet 里 setAttribute 的 key 是不是 scoreList,JSTL 标签库有没有引入。这一套「JSP 表单 + Servlet 接收 + DAO 回查」就是 JavaWeb 里最基础、最常考的请求链路。

2.3 数据库连接串里的三个必调参数:useSSL、characterEncoding、serverTimezone

数据访问层能不能连上 MySQL,八成取决于 DBUtil 里那串 jdbc 连接地址。很多学生照着网上教程抄了一串 URL,换台电脑就连不上,原因就是没搞清楚每个参数在干什么。我一般会把 DBUtil 写成静态代码块加载驱动:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/score_db" + "?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/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(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

逻辑说明:useSSL=false是关闭 SSL 加密连接,本地开发环境用不上加密,省去证书握手环节;characterEncoding=utf8保证 Java 和 MySQL 之间传输中文不乱码;serverTimezone=Asia/Shanghai是 MySQL 8.x 新版本强制的时区参数,不写会直接报 server timezone 相关异常。

参数说明:如果你的 MySQL 是 5.7 以下版本,serverTimezone可以不加,但加了也不报错。连接串里的localhost:3306要根据实际 MySQL 端口改,很多教程把端口改成 3307 或 3308,照抄本地必挂。这里的用户名密码也是一样,别小看这几行配置,答辩现场连不上数据库的翻车案例我见过太多了。

3. 在 IDEA 里跑通源码:环境版本匹配、Tomcat 部署与首屏验证

把项目骨架读懂之后,再开始动手跑。很多人遇到「idea运行javaweb项目配置」问题,往往不是 IDEA 不会用,而是没把 JDK、Tomcat、MySQL 三者的版本关系对齐。我在第 4 章会详细讲数据库文件,这一章先集中解决「怎么把源码变成一个能打开的网页」。

3.1 先对齐环境:JDK、Tomcat、MySQL 版本不同会带来哪些连锁问题

老式 JSP 项目对环境敏感,版本差一点也不至于完全跑不了,但会有各种让你怀疑人生的报错。下面这张表是我的基准配置,按这个来,兼容性最好:

环境项推荐版本说明
JDK1.8 或 11老项目用 JDK 17 可能遇到反射访问报错
Tomcat8.5 或 9.0Tomcat 10 把 javax.servlet 换成 jakarta.servlet,老代码全挂
MySQL5.7 或 8.08.0 需要驱动类名改 com.mysql.cj.jdbc.Driver
IDEA2020 及以后均可主要影响 Tomcat 集成面板样式,不影响编译

逻辑说明:Tomcat 10 是一次分水岭,凡是在 web.xml 里写javax.servlet的项目,拿到 Tomcat 10 上会报找不到类。课程设计源码基本都是 2018 到 2022 年之间写的,默认就是 javax 包,所以老老实实用 Tomcat 8.5 或 9.0,别追新。

参数说明:如果 MySQL 装的是 5.7.44,按照网上「mysql 5.7.44 安装过程详细」教程走一遍通常能装好,但要注意 Auth 插件默认是 mysql_native_password 还是 caching_sha2_password。这个问题放到第 5 章避坑里细讲,环境这步先把版本记牢。

3.2 导入项目与补依赖:非 Maven 项目把 lib 打进 Artifact 的正确姿势

课程设计源码分两种:Maven 工程和非 Maven 工程。Maven 工程打开 pom.xml 就能自动下载依赖,省事很多;非 Maven 工程只有 WEB-INF/lib 下面几个 jar,很多人导入后 IDEA 报一片红,就是因为 lib 没有进 Artifact。

非 Maven 项目的导入步骤我按顺序拆成四步:

  1. IDEA 选 File → Open,定位到项目根目录,如果弹窗问是否作为 Maven 导入,选 No。
  2. 打开 Project Structure → Modules,把 src 目录标记为 Sources,把 web 目录标记为 Web Resources。
  3. Project Structure → Artifacts → 点加号 → Web Application: Exploded,把 WEB-INF/lib 下的 mysql-connector-java.jar 加进去。
  4. 打开 Run/Debug Configurations → 添加 Tomcat Server → Local,Deployment 页签里添加这个 Artifact,Application context 填/score。

如果源码里带了 pom.xml,那只要确认一个依赖版本:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>

逻辑说明:如果用 MySQL 8.0,把版本号换成8.0.30,驱动类名在 DBUtil 里也要同步改成com.mysql.cj.jdbc.Driver。5.1.49 是 5.x 系列的最后一个稳定版本,兼容 MySQL 5.7 很稳,但连 8.0 时常常被 SSL 和时区问题卡住。

参数说明:Application context 填/score意味着访问地址是http://localhost:8080/score/xxx。如果你想直接通过根路径访问,也可以填/,但改 context 之后 JSP 里所有相对路径都会受影响,所以建议保持一个固定名字。

3.3 第一次启动后的五分钟验证:日志、端口、登录、数据加载

配置完点击启动,浏览器先别急着开。看 IDEA 底部 Tomcat 日志,出现「Artifact ... deployed successfully」和「Server startup in」这两个关键词,至少证明部署没大问题。紧接着在浏览器访问http://localhost:8080/score/,正常会跳到登录页。

进登录页后按下面顺序验证:先用管理员账号登录,看能不能进主页;再打开学生列表,看图片、CSS 是否正常;最后进成绩查询页,输入一个学号,看表格有没有数据。如果数据是空的,不要先怀疑代码,先回第 4 章检查数据库里有没有初始数据。

这里补一个很实用的浏览器习惯:JSP 页面启动后,直接 Ctrl+F5 强制刷新一次,或者打开无痕窗口访问。很多人改完 JSP 再切回浏览器,看到的还是旧页面,就以为是 Tomcat 没热部署,实际上就是本地缓存。jsp 页面让加载完后刷新一次,这个动作应该成为你调试时的本能反应。

4. 数据库文件的正确打开方式:建表脚本、导入顺序与成绩统计 SQL

源码包带数据库文件,是这个项目最大的省心点。但省心不代表可以闭眼导入。我在帮人调试这种成绩管理系统时,最常遇到的就是「导入时报错」和「导入成功但登录不了」两种情况,前者多半是字符集或外键问题,后者多半是账号密码不匹配。这一章说清楚正确打开方式。

4.1 学生、课程、成绩、用户四张核心表的关系与建表脚本

成绩管理系统数据库再简单,至少也得有四张表:sys_user 存登录账号,student 存学生信息,course 存课程,score 存成绩。很多 98 分项目还会加上班级、教师、学期之类的扩展表,但核心业务跑在这四张上。我习惯的建表脚本是这样:

CREATE DATABASE IF NOT EXISTS score_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE score_db; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role TINYINT DEFAULT 1 ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, class_name VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(50) NOT NULL, credit TINYINT DEFAULT 0 ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) NOT NULL, exam_date DATE, UNIQUE KEY uk_student_course (student_id, course_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 );

逻辑说明:score表用UNIQUE KEY uk_student_course保证同一个学生同一门课只有一条成绩,避免重复录入。外键加ON DELETE CASCADE的意思是删掉一个学生,他的成绩记录也一起删,不然删学生时会被外键拦下来。

参数说明:DECIMAL(5,2)表示成绩最多三位整数加两位小数,能存 999.99,足够课程成绩用。DATETIME DEFAULT CURRENT_TIMESTAMP是插入时自动生成当前时间,这个字段在答辩时可以用来解释「新增时间是从哪来的」。

4.2 让 SQL 文件在不同 MySQL 版本下一键导入:字符集、外键与导入顺序

拿到源码里的数据库文件,最常见的导入方式是命令行 source 命令,也可以直接用 Navicat 打开运行。命令行方式最稳妥:

mysql -uroot -p --default-character-set=utf8mb4 score_db < score_db.sql

逻辑说明:--default-character-set=utf8mb4可以让脚本里的中文注释和中文初始数据正确落库。如果漏掉这个参数,导入时看着没报错,打开表一瞧,全部都是问号,后面 JSP 页面显示出来的也是乱码,这种问题排查起来特别隐蔽。

参数说明:如果 SQL 文件里的建表语句本身没带CREATE DATABASE,你要先手动执行CREATE DATABASE score_db,然后导入表。顺序错了会报「No database selected」;如果表之间有外键依赖,先导父表再导子表,不然外键引用不存在的表也会失败。还有一点,部分老 SQL 文件开头会写SET FOREIGN_KEY_CHECKS=0,意思是先关掉外键检查,导完再开回来,这种脚本兼容性更好。

数据库导入成功不等于能登录。拿着源码包里的 SQL 文件导入后,登录页面一直报用户名密码错误,优先怀疑源码 db.properties 里配置的账号密码和你导入的数据不一致。常见做法是看 SQL 里的INSERT INTO sys_user语句,确认里面初始化的管理员账号密码,再用它去登录。

4.3 把「查成绩」升级成「算成绩」:三条常用的分组统计 SQL

如果只在页面上把成绩一条条列出来,答辩时没什么亮点。把原始成绩聚合成统计结果,才更容易拿高分。成绩统计最常见的就是按课程算平均分、最高分、最低分,SQL 这样写:

SELECT c.course_name, COUNT(s.student_id) AS exam_cnt, ROUND(AVG(s.score), 2) AS avg_score, MAX(s.score) AS max_score, MIN(s.score) AS min_score FROM score s JOIN course c ON s.course_id = c.id GROUP BY c.id, c.course_name ORDER BY avg_score DESC;

逻辑说明:GROUP BY c.id, c.course_name是按课程分组,AVG、MAX、MIN分别算平均、最高、最低分。最后的ORDER BY avg_score DESC正好对应你搜过的 mysql 排序——按平均分从高到低展示,一眼看出哪门课最难。

参数说明:ROUND(AVG(s.score), 2)是为了把平均分保留两位小数,否则 MySQL 会返回很长的浮点数。如果你要的是班级总分排名,可以改成按 student_id 分组算总分:

SELECT student_id, SUM(score) AS total_score FROM score GROUP BY student_id ORDER BY total_score DESC;

这条 SQL 可以直接让学生列表页多出一列「总分」,或者顺手做一个成绩排行页面。把这两条 SQL 放进 DAO 里,等于在原项目上加了两个真实功能,答辩时讲起来会很加分。

5. 避坑指南:从「能运行」到「演示不翻车」的常见问题排查

这套项目跑通并不难,难的是演示时不出岔子。我把在学生成绩管理系统里翻车频率最高的五个问题列出来,每条都按「现象 → 原因 → 解决」给你拆开。这些问题看着像玄学,实际上每一类都有确定的手法和固定的解法。

5.1 JSP 中文乱码:从页面到数据库的三处编码不一致

现象:页面上的中文变成「???」或「温度」这类乱码,数据库表里存进去的也是乱码。

原因:编码问题至少有三个坑位。第一,JSP 文件本身的保存编码不是 UTF-8;第二,Tomcat 接收请求参数时没有按 UTF-8 解码;第三,MySQL 连接串没加characterEncoding=utf8,或者表默认字符集是 latin1。一处漏配,中间乱一处。

解决:先把 JSP 页面头部写清楚:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

然后在 DBUtil 连接串里补上characterEncoding=utf8,最后用第 4 章的脚本重建数据库,确认表和字段都是 utf8mb4。这样三层统一后,中文乱码基本绝迹。需要注意的是,如果你已经导入了乱码数据,只改编码不会让旧数据自动变好,必须清空表重新导入。

5.2 Tomcat 端口被占用与 Servlet 404

现象:Tomcat 启动时立刻报Port 8080 was already in use,或者项目启动成功但访问/score/score/list返回 404。

原因:端口被占多半是之前残留的 Java 进程还活着,或者本机装了其他 Web 服务。404 则分成两类:一类是地址漏了 contextPath,你把/score删了直接访问/score/list;另一类是 web.xml 里 Servlet 映射写错,注解没生效,请求落了空。

解决:先查端口占用,再开杀进程。命令行工具最直接:

# Windows netstat -ano | findstr :8080 taskkill /PID <查到的PID> /F # Linux / macOS lsof -i:8080 kill -9 <查到的PID>

杀掉进程后重启 Tomcat。如果端口被其他重要服务占着,就改 Tomcat 端口,在conf/server.xml里把 8080 改成 8081。404 的话,先确认访问地址是不是http://localhost:8080/score/score/list,再检查 web.xml 里<url-pattern>的值和@WebServlet注解是否一致;如果两种配置都写了,优先删掉 web.xml 里旧的映射,避免双配置冲突。

5.3 MySQL 8.0 驱动、SSL 与时区三连坑

现象:启动登录功能时抛ClassNotFoundException: com.mysql.jdbc.Driver,或者报Communications link failure,又或者是The server time zone value 'CST' is unrecognized。

原因:MySQL Connector/J 从 8.0 开始,驱动类改名了,旧名字com.mysql.jdbc.Driver被删掉。同时 8.0 默认开启 SSL 连接,如果你的 MySQL 没有正确配置 SSL 证书,就会卡在握手阶段;时区参数不写也会直接拒连。这三个问题经常一起出现,报错信息看起来各不相同,根源全在连接串。

解决:把 db.properties 或 DBUtil 里的驱动类和连接地址统一改掉:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/score_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

逻辑说明:useSSL=false关闭 SSL 握手,serverTimezone=Asia/Shanghai指定时区,driver换成新名字。如果你遇到的是 MySQL 8.0 的认证插件问题,登录时报Public Key Retrieval is not allowed,可以在连接串后面追加allowPublicKeyRetrieval=true。还有一个兜底方案,在 MySQL 里把账号认证方式改回兼容模式:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';

这个 mysql ssl 连接错误和时区问题一旦解决,项目在你本地就能稳住大半。

5.4 JSP 里图片和 CSS 定位不到:相对路径与坐标定位

现象:登录页能打开,但图片显示红叉,CSS 样式全掉;换了一个访问路径之后,本来好的页面反而坏了。

原因:JSP 页面里的<img src="images/logo.png">是相对路径,它会跟着浏览器地址栏的层级变化。你在http://localhost:8080/score/访问是好的,跳转到http://localhost:8080/score/student/list后再刷新,浏览器会把相对路径解析成/score/student/images/logo.png,自然 404。

解决:统一用绝对路径拼资源地址,最常见做法是在 JSP 顶部定义一个ctx变量:

<c:set var="ctx" value="${pageContext.request.contextPath}"/> <link rel="stylesheet" href="${ctx}/css/style.css"> <img src="${ctx}/images/avatar.png" style="position:absolute;left:60px;top:20px;width:80px;height:80px;">

逻辑说明:${pageContext.request.contextPath}会输出/score,不管浏览器地址怎么变,资源地址永远从项目根路径开始找,不会再迷路。关于 jsp 图片如何对坐标定位,上面这段就是完整答案:先把图片从资源文件夹里正确加载出来,再用 CSS 的position:absolute加 left、top 控制摆放位置。如果 CSS 里定了宽高,图片不至于变形。

5.5 刷新页面导致成绩重复提交:重定向与事务

现象:添加成绩后浏览器刷新一次,页面提示重复提交,数据库里多了一模一样的成绩记录。

原因:表单提交走的是 POST 请求,Servlet 拿到数据后如果直接forward回 JSP,浏览器地址栏还停在上一次提交的 URL 上,一按 F5 就把同一个 POST 又发了一遍。

解决:采用 Post/Redirect/Get 模式,业务处理完不要 forward,而是重定向到查询页面:

resp.sendRedirect(req.getContextPath() + "/score/list?studentId=" + studentId);

逻辑说明:sendRedirect让浏览器重新发起一次 GET 请求,这时候刷新页面只会刷新查询结果,不会重复提交表单。如果你的成绩录入涉及多科目批量插入,还要把这几个 SQL 包在同一个事务里,这就是你搜过的 mysql 事务处理场景。用Connection.setAutoCommit(false),全部插入成功后 commit,中途失败就 rollback,保证成绩要么一起入库,要么全都不入:

conn.setAutoCommit(false); try { // 多条 insert 操作 conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; }

6. 把 98 分的课程设计做成答辩亮点:三个低风险加戏技巧与自检清单

6.1 用过滤器统一处理登录校验和字符编码

课程设计里的 StringFormatFilter 如果用得干净,答辩加 1-2 分基本稳了。把所有 Servlet 里重复的编码设置抽到一个过滤器里,既能减少代码量,又能体现你对「横切逻辑」的理解:

@WebFilter("/*") public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); String uri = request.getRequestURI(); if (uri.endsWith("/login.jsp") || uri.endsWith("/login") || uri.endsWith(".css") || uri.endsWith(".js")) { chain.doFilter(request, response); return; } Object user = request.getSession().getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); } else { chain.doFilter(request, response); } } }

逻辑说明:这个过滤器做了两件事:一是统一设置请求和响应编码,二是在访问业务页面时检查 session 里有没有登录用户数据;没登录就踢回登录页,静态资源和登录页本身不过滤。原来每个 Servlet 都写一遍的编码代码,现在全都去掉。

6.2 给成绩加一个可视化统计页:JSON 输出与轻量前端图表

只要保证数据正确,往表格页里加图表是低风险加戏,最好的方式是不引入重量级后端报表框架,而是让 Servlet 输出 JSON,前端用 ECharts 画图。Servlet 里写最简单的 JSON 输出:

@WebServlet("/score/chartData") public class ChartDataServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); resp.getWriter().write( "{\"labels\":[\"语文\",\"数学\",\"英语\"],\"values\":[88,92,79]}" ); } }

对应页面里引入 ECharts 后:

fetch('<%=request.getContextPath()%>/score/chartData') .then(res => res.json()) .then(data => { const chart = echarts.init(document.getElementById('chart')); chart.setOption({ xAxis: { type: 'category', data: data.labels }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: data.values }] }); });

逻辑说明:这段代码把成绩统计从「查表格」升级成「看图表」,而数据源还是我们第 4.3 节写好的统计 SQL。如果不想引入 ECharts,也可以直接用 HTML 的进度条样式做一个简易可视化,改造风险更低。

6.3 答辩前 10 分钟的自检清单和我的使用习惯

正式演示前,我习惯按这个清单过一遍:

检查项判定标准
Tomcat 重启后能否访问关闭再启动,确认数据不是存在内存里的临时数据
MySQL 服务是否已启动打开服务管理器,确认 mysql 服务在运行
登录账号密码是否正确用 SQL 里的初始账号试一次
页面图片和样式是否完整无痕模式打开首页,确认没有红叉
数据库连接串是否本机可用核对用户名、密码、端口是否和本机 MySQL 一致
演示账号有无成绩数据查询页面输入学号能出成绩

最近一次帮别人调这种成绩管理系统,我学到的最深教训是:数据库文件一定要在答辩前用「全新克隆的环境」验证一遍,而不是只在你自己电脑上验证完就结束。不同 MySQL 端口、不同 Tomcat 版本、不同 JDK 都会让项目状态翻天覆地。

这也是我现在的固定习惯:完成一个 JavaWeb 课程设计后,先在本地完整跑通,再打开一份笔记把环境和步骤录下来,甚至录一段运行录屏,答辩前只看笔记和录屏就能快速回神。希望这些经验能帮你把这份 98 分的成绩管理系统真正吃透,变成你自己的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询