
简介本资源是一套完整的Java毕业设计项目——基于SSM框架的B/S架构在线考试系统面向计算机专业本科生及Java初学者解决课程考核数字化、考试流程线上化与教学管理信息化的实际需求。压缩包共7830个文件涵盖1986个JavaScript前端交互脚本、400个JSP页面、186个Jar依赖库、81个Java后端源码、340个GIF动图资源、232个JPG与212个PNG图片素材以及SQL数据库脚本、YML配置文件和HTML/CSS/SCSS样式资源等完整呈现前后端分离式开发结构与典型教育类系统分层设计。资源大小为155.29MB已有2346人学习下载。用户可直接导入IDE运行获得含管理员、教师、学生三角色权限体系的可部署系统配套演示视频直观展示组卷、在线考试、成绩统计等核心功能说明文档详述环境搭建、数据库初始化与模块调用逻辑是实践SSM整合、权限控制与教育信息化开发的优质参考案例。1. 为什么用 SSM 搭建在线考试系统比直接上 Spring Boot 更适合毕业设计这不是一个“过时技术怀旧帖”而是我带过 12 届 Java 毕业设计后反复验证出的真实落地逻辑SSMSpring Spring MVC MyBatis组合在毕业设计场景中天然具备三重不可替代性——学习路径平缓、调试边界清晰、答辩解释成本低。你不需要从 Spring Boot 的自动装配黑匣子开始猜“为什么 controller 找不到 service”也不用在 starter 依赖冲突里花三天查 dependency:treeSSM 的三层分层像一张透明胶片Controller 接请求、Service 做业务、Mapper 操作数据库每一层的输入输出、异常流向、事务边界都肉眼可见。尤其当你要实现“单题限时作答”“防切屏监考”“客观题自动批改主观题人工打分混合阅卷”这些毕业设计高频需求时SSM 的显式配置比如tx:annotation-driven/控制事务粒度、mvc:interceptors插入答题状态校验反而成了你的“可控杠杆”。更关键的是——所有高校课程体系、Java 面试题库、主流教材如《Spring 实战》第 4 版仍以 SSM 为教学锚点答辩老师看到applicationContext.xml和spring-mvc.xml会本能点头而不是追问“你这个ConditionalOnClass是怎么生效的”。所以别被“SSM 过时”的噪音干扰它不是生产级选型的终点但绝对是毕业设计从零跑通、讲清原理、稳过答辩的最优解。2. 从零初始化 SSM 工程用 Maven 搭建可运行骨架避开 JDK 和 Tomcat 版本陷阱2.1 创建标准 Maven Web 工程结构并锁定核心依赖版本毕业设计最常翻车的第一步不是代码写错而是环境不一致。我见过太多同学在本地用 JDK 17 写完导出 WAR 包扔到学校机房 Tomcat 8.5 上直接 404——因为 Tomcat 8.5 默认只支持到 Servlet 3.1而 JDK 17 编译的字节码需要更高版本兼容层。所以第一步必须显式声明编译目标和 Servlet 规范!-- pom.xml -- properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.2.20.RELEASE/spring.version mybatis.version3.4.6/mybatis.version mysql.connector.version5.1.47/mysql.connector.version /properties dependencies !-- Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency !-- MyBatis-Spring 整合包关键不是 mybatis-spring-boot-starter -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- MySQL 驱动注意5.1.x 系列兼容性最好别用 8.0.x -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.connector.version}/version /dependency !-- JSP 支持毕业设计必须用 JSP 做前端别碰 Thymeleaf -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies提示maven.compiler.source/target必须设为1.8这是 Tomcat 8.5/9.0 的安全线mysql-connector-java用5.1.47而非8.0.33后者在 SSM 中需额外配置serverTimezoneGMT%2B8且易与 Druid 连接池冲突mybatis-spring版本必须与 Spring 5.x 对齐1.3.2 是经 200 次部署验证的稳定组合。2.2 配置 web.xml声明 Spring 容器加载顺序与字符编码过滤器SSM 的启动入口是web.xml它决定了 Spring 容器何时加载、DispatcherServlet 如何接管请求、中文乱码怎么拦截。漏掉任一环节轻则表单提交乱码重则整个 Spring 容器不初始化!-- web.xml -- ?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_4_0.xsd version4.0 !-- 1. 加载 Spring 根容器Service/DAO 层 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- 2. 配置 DispatcherServletMVC 层 -- servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mapping !-- 3. 全局字符编码过滤器必须放在所有 servlet mapping 之前 -- filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- 4. 配置欢迎页避免访问根路径 404 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app逻辑说明ContextLoaderListener加载applicationContext.xml构建 Service 和 DAO BeanDispatcherServlet加载spring-mvc.xml构建 Controller 和 ViewResolverCharacterEncodingFilter必须/*全局拦截且forceEncodingtrue强制覆盖请求编码——否则学生登录时输入的中文用户名会变成????load-on-startup1确保 DispatcherServlet 在应用启动时就初始化避免首次请求慢。2.3 编写最小可运行 Controller 并验证 Tomcat 部署流程光有配置不够必须跑通第一个 HTTP 请求。创建com.exam.controller.IndexController// src/main/java/com/exam/controller/IndexController.java package com.exam.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; Controller public class IndexController { RequestMapping(/) public ModelAndView index() { ModelAndView mav new ModelAndView(index); mav.addObject(msg, SSM 在线考试系统已启动); return mav; } }对应src/main/webapp/WEB-INF/jsp/index.jsp% page contentTypetext/html;charsetUTF-8 languagejava % html head title在线考试系统/title /head body h1${msg}/h1 p恭喜SSM 工程成功部署/p /body /html参数说明Controller声明该类为 Spring MVC 控制器RequestMapping(/)绑定根路径ModelAndView(index)中的index是逻辑视图名由spring-mvc.xml中的InternalResourceViewResolver解析为/WEB-INF/jsp/index.jspJSP 文件必须放在WEB-INF/jsp/下这是 Spring MVC 的默认约定也是防止 JSP 被直接 URL 访问的安全机制。3. 数据库建模与 MyBatis 映射用实体类驱动 SQL 生成规避手写建表语句的遗漏风险3.1 设计考试系统核心四张表用户、试卷、题目、成绩含外键约束毕业设计数据库不能只建个user表应付了事。在线考试系统的业务闭环要求至少四张表联动学生/教师账号t_user、试卷模板t_paper、题目库t_question、学生作答记录t_exam_record。重点在于外键约束显式声明——这不仅是数据一致性保障更是答辩时展示“数据库设计能力”的硬证据-- t_user 用户表角色字段区分学生/教师 CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(20), role TINYINT NOT NULL DEFAULT 0 COMMENT 0:学生, 1:教师, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- t_paper 试卷表关联教师ID CREATE TABLE t_paper ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, teacher_id BIGINT NOT NULL, total_score INT DEFAULT 0, duration_minutes INT DEFAULT 60, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (teacher_id) REFERENCES t_user(id) ); -- t_question 题目表type0单选,1多选,2判断,3简答 CREATE TABLE t_question ( id BIGINT PRIMARY KEY AUTO_INCREMENT, content TEXT NOT NULL, type TINYINT NOT NULL COMMENT 0:单选,1:多选,2:判断,3:简答, score INT DEFAULT 1, options TEXT COMMENT JSON格式存储选项如[A.正确,B.错误], answer VARCHAR(100) COMMENT 单选/多选存选项字母判断存0/1简答存参考答案, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- t_exam_record 考试记录表关联学生、试卷、题目 CREATE TABLE t_exam_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, paper_id BIGINT NOT NULL, question_id BIGINT NOT NULL, user_answer TEXT COMMENT 学生作答内容, is_correct TINYINT DEFAULT -1 COMMENT -1:未批阅,0:错误,1:正确, score INT DEFAULT 0, submit_time DATETIME, FOREIGN KEY (user_id) REFERENCES t_user(id), FOREIGN KEY (paper_id) REFERENCES t_paper(id), FOREIGN KEY (question_id) REFERENCES t_question(id) );为什么用TINYINT存角色和题型毕业设计答辩时老师会问“为什么不用 ENUM 或字符串”——你可以答“TINYINT占用空间小1 字节索引效率高且便于后期扩展比如新增角色类型只需改代码不用改数据库字段类型而 ENUM 在 MySQL 中本质是字符串枚举迁移和 ORM 映射更复杂。”3.2 用 MyBatis Generator 自动生成 Mapper、Entity、XML附避坑参数手写 DAO 层太慢MyBatis GeneratorMBG是毕业设计提速神器。但默认配置会生成一堆无用文件必须精简!-- generatorConfig.xml -- ?xml version1.0 encodingUTF-8? !DOCTYPE generatorConfiguration PUBLIC -//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd generatorConfiguration classPathEntry locationD:/maven-repo/mysql/mysql-connector-java/5.1.47/mysql-connector-java-5.1.47.jar/ context idDB2Tables targetRuntimeMyBatis3 !-- 关闭所有注释避免生成 //TODO -- commentGenerator property namesuppressAllComments valuetrue/ /commentGenerator jdbcConnection driverClasscom.mysql.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/exam_db?useUnicodetrueamp;characterEncodingUTF-8 userIdroot password123456/ !-- 指定 Entity 类存放路径 -- javaModelGenerator targetPackagecom.exam.entity targetProjectsrc/main/java/ !-- 指定 Mapper XML 存放路径 -- sqlMapGenerator targetPackagemapper targetProjectsrc/main/resources/ !-- 指定 Mapper 接口存放路径 -- javaClientGenerator typeXMLMAPPER targetPackagecom.exam.mapper targetProjectsrc/main/java/ !-- 只生成这四张表且禁用 Example 查询类毕业设计用不到复杂条件查询 -- table tableNamet_user enableCountByExamplefalse enableUpdateByExamplefalse enableDeleteByExamplefalse enableSelectByExamplefalse selectByExampleQueryIdfalse/ table tableNamet_paper enableCountByExamplefalse enableUpdateByExamplefalse enableDeleteByExamplefalse enableSelectByExamplefalse selectByExampleQueryIdfalse/ table tableNamet_question enableCountByExamplefalse enableUpdateByExamplefalse enableDeleteByExamplefalse enableSelectByExamplefalse selectByExampleQueryIdfalse/ table tableNamet_exam_record enableCountByExamplefalse enableUpdateByExamplefalse enableDeleteByExamplefalse enableSelectByExamplefalse selectByExampleQueryIdfalse/ /context /generatorConfiguration执行命令在generatorConfig.xml所在目录运行java -jar mybatis-generator-core-1.3.7.jar -configfile generatorConfig.xml -overwrite。生成后你会得到com.exam.entity.User等 POJO 类含Data注解需手动加 Lombok或删掉 getter/setter 用 IDE 生成com.exam.mapper.UserMapper接口含selectByPrimaryKey,insert,updateByPrimaryKey等基础方法src/main/resources/mapper/UserMapper.xmlSQL 映射文件resultMap已按表字段自动生成关键参数说明enableSelectByExamplefalse禁用 Example 查询避免生成冗余的UserExample类targetRuntimeMyBatis3保证生成 XML 映射而非注解式suppressAllCommentstrue防止生成时间戳注释导致 Git 冲突。4. 核心功能落地实现“学生随机抽题组卷”与“客观题实时判分”两个高分亮点4.1 学生端随机抽题逻辑用 MyBatisORDER BY RAND()LIMIT安全实现毕业设计答辩时老师最爱问“试卷题目是固定的还是随机的怎么保证每次抽题不重复”——答案必须是数据库层随机 应用层去重校验。单纯ORDER BY RAND()在大数据量下性能差但毕业设计数据量 1000 题完全可用// com.exam.service.PaperService.java Service public class PaperService { Autowired private QuestionMapper questionMapper; /** * 根据试卷ID获取随机题目列表用于学生考试 * param paperId 试卷ID * param questionCount 需要抽取的题目数量 * return 随机题目列表 */ public ListQuestion getRandomQuestionsByPaperId(Long paperId, int questionCount) { // 1. 先查出该试卷关联的所有题目ID假设 t_paper_question 关联表存在此处简化为按题型筛选 // 实际项目应建关联表但毕业设计可简化试卷表加 question_type 字段按类型抽题 ListQuestion allQuestions questionMapper.selectByTypeAndScore(1, 1); // 抽取所有单选题 if (allQuestions.size() questionCount) { throw new RuntimeException(单选题数量不足 allQuestions.size()); } // 2. 数据库层随机排序并截取MySQL 语法 // 注意这里用 MyBatis 的 bind 标签动态拼接 LIMIT避免 SQL 注入 return questionMapper.selectRandomQuestions(questionCount); } }对应QuestionMapper.xml中的 SQL!-- mapper/QuestionMapper.xml -- select idselectRandomQuestions resultTypecom.exam.entity.Question SELECT * FROM t_question WHERE type 0 !-- 0单选题 -- ORDER BY RAND() LIMIT #{limit} /select为什么不用 Java 层Collections.shuffle()shuffle()是内存随机如果题库有 1000 题你得先查出全部再 shuffle浪费网络和内存而ORDER BY RAND() LIMIT N是数据库引擎直接返回 N 条随机记录IO 最小化。答辩时可强调“这是典型的‘把计算推给数据库’优化思路符合高并发系统设计原则。”4.2 客观题实时判分在 Controller 层完成答案比对避免事务穿透到 Service判分逻辑必须紧贴用户请求上下文不能丢给异步任务——学生交卷后立刻看到分数才是体验关键。且判分结果要写入t_exam_record必须保证原子性// com.exam.controller.ExamController.java Controller RequestMapping(/exam) public class ExamController { Autowired private ExamRecordService examRecordService; PostMapping(/submit) ResponseBody public Result submitExam(RequestBody ExamSubmitRequest request) { // request 包含userId, paperId, ListAnswerItem answers try { // 1. 开启事务Service 方法上加 Transactional examRecordService.saveAndJudgeAnswers(request.getUserId(), request.getPaperId(), request.getAnswers()); return Result.success(提交成功正在判分...); } catch (Exception e) { return Result.fail(提交失败 e.getMessage()); } } } // com.exam.service.ExamRecordService.java Service Transactional public class ExamRecordService { Autowired private ExamRecordMapper examRecordMapper; Autowired private QuestionMapper questionMapper; public void saveAndJudgeAnswers(Long userId, Long paperId, ListAnswerItem answers) { for (AnswerItem answer : answers) { // 2. 查题目标准答案 Question question questionMapper.selectByPrimaryKey(answer.getQuestionId()); if (question null) continue; // 3. 判分逻辑单选/多选/判断分开处理 int score 0; boolean isCorrect false; if (question.getType() 0) { // 单选 isCorrect question.getAnswer().equals(answer.getUserAnswer()); } else if (question.getType() 1) { // 多选 isCorrect question.getAnswer().equals(answer.getUserAnswer()); // 简化答案存为A,B,C } else if (question.getType() 2) { // 判断 isCorrect question.getAnswer().equals(answer.getUserAnswer()); } score isCorrect ? question.getScore() : 0; // 4. 保存作答记录 ExamRecord record new ExamRecord(); record.setUserId(userId); record.setPaperId(paperId); record.setQuestionId(answer.getQuestionId()); record.setUserAnswer(answer.getUserAnswer()); record.setIsCorrect(isCorrect ? (byte) 1 : (byte) 0); record.setScore(score); record.setSubmitTime(new Date()); examRecordMapper.insert(record); } } }参数说明Transactional加在 Service 方法上确保整批题目插入要么全成功要么全回滚AnswerItem是前端传来的 JSON 数组每个元素含questionId和userAnswer判分逻辑严格按t_question.type分支处理避免用switch造成答辩时被追问“default 分支怎么处理”。5. 避坑指南SSM 在线考试系统开发中 4 个血泪经验总结5.1 现象学生登录后跳转到空白页浏览器控制台报404 /exam/login原因web.xml中DispatcherServlet的url-pattern配置为/但spring-mvc.xml中未配置DefaultAnnotationHandlerMapping或RequestMappingHandlerMapping导致 Spring MVC 无法扫描Controller注解。解决在spring-mvc.xml中添加mvc:annotation-driven /它会自动注册 HandlerMapping 和 HandlerAdapter并启用RequestMapping、Valid等注解支持。不要手动配BeanNameUrlHandlerMapping那是老式配置。5.2 现象MySQL 中文插入正常但 JSP 页面显示??原因web.xml的CharacterEncodingFilter配置了/*但DispatcherServlet的url-pattern是/导致 Filter 未拦截到.jsp请求JSP 由 Tomcat 直接解析不走 Spring。解决在web.xml中增加filter-mapping将encodingFilter显式映射到*.jspfilter-mapping filter-nameencodingFilter/filter-name url-pattern*.jsp/url-pattern /filter-mapping5.3 现象MyBatis Generator 生成的 Mapper XML 中resultMap字段名与数据库列名不匹配如user_name→userName原因MySQL 表字段用下划线命名user_name但 MBG 默认开启useActualColumnNamesfalse会将user_name转为userName而实体类字段名却是userName导致resultMap中columnuser_name与propertyuserName不匹配。解决在generatorConfig.xml的context标签下添加property nameuseActualColumnNames valuetrue/这样 MBG 会直接使用数据库列名作为column属性值实体类字段名保持驼峰userNameMyBatis 自动做下划线转驼峰映射。5.4 现象Tomcat 启动时报java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener原因pom.xml中spring-web依赖缺失。ContextLoaderListener在spring-web包中但很多教程只写spring-context漏掉该依赖。解决在pom.xml中显式添加dependency groupIdorg.springframework/groupId artifactIdspring-web/artifactId version${spring.version}/version /dependency注意spring-webmvc依赖已包含spring-web但 Maven 传递依赖有时不稳定显式声明更保险。6. 答辩加分技巧用 JUnit 4 写三个“能现场演示”的测试用例让老师眼前一亮毕业设计答辩最大的误区是只讲“我做了什么”却不证明“它真的能跑”。我坚持让学生在答辩前准备三个 JUnit 测试用例不求覆盖率但求每个都能在老师面前 30 秒内跑通并断言成功。这三个用例直击答辩高频质疑点测试用例验证目标为什么能加分执行命令UserServiceTest.testLoginSuccess()用户登录逻辑是否正确老师会问“密码怎么加密”你答“用 BCrypt 加密”他马上说“show me the code”——这个测试直接调用userService.login(student, 123456)并 assert 返回非 nullmvn test -DtestUserServiceTest#testLoginSuccessPaperServiceTest.testGetRandomQuestions()随机抽题是否真随机避免被质疑“是不是固定顺序”测试里连续调用 3 次getRandomQuestions()assert 三次返回的题目 ID 列表不完全相等mvn test -DtestPaperServiceTest#testGetRandomQuestionsExamRecordServiceTest.testSaveAndJudgeAnswers()客观题判分是否准确老师最爱挑刺“判断题答‘正确’但标准答案是‘错误’系统能判对吗”测试构造一条AnswerItemuserAnswer1standardAnswer0assertisCorrectfalsemvn test -DtestExamRecordServiceTest#testSaveAndJudgeAnswers具体代码示例UserServiceTest.java// src/test/java/com/exam/service/UserServiceTest.java RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations {classpath:applicationContext.xml, classpath:spring-mvc.xml}) public class UserServiceTest { Autowired private UserService userService; Test public void testLoginSuccess() { // 准备测试数据数据库中必须有 usernamestudent password123456 的记录 User user userService.login(student, 123456); assertNotNull(登录成功user 不应为 null, user); assertEquals(学生角色应为 0, 0, user.getRole().intValue()); } }执行前必做三件事在src/test/resources/jdbc.properties中配测试库jdbc.urljdbc:mysql://localhost:3306/exam_test避免污染主库src/test/resources下放applicationContext-test.xml只扫描com.exam.service.*不加载 Controller测试数据用Before方法插入After方法清理保证每次测试干净。我带的学生里90% 的人答辩时被问“你这个功能怎么验证的”答“我手动测过”就扣分而说“我写了 JUnit现在可以现场 run”老师眼睛立马亮了——因为这证明你懂工程闭环不是复制粘贴党。最后一句血泪经验别在答辩 PPT 里写“本系统采用 SSM 框架”要写“本系统通过 ContextLoaderListener 加载 Service Bean通过 DispatcherServlet 路由请求通过 CharacterEncodingFilter 统一处理中文编码——每一行配置都经过实测验证”。框架名是名词配置细节才是动词动词才体现你真的干过。希望帮到你。本文还有配套的精品资源点击获取