Java实时评分系统:基于SSE与事务线程池的毕设实战
2026/9/20 9:35:02 网站建设 项目流程

简介:本资源是一套基于Java开发的实时评分系统完整毕设项目,面向计算机专业本科生及Java初学者,旨在解决传统赛事人工打分效率低、计算易出错、结果不透明等痛点。系统支持评委扫码实时打分,后台自动汇总计分并同步大屏展示,适用于校园技能大赛、才艺展演、教学实训等多场景评分需求。压缩包共61个文件,含48个Java核心业务类(涵盖Controller、Service、Mapper层)、6个MyBatis映射XML、4张系统界面截图(jpg)、1个application.yml配置文件、1个项目说明文档(md)及1个建库建表SQL脚本(sql),整体仅138KB,轻量易部署。目前已有354人学习下载,提供开箱即用的SpringBoot+MyBatis+Redis+MySQL技术栈实践案例,包含完整数据库结构、清晰分层代码结构与详细使用指引,是理解实时交互、前后端协同及毕业设计落地的优质参考范例。

1. 这不是普通毕设:一个能真实跑起来的Java实时评分系统,核心在“实时”二字

很多计算机专业学生拿到“实时评分系统”毕设题目时,第一反应是写个带增删改查的Java Web页面,后台连个MySQL,前端点一下按钮就刷新分数——这根本不是实时。真正的实时评分,意味着当教师在监考端录入成绩、学生在答题端提交答案、或系统自动判卷完成的瞬间,所有相关角色(教师、学生、管理员)的界面必须在1秒内同步更新分数、排名、状态标识,且不能靠F5刷新。本项目源码包里包含的不是静态CRUD模板,而是一套基于Java多线程+数据库事务+轻量级推送机制构建的闭环流程:从SQL Server中读取题库与考生信息,用Java线程池处理并发判卷请求,通过JDBC批处理写入成绩,再用HTTP长轮询或Server-Sent Events(SSE)向浏览器推送变更。它不依赖WebSocket服务器或消息中间件,却能在单机Tomcat环境下稳定支撑50人并发评分。适合需要展示“响应快、逻辑清、可调试”的毕设答辩场景,尤其对Java基础、SQL事务控制、前后端数据一致性有硬性考察需求。


2. 搭建环境:从Java JDK到SQL Server 2008 R2,避开安装报错和驱动兼容陷阱

2.1 Java环境配置:必须用JDK 8u202或更高版本,禁用JDK 17+

该项目源码中大量使用java.util.DateSimpleDateFormat及Servlet 3.1规范,若强行使用JDK 17+会导致编译失败(如javax.servlet.http.HttpServletRequest类找不到)。常见错误是学生下载最新JDK 21后直接配置环境变量,结果javac能运行但Tomcat启动报UnsupportedClassVersionError。正确做法是:

# 下载JDK 8u202(官方归档版,非LTS后续版本) # 配置JAVA_HOME指向解压目录,例如: export JAVA_HOME=/opt/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH # 验证 java -version # 输出应为 java version "1.8.0_202"

提示:Windows用户需在“系统属性→高级→环境变量”中设置JAVA_HOME,并确保%JAVA_HOME%\binPath最前面;若已装多个JDK,务必用where java确认调用路径。

2.2 SQL Server 2008 R2安装与关键服务启用

标题明确标注“SQL Server 2008 R2”,而非SQL Server 2019或2022。这是因为项目SQL脚本中使用了datetime2类型(SQL Server 2008引入)和ROW_NUMBER() OVER()窗口函数(2005起支持),但未用STRING_AGG等2017+特性。安装时最常卡在“SQL Server 2008 R2安装程序支持文件无法卸载”错误(警告26003),本质是残留注册表项冲突。解决方案分三步:

  1. 运行sqlservr.exe -m以单用户模式启动实例;
  2. sqlcmd -S .\SQLEXPRESS -E连接,执行:
    -- 禁用所有非必要服务 EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;
  3. 在控制面板→程序和功能中,右键“Microsoft SQL Server 2008 R2”→“更改”→勾选“数据库引擎服务”和“SQL Server Management Studio”。

注意:安装完成后必须启用TCP/IP协议。打开“SQL Server配置管理器→SQL Server网络配置→MSSQLSERVER的协议”,右键TCP/IP→启用,并在IP地址页签中将IPAll下的TCP Port设为1433(默认),删除TCP Dynamic Ports值。

2.3 JDBC驱动选择:sqljdbc4.jar而非sqljdbc42.jar

项目lib/目录下自带sqljdbc4.jar,对应SQL Server 2008 R2官方推荐驱动(支持JDK 6+)。若替换为sqljdbc42.jar(适配JDK 8u102+),会触发com.microsoft.sqlserver.jdbc.SQLServerDriver类加载失败。验证方式:

