简介:基于JavaWeb的在线考试系统毕业设计说明文档,系统性地呈现了从需求分析到系统测试的完整开发流程。文档以B/S架构为主线,选择JSP+MySQL+JDBC作为技术方案,详细介绍了系统开发背景与意义、开发工具、可行性分析、数据库设计、三层结构设计、界面设计,以及登录注册、在线考试、试卷设置、试题管理、成绩统计与查询等核心模块的实现思路。考生、教师、管理员三类角色的功能边界与操作流程也都有清晰说明,并配有软件测试内容,便于对照验证。这份毕业设计说明为单个docx文档,共1.25MB,包含中英文摘要、目录和约44页正文,章节层次分明,适合计算机相关专业毕业生、课程设计学生以及JavaWeb开发者作为论文撰写、系统开发或答辩准备的参考资料。目前已有396人学习浏览,资源内容紧凑实用,可帮助快速理解在线考试系统的设计与实现要点,节省自研摸索时间。
1. 在线考试系统设计与实现这件事,先想清楚三个问题
如果你打开那份标题为《基于JavaWeb的在线考试系统的设计与实现毕业设计说明》的文档,看到的通常不是代码堆砌,而是从选题背景一路写到测试部署的完整链路。这类系统几乎每年都会出现在毕业设计选题清单上,不是因为选题陈旧,而是它恰好覆盖了 JavaWeb 最核心的几个环节:外层的 HTTP 请求处理、中层的业务状态管理、底层的数据库事务与一致性。把这个流程走通一遍,Servlet、JSP、JDBC、Filter、Session、MySQL 这些技术就不再是孤立的概念。
做在线考试系统之前,先回答三个问题:谁来用、考什么、怎么判分。教师要能维护题库和阅卷,学生要能在线答题且提交后立刻知道分数,管理员要能处理科目和用户数据。判分这件事最容易被低估——单选题、多选题、判断题三种题型的得分规则完全不同,多选题漏选是否给分又是一种业务决策。这些规则一旦在纸上定死,后面建表写代码都能顺理成章。本文就按照这个思路,把设计文档变成可运行的工程,并说明每步选择背后的理由,适合准备答辩、正在做 JavaWeb 课程设计或想完整过一遍工程流程的读者。
2. JavaWeb双路径选型:Servlet + JSP 与 Spring Boot 的取舍与工程骨架
技术选型是本设计最先落地的决策点。大多数毕业设计文档用的是经典三层架构:Web 层用 Servlet 接收浏览器请求,业务层写 Java 类处理规则,数据访问层用 JDBC 操作 MySQL。也有不少新版设计文档选择了 Spring Boot + MyBatis,理由是企业开发已是主流、起步快、内置 Tomcat。这两条路都成立,差别在于你要演示的重点是"手工搭建的完整 HTTP 处理链路"还是"贴近真实项目的工程化能力"。
2.1.1 为什么经典 Servlet 依然适合作为设计与实现的主线
如果说明文档中的系统需求分析写得很详细,比如有 processon 导出的用例图、E-R 图、数据流图,那 Servlet + JSP 的直白结构反而更好配合这些图去讲。Servlet 的生命周期、请求转发与重定向、Session 的创建与失效,这些是 JavaWeb 教学案例里的高频考点,答辩时评委大概率会围绕它们提问。Spring Boot 把容器细节都封装了,反而少了很多可以展开讲的内容。
工程骨架按 Maven 标准目录组织即可:
exam-system/ ├── pom.xml ├── src/main/java/com/example/exam/ │ ├── controller/ # Servlet 类 │ ├── service/ # 业务逻辑接口与实现 │ ├── dao/ # JDBC 数据访问 │ ├── entity/ # 实体类 │ └── filter/ # 登录与编码过滤器 └── src/main/webapp/ ├── jsp/ # 页面文件 └── WEB-INF/web.xmlpom.xml 里只要引入 servlet-api(provided 作用域)、mysql-connector-java、jstl 这三个依赖,不需要引入任何框架。对这个体量的系统,手写 JDBC 代码量完全可控,每张表的增删改查加在一起不过几百行,而且每一步都能在调试器里看到连接和预编译语句的真实行为。
2.1.2 数据源与全局配置的初始化方式
数据库连接不要在每个 DAO 方法里重复写 DriverManager.getConnection,那会让连接资源失控。标准做法是写一个基于单例的数据库工具类,用静态代码块完成驱动加载和连接池初始化。连接池不引入第三方库的话,可以用 Apache DBCP2 或 HikariCP 单独作为一个依赖,也可以退一步用 ThreadLocal 保存同一个事务内的连接引用。
一个常见实现是定义 db.properties 配置文件:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/exam_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=********注意 url 里的 characterEncoding=utf8 和 serverTimezone=Asia/Shanghai 两个参数。前者保证中文字符不乱码,后者避免 MySQL 8.x 默认时区引发的时间类型报错。用 IDEA 创建 JavaWeb 项目时,官方新版本向导虽然变了,但传统做法还是建 Maven Web 项目之后手动补一个 web.xml,或者直接用骨架 org.apache.maven.archetypes:maven-archetype-webapp 生成。
工具类中读取配置的代码是每个 DAO 使用的基础设施,下面这个封装值得直接抄进工程:
public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(in); Class.forName(props.getProperty("jdbc.driver")); url = props.getProperty("jdbc.url"); username = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); } catch (Exception e) { throw new ExceptionInInitializerError("数据库配置初始化失败"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps != null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} } } }这段代码的关键在于把配置文件的加载固定在静态代码块里,类加载失败就直接抛出初始化异常,应用启动阶段暴露问题而非运行到第一个请求才报错。close 方法单独封装是为了避免 finally 块里写三遍空判断。日常开发中很多人会在这个封装上偷懒,在 DAO 里到处写连接获取和关闭,遇到数据库连接泄漏时排查起来相当痛苦。
参数说明:Connection、PreparedStatement、ResultSet 三者关闭有顺序要求,先 ResultSet 再 Statement 最后 Connection,封装成 close(conn, ps, rs) 后调用方只需在 finally 块里传一次参数,避免遗漏。连接池场景下 conn.close() 实际上是归还连接,不要因为名字叫 close 就以为连接被销毁。
2.1.3 web.xml 中的核心注册与编码过滤器
JavaWeb 项目的请求入口都需要在 web.xml 里注册。Servlet 3.0 之后虽然可以用 @WebServlet 注解替代,但毕业设计说明文档通常要求有一段对部署描述符的讲解,保留 web.xml 反而更好交代请求映射、过滤器链和欢迎页的关系。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>Online Exam System</display-name> <filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.example.exam.filter.EncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app>EncodingFilter 必须映射为 /* 才能拦截所有请求,包括直接访问 JSP 的请求。如果不写这个过滤器,表单提交的中文会以页面编码提交,与数据库连接串里的 characterEncoding 不一致,查出来的数据就变成问号。课程设计里遇到中文乱码,十次有八次是这一步没做。
3. 在线考试系统的数据库设计与 DAO 层落地
在线考试系统的表结构是整份说明文档里最好画图的部分,也是最容易设计过度的地方。五张核心表足够覆盖所有业务场景:用户表、科目表、题目表、试卷表(含答卷)、答题明细表。不需要设计复杂的角色权限中间表,用户表上加一个 role 字段区分管理员、教师、学生即可。这对一个毕业设计来说职责清晰,也方便在答辩时说清楚权限控制的粒度。
3.1.1 核心表结构与关键字段设计
建表时最重要的决策在于:题目、试卷、答卷三者的关系如何表达。一个常见且稳妥的方案是:题目表存所有试题并带 subject_id 外键;考试表(exam)记录一次考试的基本信息,包括科目、考试时长、总分;试卷表(exam_paper)关联考试与题目,用来固定这次考试选了哪些题;答卷表记录学生提交的答案。
CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(64) NOT NULL, `real_name` VARCHAR(50) NOT NULL, `role` TINYINT NOT NULL DEFAULT 2 COMMENT '1管理员 2教师 3学生', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `subject` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `question` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `subject_id` INT NOT NULL, `type` TINYINT NOT NULL COMMENT '1单选 2多选 3判断', `content` TEXT NOT NULL, `option_a` VARCHAR(255), `option_b` VARCHAR(255), `option_c` VARCHAR(255), `option_d` VARCHAR(255), `answer` VARCHAR(255) NOT NULL, `score` INT NOT NULL DEFAULT 5 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `exam` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `title` VARCHAR(100) NOT NULL, `subject_id` INT NOT NULL, `duration_minutes` INT NOT NULL DEFAULT 60, `total_score` INT NOT NULL DEFAULT 100, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0未发布 1已发布 2已结束' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `exam_record` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `exam_id` INT NOT NULL, `student_id` INT NOT NULL, `score` INT DEFAULT NULL, `submit_time` DATETIME DEFAULT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0考试中 1已交卷' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;question 表中的答案字段用 VARCHAR 而不是固定一个字符串,是为了兼容多选题。多选题答案存成 "A,B,D" 这样的逗号拼接格式,判分时拆分后比较集合即可。option 字段允许为空是为判断题准备的,判断题只用 content 和 answer,选项留空。exam 表里的 status 字段设计成 0、1、2 三态,而不是单纯用布尔值,方便后续扩展暂停考试等状态。
考试记录表是关键中的关键。它必须在学生开始考试时插入一条记录,而不是在交卷时才生成。这样可以记录谁开始过考试但没交卷、谁答到一半断线,status字段能在崩溃场景下区分"考试中"和"已交卷",为管理员提供恢复依据。
3.1.2 DAO 层事务处理方式与判分的数据流转
判分是唯一需要数据库事务支撑的场景,因为交卷时要做三件事:批改答题明细、更新考试记录总分、把答题明细落库。三件事必须同时成功或同时失败,否则会出现"记录显示已交卷但没有成绩"的脏数据。
手动 JDBC 的写法是在 Service 层拿连接、关闭自动提交、继续使用这同一个连接执行多张表的更新,最后统一 commit。这个模式比用 JdbcTemplate 更能讲清事务边界:
public void submitExam(int examId, int studentId, Map<Integer, String> answers) throws SQLException { Connection conn = DBUtil.getConnection(); conn.setAutoCommit(false); PreparedStatement ps = null; try { int totalScore = 0; // 1. 逐题批改,同时写入答题明细 for (Map.Entry<Integer, String> entry : answers.entrySet()) { Question q = questionDao.findById(conn, entry.getKey()); boolean correct = checkAnswer(q, entry.getValue()); int gotScore = correct ? q.getScore() : 0; totalScore += gotScore; examRecordDao.insertDetail(conn, examId, studentId, q.getId(), entry.getValue(), gotScore); } // 2. 更新考试记录状态与总分 examRecordDao.updateScoreAndStatus(conn, examId, studentId, totalScore); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { DBUtil.close(conn, ps, null); } }这个方法的逻辑说明:外层 Map 的 key 是题目 ID,value 是学生提交的答案。因为批改和插明细用的是同一个连接对象,所以 checkAnswer 查题目和两个写入方法之间严格共享这一个事务。注意 DAO 方法签名上都带 Connection 参数,这是手动事务管理的关键——连接由 Service 层创建和释放,DAO 只负责执行 SQL,事务边界由此清晰可见。
参数含义说明:conn.setAutoCommit(false)是事务开始的标志,conn.commit()提交所有变更,conn.rollback()回滚到事务开始前的状态。这里有一个容易写错的细节:不要在 try 块里关闭连接,放在 finally 里,且要保证 rollback 时连接仍然存活。
3.1.3 checkAnswer 的多选与判断题处理
判分规则不是简单的字符串相等比较,必须区分题型。单选题和判断题直接比较答案字符串,多选题则要比较集合。这里有一个业务决策:多选题漏选算不算分?不同的设计文档策略不同,常见做法是全对才得分,这样实现最简单、答辩也好解释。
private boolean checkAnswer(Question q, String studentAnswer) { if (studentAnswer == null || studentAnswer.trim().isEmpty()) { return false; } String normalizedStudent = normalize(studentAnswer); if (q.getType() == 2) { String correct = normalize(q.getAnswer()); String[] studentArr = normalizedStudent.split(","); String[] correctArr = correct.split(","); Set<String> sSet = new HashSet<>(Arrays.asList(studentArr)); Set<String> cSet = new HashSet<>(Arrays.asList(correctArr)); return sSet.equals(cSet); } return normalizedStudent.equalsIgnoreCase(normalize(q.getAnswer())); }normalize 方法统一去空格并转大写,避免半角全角逗号混用导致误判。多选题判分最怕学生提交的选项顺序不同——"A,B,D" 和 "B,D,A" 是同一组答案,但字符串直比会判错,所以必须转成 Set 后再比较。
4. 在线答题主流程与页面交互的关键实现细节
整个系统的核心体验是答题页。学生点开考试、计时开始、逐题作答、提交试卷。这一个流程牵涉数据加载、倒计时刷新、交卷确认三层逻辑,任何一个环节的缺陷都会在演示时被评委注意到。
4.1.1 题目加载方式与页面渲染策略
常见做法有两种:一次性把整套题的 JSP 页面输出到浏览器,或者按题号动态加载。毕业设计选前者,理由很实际——代码简单、面试好讲、流量可控。
在 Servlet 中查出题目列表后放入 request 域再 forward 到 exam.jsp,页面通过 JSTL 的 c:forEach 循环渲染。这个方案下代码量最小:
@WebServlet("/student/exam") public class StudentExamServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(false); if (session.getAttribute("student") == null) { resp.sendRedirect("login.jsp"); return; } int examId = Integer.parseInt(req.getParameter("examId")); List<Question> questions = questionDao.findByExamId(examId); req.setAttribute("questions", questions); req.setAttribute("examId", examId); req.getRequestDispatcher("/jsp/exam.jsp").forward(req, resp); } }单次查询查出所有题目交给 JSP 渲染,没有懒加载,也不需要 AJAX 分页。题目数量控制在 50 题以内时性能完全没问题。这里需要注意的是 forward 而非 sendRedirect,因为 request 域中的数据只有在 forward 情况下才能传递到 JSP。
4.1.2 计时与自动交卷的核心写法
答题页倒计时是最容易出 bug 的地方。只在前端用 JavaScript 倒计时是不够的,页面一刷新计时就重新开始,学生可以利用这个漏洞无限延长考试时间。正确处理方式是把结束时间存进 Session,用 JS 每秒读取剩余秒数,提示时间到后自动提交表单。
let remainSeconds = ${sessionScope.examEndTime - System.currentTimeMillis() / 1000}; function countDown() { if (remainSeconds <= 0) { document.getElementById('examForm').submit(); return; } let min = Math.floor(remainSeconds / 60); let sec = remainSeconds % 60; document.getElementById('timer').innerText = '剩余时间 ' + min + ' 分 ' + sec + ' 秒'; remainSeconds--; setTimeout(countDown, 1000); } window.onload = countDown;这里的核心思路是:开始考试时后端计算examEndTime = System.currentTimeMillis() + duration * 60000,存入 Session。前端只是把它显示出来。即使学生刷新页面,Session 里的截止时间不会重置,刷新只会让 JS 重新获取同一个值继续倒数。后端在提交时还必须再校验一遍时间,防止学生改浏览器本地时间或直接构造请求绕过前端限制。
4.1.3 防止重复提交的令牌机制
在线考试最容易收到的异常反馈是"我明明没点交卷,怎么成绩就出来了"。常见诱因是学生双击提交按钮,或者网络抖动导致重发请求。防止重复提交的 URL 层手段是重定向到结果页,但更稳妥的做法是加一次性令牌。
public class TokenFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if ("POST".equalsIgnoreCase(req.getMethod()) && req.getRequestURI().contains("/submitExam")) { HttpSession session = req.getSession(); String sessionToken = (String) session.getAttribute("submitToken"); String requestToken = req.getParameter("token"); if (sessionToken == null || !sessionToken.equals(requestToken)) { ((HttpServletResponse) response).sendRedirect("error.jsp"); return; } session.removeAttribute("submitToken"); } chain.doFilter(request, response); } }页面生成时在 JSP 里放一个随机 token 到隐藏字段,同时放入 Session。交卷提交时过滤器比较两者,只允许第一次请求通过。注意提交成功后必须立刻从 Session 中移除,这样第二次点击就无法通过校验。把这段逻辑写在 Filter 里而不是写在具体的 Servlet 中,能让题库维护、用户管理等同样需要防重复的接口直接复用。
5. Session 管理、异常处理与跨浏览器支持的三处排错
在线考试系统里有两个高频扣分点:考试中途 Session 过期导致提交失败,以及学生用不兼容浏览器访问页面导致页面错乱或倒计时失效。这两类问题在设计说明中都属于"运行时质量"章节,但往往被忽略,直到答辩演示时才暴露。
5.1.1 Session 超时策略与有效期刷新机制
Tomcat 默认 Session 超时时间为 30 分钟,但一次考试可能设置 60 分钟或更长。如果考试时长超过 Session 过期时间,学生答到一半 Session 失效,提交时直接被踢回登录页。解决方式是在 web.xml 中显式调大 Session 超时时间:
<session-config> <session-timeout>120</session-timeout> </session-config>session-timeout 单位是分钟。设置 120 分钟在多数考试场景下够用,也可以在设计文档里说明这是按最长考试时长加 30 分钟冗余计算的。注意这是一个全局配置,会影响登录状态的有效期,所以需要判断是否会与安全要求冲突。如果担心 Session 固定攻击,可以在登录成功后调用request.changeSessionId()重新生成会话 ID。
更优雅的方案是滑动过期:在过滤器里检查请求是否来自已登录用户,是则调用session.setMaxInactiveInterval(120 * 60)顺延有效期。但这个方案只适合有有效交互的场景,学生开着页面不动是不算活跃的,所以滑动过期对考试系统来说意义不大,直接调大全局超时时间即可。
5.1.2 统一异常处理与日志记录
Servlet 3.0 之后可以在 web.xml 声明错误页:
<error-page> <error-code>500</error-code> <location>/jsp/error.jsp</location> </error-page> <error-page> <exception-type>java.lang.Exception</exception-type> <location>/jsp/error.jsp</location> </error-page>配置后任何 Servlet 抛出未捕获异常,用户都会看到统一的错误页面,而不是 Tomcat 的默认堆栈信息页。这个细节对于答辩演示非常重要,因为一旦演示时蹦出异常堆栈,观感很差。与此同时,堆栈不能只给用户看,还要落到日志文件里,通常做法是结合 log4j2 或直接在 catch 块中调用e.printStackTrace()并配合 IDEA 控制台排查。正经项目建议用 log4j2 的异步 Logger,但课程设计里写清楚printStackTrace的局限性和日志框架的替代方案即可。
5.1.3 跨浏览器兼容的三个常见坑
在线答题页面依赖 JavaScript 倒计时和表单提交,以下三个点在 Chrome 以外环境最容易出问题:
第一,Date 对象解析的差异。倒计时代码里parseInt((new Date().getTime() - startTime) / 1000)这种写法没有兼容性问题,但如果使用了new Date("2025-01-01 10:00:00")这种带横杠的字符串,IE 会解析失败返回 NaN。统一使用时间戳传递。
第二,表单的自动完成行为。Chrome 会在刷新后自动恢复之前填过的 input 内容,这在考试场景可能导致学生看到自己之前做的答案。在 form 标签上增加autocomplete="off"即可禁用,但对 MooTools 等老框架渲染出的内容可能需要额外处理。
第三,浏览器对 JSP 中\n在 textarea 里的回显差异。学生提交的文字型答案如果包含换行,应统一转换后再存入 MySQL。\r\n与\n的混用在 Windows 与 Linux 环境中表现不一,简单方案是入库前把\r\n替换成\n,读取时前端用 CSSwhite-space: pre-wrap保持换行。
6. 部署与答辩演示前的三个验收技巧
开发完成不是终点,真正的挑战是把整套工程在干净机器上从零跑通,并且让验收环境稳定复现。这个环节最常见的翻车点是数据库版本差异和本地文件路径问题,下面给出三个能直接照做的技巧。
6.1.1 一键初始化数据库的脚本设计
把建库、建表、插入种子数据三个步骤合并进一个 init.sql 脚本,不要手动在 Navicat 里点鼠标执行。脚本头部加上建库和 USE 语句:
CREATE DATABASE IF NOT EXISTS exam_db DEFAULT CHARACTER SET utf8mb4; USE exam_db; SOURCE /absolute/path/to/schema.sql;SOURCE 命令在 MySQL 8.x 客户端下可以正常工作,但路径必须是客户端机器的绝对路径。如果演示用的电脑没有配置环境变量,应用启动时优先检查数据库连接串是否能通,用 IDEA 自带的 Database 面板测试连接,不要等到浏览器里查不到数据才去排查。
6.1.2 演示数据的质量控制
种子数据比其他任何东西都影响答辩观感。用户表里准备一个管理员账号、一个教师账号、两个学生账号。题库里为每个科目准备至少 20 道题,覆盖单选、多选、判断三种题型。其中故意留一道多选题的正确答案是 "A,C,D",用来演示漏选不得分的规则。
最重要的技巧是:提前用学生账号完完整整考一遍试,把成绩和答题记录留在库里。演示时优先展示"已有成绩的过往记录",然后再现场发起一场新考试。这样即使现场网络出了状况,也不会因为没有数据可讲而冷场。
6.1.3 验证自动判分结果的 SQL 检查方法
演示完一场考试后,用一行 SQL 核对总分计算是否正确:
SELECT er.id, er.student_id, er.score, SUM(rd.got_score) AS detail_sum FROM exam_record er JOIN record_detail rd ON er.id = rd.record_id WHERE er.id = 1 GROUP BY er.id, er.student_id, er.score HAVING detail_sum <> er.score;这条 SQL 找出总分字段与明细累加值不一致的记录,专门用来验证事务提交的完整性。如果查询结果为空,说明批改、累分、更新三步都处于一致状态;如果查出数据,则立即定位到事务回滚失效或某道题的 score 字段赋值异常。
部署完成后在浏览器地址栏直接访问http://localhost:8080/exam-system/login.jsp,用种子数据里的学生账号走一遍完整的答题、交卷、查看成绩的流程,再回到 MySQL 里执行上面这条 SQL。
本文还有配套的精品资源,点击获取