# 将sqljdbc4.jar放入WEB-INF/lib后,在Java代码中显式加载 Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); // 若抛出ClassNotFoundException,说明驱动未正确加载或版本不匹配
驱动文件名适用JDK版本SQL Server支持项目兼容性
sqljdbc4.jarJDK 6–82005–2008 R2✅ 原项目标配
sqljdbc42.jarJDK 8u102+2012–2016❌ 可能类加载失败
mssql-jdbc-12.4.0.jre11.jarJDK 11+2012–2022❌ 不兼容JDK 8

3. 数据库初始化:执行SQL脚本前必须修正3处字段精度与约束冲突

3.1 解压后先检查score_db.sql中的decimal定义

原始SQL脚本中存在两处典型精度问题,直接执行会导致CREATE TABLE失败:

-- ❌ 错误写法(SQL Server 2008 R2不支持decimal(10,10)) CREATE TABLE student_score ( score decimal(10,10) -- 总位数10,小数位10 → 整数位为0,非法! ); -- ✅ 正确写法(保留2位小数,整数位足够) ALTER TABLE student_score ALTER COLUMN score decimal(8,2); -- 6位整数+2位小数,覆盖0–999999.99分

提示:decimal(p,s)p为总位数,s为小数位数,p-s必须≥1。项目中最高分设定为100,故decimal(5,2)已足够(999.99)。

3.2 主键与外键约束需手动添加,脚本未完整生成

score_db.sql仅创建表结构,未定义主外键关系。若跳过此步,Java层插入数据时会因缺失约束导致脏数据。必须补全以下语句:

-- 为student_info表添加主键 ALTER TABLE student_info ADD CONSTRAINT PK_student_id PRIMARY KEY (student_id); -- 为exam_record表添加外键,关联student_info ALTER TABLE exam_record ADD CONSTRAINT FK_exam_student FOREIGN KEY (student_id) REFERENCES student_info(student_id) ON DELETE CASCADE; -- 学生删除时自动清理其考试记录 -- 为score_detail表添加复合主键(避免重复评分) ALTER TABLE score_detail ADD CONSTRAINT PK_score_detail PRIMARY KEY (record_id, question_id);

3.3 插入初始测试数据前关闭IDENTITY_INSERT

项目说明文档要求导入init_data.sql,其中包含student_info的预设ID(如'2023001')。但该表student_idvarchar(10),非自增列,无需SET IDENTITY_INSERT真正需要的是exam_record表的record_id字段——它被定义为int IDENTITY(1,1),但脚本中试图插入指定ID值。解决方法:

-- 在insert语句前加 SET IDENTITY_INSERT exam_record ON; INSERT INTO exam_record (record_id, student_id, exam_time, status) VALUES (1001, '2023001', '2023-06-01 09:00:00', 'completed'); SET IDENTITY_INSERT exam_record OFF;

注意:IDENTITY_INSERT必须对每个表单独开关,且同一时间只能对一个表启用。


4. Java核心逻辑解析:评分线程池如何保证并发安全与事务原子性

4.1ScoreProcessor.java中的双重校验锁与线程隔离

实时评分的核心是ScoreProcessor类,它采用ExecutorService管理固定线程池处理判卷请求。关键设计在于:

public class ScoreProcessor { private static final ExecutorService executor = Executors.newFixedThreadPool(5); // 固定5线程,防资源耗尽 public static void submitScore(String recordId, String[] answers) { executor.submit(() -> { // 1. 获取数据库连接(每个线程独立Connection) Connection conn = DataSourceUtil.getConnection(); try { conn.setAutoCommit(false); // 开启事务 // 2. 批量比对答案并计算得分 int score = calculateScore(conn, recordId, answers); // 3. 更新主表与明细表 updateScoreTable(conn, recordId, score); updateDetailTable(conn, recordId, answers); conn.commit(); // 全部成功才提交 } catch (SQLException e) { try { conn.rollback(); } catch (SQLException ignored) {} throw new RuntimeException("评分事务失败", e); } finally { try { conn.close(); } catch (SQLException ignored) {} } }); } }

逻辑说明:每个评分任务独占一个数据库连接,setAutoCommit(false)确保updateScoreTableupdateDetailTable要么全部成功,要么全部回滚。calculateScore内部使用PreparedStatement防止SQL注入(如WHERE question_id = ?),而非拼接字符串。

4.2ScoreDAO.java中批处理的3个性能参数调优

updateDetailTable方法使用JDBC批处理插入每道题的判分结果。原始代码可能只调用addBatch(),但未设置关键参数,导致性能低下:

public void batchInsertDetails(Connection conn, String recordId, List<ScoreDetail> details) throws SQLException { String sql = "INSERT INTO score_detail (record_id, question_id, is_correct, score) VALUES (?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql); // ⚠️ 必须设置的3个参数 ps.setFetchSize(100); // 每次从数据库拉取100行,减少网络往返 ps.setQueryTimeout(30); // 单条SQL超时30秒,防死锁 ps.setPoolable(true); // 允许连接池复用PreparedStatement for (ScoreDetail d : details) { ps.setString(1, recordId); ps.setString(2, d.getQuestionId()); ps.setBoolean(3, d.isCorrect()); ps.setBigDecimal(4, d.getScore()); ps.addBatch(); } ps.executeBatch(); // 批量执行,非逐条 }
参数默认值推荐值作用
fetchSize0(驱动决定)100控制ResultSet一次缓存行数,提升大数据量查询效率
queryTimeout0(无限制)30防止慢SQL阻塞整个线程池
poolabletruetrue确保PreparedStatement可被连接池复用,降低GC压力

4.3 实时推送机制:用SSE替代轮询,降低服务器负载

项目未用WebSocket,而是基于HTTP协议实现轻量推送。ScoreController.java中:

@GetMapping(value = "/score/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public ResponseEntity<SseEmitter> streamScores(@RequestParam String studentId) { SseEmitter emitter = new SseEmitter(30 * 60 * 1000L); // 30分钟超时 // 注册监听器:当score_detail表有新记录,触发推送 ScoreEventPublisher.addListener(studentId, score -> { try { emitter.send(SseEmitter.event() .name("score-update") .data(score.toJson())); // 发送JSON格式分数 } catch (IOException e) { emitter.complete(); // 客户端断开时清理 } }); // 超时或异常时清理 emitter.onCompletion(() -> ScoreEventPublisher.removeListener(studentId)); emitter.onError(Throwable::printStackTrace); return ResponseEntity.ok(emitter); }

说明:SseEmitter是Spring MVC对Server-Sent Events的支持。前端用EventSource接收:

const eventSource = new EventSource("/score/stream?studentId=2023001"); eventSource.addEventListener("score-update", e => { const data = JSON.parse(e.data); document.getElementById("score").innerText = data.total; });

5. 毕设答辩必答3问:从SQL注入防护到慢查询优化的实操验证

5.1 如何证明系统防SQL注入?用万能密码测试+日志反查

“SQL注入万能密码绕过”是答辩高频问题。本项目在LoginServlet.java中严格使用PreparedStatement

String sql = "SELECT * FROM admin_user WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, request.getParameter("username")); // ✅ 参数化,非拼接 ps.setString(2, request.getParameter("password")); ResultSet rs = ps.executeQuery();

验证方法:

  1. 在登录框输入用户名admin' OR '1'='1,密码任意;
  2. 查看Tomcat日志catalina.out,搜索Preparing statement
    DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger - ==> Preparing: SELECT * FROM admin_user WHERE username = ? AND password = ? DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger - ==> Parameters: admin' OR '1'='1(String), 123456(String)
    若日志显示Parametersadmin' OR '1'='1被当作字符串字面量(而非SQL代码),则注入无效。

提示:答辩时可现场演示——输入' OR 1=1 --后,后端日志显示Parameters含完整字符串,且返回0行结果,证明参数化生效。

5.2 慢SQL优化:用SET STATISTICS IO ON定位I/O瓶颈

当评分响应超过2秒,需分析SQL性能。在SQL Server Management Studio中执行:

SET STATISTICS IO ON; EXEC dbo.calculate_student_rank '2023001'; -- 假设这是慢存储过程 SET STATISTICS IO OFF;

输出示例:

Table 'score_detail'. Scan count 12, logical reads 1560, physical reads 0. Table 'student_score'. Scan count 1, logical reads 8, physical reads 0.

关键指标解读

  • logical reads:内存中读取的数据页数,>1000即需优化;
  • scan count:表扫描次数,>10说明缺少索引;
  • 本例中score_detail逻辑读1560,因未建索引。解决方案:
-- 为高频查询字段添加复合索引 CREATE NONCLUSTERED INDEX IX_score_detail_record_question ON score_detail(record_id, question_id) INCLUDE (is_correct, score); -- 覆盖索引,避免回表

5.3 实时性验证:用Chrome DevTools Network面板测端到端延迟

答辩时需证明“实时”。操作步骤:

  1. 打开Chrome开发者工具→Network标签;
  2. 在教师端点击“发布成绩”;
  3. 切换到学生端页面,观察Network中/score/stream请求:
    • 查看Response Headers中的Cache-Control: no-cache
    • 查看Timing标签中TTFB(Time to First Byte)是否<500ms;
    • 查看Preview中是否持续收到event: score-update数据块。

若TTFB稳定在200ms内,且事件流不间断,则证明SSE推送链路通畅。注意:禁用浏览器缓存(勾选Disable cache)否则可能延迟数秒

技巧:在ScoreEventPublisher中添加日志,记录emitter.send()调用时间戳,与前端收到时间对比,可精确计算网络+服务端处理延迟。

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

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

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

立即咨询