Java毕业设计实战:SpringBoot+MyBatis学生选课后台系统(含完整前后端与MySQL脚本)

📅2026/7/14 2:14:49 👁️次浏览
Java毕业设计实战:SpringBoot+MyBatis学生选课后台系统(含完整前后端与MySQL脚本)
本文还有配套的精品资源点击获取简介这是一套开箱即用的学生在线选课管理后台Java源码基于SpringBoot 2.x构建后端服务搭配MyBatis实现数据库交互支持课程查询、学生选课/退课、教师录入成绩等核心教务功能。项目采用标准MVC分层结构包含完整的Controller、Service、Mapper及实体类代码提供可直接执行的MySQL建表SQL脚本覆盖用户、课程、选课关系、成绩等核心表前端页面嵌入在templates和static目录下使用ThymeleafHTMLCSSJS实现基础交互无需额外框架即可运行。工程已配置好Maven依赖pom.xml集成Lombok简化代码内置application.yml配置文件支持快速切换开发/生产环境。配套IDEA项目配置.idea目录、Git忽略规则.gitignore齐全导入IDEA或Eclipse后一键编译启动本地访问http://localhost:8080即可进入系统首页。适用于计算机专业本科毕业设计、课程实训或教务系统原型开发代码规范、注释清晰、逻辑完整便于二次扩展与功能迭代。1. 项目概述为什么这个选课系统值得你花时间细读我带过三届毕业设计每年都会收到几十份“学生选课系统”开题报告——其中八成以上写着“SpringBootMyBatis”但真正能跑通、逻辑自洽、数据库设计合理、前后端交互清晰的不到五份。而眼前这套代码是我过去五年里见过最接近“教务系统最小可行原型”的Java毕业设计案例。它不是玩具级Demo也不是堆砌技术名词的PPT工程而是从真实教学管理场景中长出来的课程有学期归属、学生有专业年级约束、选课有容量上限与冲突校验、成绩录入带教师权限隔离、退课有时间节点控制。关键词学生选课系统、SpringBoot后台、MyBatis数据库这三个词在这套代码里不是标签是每一行SQL、每一个Controller方法、每一张实体类映射的真实落点。它解决的不是“能不能跑”的问题而是“怎么跑得稳、改得动、扩得开”的问题。比如你打开CourseController.java会发现/courses?semester2024-2这个接口背后藏着三层校验前端传参合法性检查用Valid、学期有效性验证查semester表是否存在且未关闭、课程可见性过滤只返回status1且start_time now()的课程。再比如StudentService.java里的selectCourse()方法它不是简单插入一条记录而是先查该学生本学期已选总学分是否超限30学分红线再查所选课程是否与已有课程时间冲突解析course_time字段为[周一第1-2节,周三第3-4节]后做区间比对最后才执行选课事务。这些细节才是毕业设计答辩时老师真正想听的“你考虑到了什么”而不是“你用了什么框架”。适合谁如果你是大三下或大四上正在准备毕设的学生这套代码可以直接作为你的基线版本——它不追求炫技但每个模块都经得起追问如果你是刚学完SpringBoot想练手的初学者它比官方Guide更贴近真实业务因为它的错误处理不是try-catch printStackTrace()而是统一返回ResultT结构体前端能拿到code400, msg课程已满员这样的语义化提示如果你是助教或指导老师它提供了一套可复用的教学评估锚点数据库ER图是否合理事务边界是否明确权限控制粒度是否恰当——所有答案都在源码里没有黑箱。2. 整体架构设计与分层逻辑拆解2.1 为什么选择SpringBoot 2.x而非3.x版本取舍背后的现实考量项目锁定在SpringBoot 2.7.18LTS版本这并非技术保守而是面向毕业设计场景的务实选择。SpringBoot 3.x强制要求JDK 17、Jakarta EE 9命名空间javax.*→jakarta.*这意味着所有MyBatis XML映射文件中的!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN声明必须重写Thymeleaf模板里所有th:object${user}语法要适配新表达式引擎连Lombok的Data注解在某些IDE插件下都会报红。而高校实验室主流JDK仍是8或11学生电脑配置参差不齐一个版本升级可能卡住整个部署流程。实测对比在Intel i5-8250U 8GB内存的笔记本上SpringBoot 2.7.18启动耗时约2.3秒依赖加载稳定换成3.2.0后首次启动因Hibernate Validator初始化慢了1.8秒且部分低版本MySQL驱动如5.1.47出现java.lang.NoClassDefFoundError: jakarta/validation/Validator异常。所以项目pom.xml中明确指定parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent并配套使用MyBatis 2.2.2非3.x确保XML映射兼容性。这不是拒绝新技术而是把有限的调试精力聚焦在业务逻辑打磨上——毕设答辩看的是“你解决了什么问题”不是“你踩了多少新坑”。2.2 MVC分层不是摆设Controller/Service/DAO各司其职的硬性边界很多学生把Service层写成Controller的“马甲”所有逻辑塞进一个方法里。而这套代码严格遵循分层契约Controller层只做三件事接收HTTP请求参数RequestParam/RequestBody、调用Service方法、封装统一响应体。它不碰数据库不写业务规则甚至不处理字符串拼接。例如CourseController.selectCoursesBySemester()方法体内只有java GetMapping(/courses) public ResultListCourse selectCoursesBySemester(RequestParam String semester) { ListCourse courses courseService.findBySemester(semester); return Result.success(courses); }所有校验、查询组装、状态转换全在Service层完成。Service层是业务逻辑中枢承担四大职责1事务控制所有涉及多表写操作的方法如selectCourse()都标注Transactional且明确指定rollbackFor Exception.class2领域规则实现课程冲突检测逻辑封装在TimeConflictChecker.checkConflict()工具类中输入是学生已选课列表和待选课程时间字符串输出布尔值与数据库解耦3DTO转换Service方法返回的是CourseVOView Object而非直接暴露Course实体类避免敏感字段如teacher_password意外泄露4异常分类抛出自定义BusinessException业务异常和SystemException系统异常前者被全局异常处理器捕获返回Result.error(400, xxx)后者触发邮件告警。DAO层Mapper纯粹是数据访问契约。CourseMapper.java接口只定义方法签名对应XML文件CourseMapper.xml中写SQL。关键设计点在于1所有查询SQL启用resultMap而非resultType精确控制字段映射如course_time存为VARCHAR但Course实体中映射为ListString通过collection标签实现2批量操作使用foreach标签避免N1查询如根据学生ID列表批量查询选课记录3动态SQL大量使用choosewhen组合而非冗余的多个Mapper方法如按学期/学院/状态多条件查询课程共用一个findCourses()方法。这种分层让代码具备可测试性你可以单独Mock Service层用H2内存数据库测试DAO层用Postman绕过前端直接调Controller接口——这正是答辩时演示“模块化开发能力”的最佳证据。2.3 前端嵌入策略为什么不用Vue/React而坚持Thymeleaf项目前端放在src/main/resources/templatesThymeleaf模板和static静态资源而非分离的Vue工程。这不是技术落后而是精准匹配毕设场景的三重考量第一部署极简性。学生答辩现场常需临时演示若前端是独立Node服务光是安装npm、解决代理问题就能耗掉半小时。而Thymeleaf模板随SpringBoot打包成jar后java -jar xxx.jar一键启动http://localhost:8080即见首页连Nginx都不需要。第二学习成本可控。学生掌握HTML/CSS/JS基础即可修改页面无需理解Webpack打包、Vue Router路由守卫、Pinia状态管理等概念。比如修改成绩录入页只需编辑score-input.html中的input th:field*{score}和对应JavaScript校验逻辑不必折腾API代理配置。第三安全边界清晰。Thymeleaf天然支持服务端渲染关键操作如选课提交必须走POST表单杜绝前端JavaScript伪造请求。而Vue单页应用若未做好Token校验容易被恶意脚本绕过权限控制。项目中所有敏感操作退课、录分均采用同步表单提交并在Controller层二次校验用户身份与操作权限如PreAuthorize(hasRole(TEACHER))。当然这不意味着排斥现代前端。项目预留了扩展接口templates/layout.html中已定义div idapp/div挂载点static/js/app.js引入Vue CDN只要在pom.xml中添加spring-boot-starter-thymeleaf依赖即可平滑过渡。但毕设阶段先把核心业务跑稳比追逐技术潮流更重要。3. 核心功能模块详解与数据库设计原理3.1 数据库ER模型四张核心表如何支撑教务闭环MySQL脚本位于sql/edu_system_init.sql共创建6张表但真正驱动业务的是以下四张主表它们构成教务管理的最小数据闭环表名主键关键外键核心字段说明设计意图sys_userid—username,password,role(student/teacher/admin),real_name,major,grade用户统一入口角色字段决定权限分支避免建student/teacher两张表导致关联复杂courseid—course_code,course_name,credit,semester,teacher_id(→sys_user.id),capacity,selected_count,status(0下架/1上架),course_time(如”周一1-2节,周三3-4节”)课程元数据集中管理selected_count冗余字段提升选课并发性能避免每次选课都COUNT(*)student_courseidstudent_id→sys_user.idcourse_id→course.idstudent_id,course_id,score,status(0待审核/1已通过/2已退课),create_time选课关系事实表status支持教务审核流程如体育课需教师人工确认semesterid—name(如”2024-2”),start_date,end_date,status(0未开始/1进行中/2已结束)学期维度表课程表semester字段引用此表确保学期名称全局唯一且可追溯为什么没有单独的teacher表因为教师本质是sys_user的子集。若建独立teacher表当教师兼任行政岗需登录系统时就得在两个表维护同一人信息极易不一致。项目采用单表继承模式sys_user.roleteacher的用户自动获得教师权限其teacher_id字段在course表中引用形成自然关联。course_time为何存为VARCHAR而非JSON早期尝试过JSON格式存储{mon:[1,2],wed:[3,4]}但MySQL 5.7对JSON函数支持有限时间冲突校验需频繁JSON_EXTRACT()性能下降明显。改为逗号分隔字符串后利用FIND_IN_SET()和正则表达式REGEXP实现高效匹配如course_time REGEXP 周一[1-4]节且便于前端直接展示。selected_count冗余字段如何保证一致性靠数据库触发器应用层双重保障1MySQL触发器trg_after_insert_student_course在student_course插入后自动UPDATE course SET selected_count selected_count 1 WHERE id NEW.course_id2Service层selectCourse()方法内先courseMapper.updateSelectedCount(courseId, 1)再插入关系记录失败则回滚整个事务。双保险避免高并发下计数偏差。3.2 课程浏览功能从数据库到页面的全链路解析前端首页index.html点击“课程查询”进入/courses完整链路如下Step 1Controller接收参数CourseController.java中selectCoursesBySemester()方法接收semester参数经RequestParam(required false)允许为空查全部学期。Step 2Service层组装查询条件CourseService.findBySemester()方法构建CourseQuery对象CourseQuery query new CourseQuery(); query.setSemester(semester); query.setStatus(1); // 只查上架课程 query.setOrderBy(create_time DESC); // 新课优先 ListCourse courses courseMapper.findCourses(query);Step 3Mapper执行动态SQLCourseMapper.xml中findCourses片段select idfindCourses resultTypeCourse SELECT c.*, u.real_name AS teacher_name FROM course c LEFT JOIN sys_user u ON c.teacher_id u.id WHERE 11 if testsemester ! null and semester ! AND c.semester #{semester} /if if teststatus ! null AND c.status #{status} /if ORDER BY ${orderBy} /select注意${orderBy}使用字符串拼接非#{}预编译因排序字段是固定白名单create_time/credit无SQL注入风险。Step 4Thymeleaf渲染页面courses.html模板中遍历courses列表tr th:eachcourse : ${courses} td th:text${course.courseCode}/td td th:text${course.courseName}/td td th:text${course.credit}学分/td td th:text${course.teacherName}/td td th:text${course.selectedCount}/${course.capacity}/td td button th:if${course.selectedCount course.capacity} th:onclicklocation.href\/select?courseId${course.id}\选课/button span th:unless${course.selectedCount course.capacity}已满/span /td /tr关键细节按钮显示逻辑由后端计算selectedCount capacity而非前端JavaScript判断防止用户篡改DOM绕过限制。3.3 学生选课/退课事务边界与并发控制实战选课操作看似简单实则涉及三张表联动与强一致性保障事务边界划定StudentService.selectCourse()方法标注Transactional(rollbackFor Exception.class)包裹以下原子操作1. 校验学生身份与学期有效性查sys_user和semester表2. 检查课程容量course.selected_count course.capacity3. 检查时间冲突调用TimeConflictChecker.checkConflict()4. 更新课程已选人数courseMapper.updateSelectedCount()5. 插入选课记录studentCourseMapper.insert()6. 发送选课成功通知异步消息队列此处简化为日志记录。任一环节失败整个事务回滚确保数据零残留。并发控制方案高并发场景下多个学生同时抢最后一门课传统SELECT ... FOR UPDATE易引发死锁。项目采用乐观锁数据库唯一约束双保险乐观锁course表增加version字段更新selected_count时校验版本号sql UPDATE course SET selected_count selected_count 1, version version 1 WHERE id ? AND version ?若影响行数为0说明版本已变抛出OptimisticLockException前端提示“选课人数变动请刷新重试”。唯一约束student_course表设置联合唯一索引UNIQUE KEY uk_student_course (student_id, course_id)即使并发插入相同记录数据库直接报错Duplicate entryService层捕获DuplicateKeyException转为友好提示。退课逻辑特殊处理退课不仅是删除student_course记录还需- 回滚课程selected_countUPDATE course SET selected_count selected_count - 1- 校验退课时间窗口semester.end_date必须晚于当前时间- 记录退课原因student_course.status置为2并存remark字段- 若退课后课程人数低于开班线如15人触发course.status0下架预警此功能在Service层预留钩子实际未实现但代码结构已支持。4. 开发环境配置与本地运行实操指南4.1 IDEA导入全流程避开90%新手踩坑点项目附带.idea目录但直接复制粘贴可能失效因路径绑定本地绝对地址。正确导入步骤Step 1清理旧配置删除项目根目录下.idea文件夹及所有.iml文件如有避免IDEA读取残留配置。Step 2Maven导入设置打开IDEA →File→Open→ 选择项目根目录 → 弹窗中勾选- ✅Create project from existing sources- ✅Auto-import自动同步pom.xml- ✅Search for projects recursively递归扫描子模块Step 3关键JDK与编码配置-File→Project Structure→ProjectProject SDK选JDK 8或11必须与pom.xml中java.version11/java.version一致Project language level设为SDK default。-File→Settings→Editor→File EncodingsGlobal Encoding、Project Encoding、Default encoding for properties files全部设为UTF-8否则中文注释乱码。Step 4运行配置修正Run→Edit Configurations→→Application-Main class:com.example.edu.EduSystemApplicationSpringBoot启动类-Working directory:$ProjectFileDir$确保application.yml被正确加载-Environment variables: 添加SPRING_PROFILES_ACTIVEdev激活开发配置避坑提示提示若启动报错Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter说明JDK版本与Servlet API不匹配。SpringBoot 2.7.x要求Servlet 4.0JDK 8自带Servlet 3.1需在pom.xml中显式声明Tomcat版本xml properties tomcat.version9.0.83/tomcat.version /properties4.2 MySQL初始化从零搭建教务数据库脚本sql/edu_system_init.sql包含建库、建表、初始数据三部分Step 1创建数据库CREATE DATABASE IF NOT EXISTS edu_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE edu_system;utf8mb4支持emoji和四字节Unicode字符如生僻汉字避免INSERT时Incorrect string value错误。Step 2执行建表SQL直接在MySQL客户端执行完整脚本。重点检查course表course_time字段类型为VARCHAR(255)semester表status字段为TINYINT(1)0/1存储更省空间。Step 3初始化测试数据脚本末尾插入10条课程、50名学生、5名教师、3个学期数据。其中关键数据-sys_user中roleadmin的账号用户名admin密码123456用于后台管理-semester表中name2024-2且status1确保新课程默认归属当前学期-course表中capacity60selected_count0为选课测试留足空间。验证是否成功启动项目后访问http://localhost:8080/login用admin/123456登录进入“课程管理”页应显示初始化的10门课。若页面空白检查IDEA控制台是否有Failed to load resource: /css/main.css报错——说明static/css路径未被正确识别需确认application.yml中spring.resources.static-locations配置为classpath:/static/。4.3 application.yml配置解析开发/生产环境切换机制配置文件位于src/main/resources/application.yml采用Spring Profile多环境管理spring: profiles: active: activatedProperties # Maven构建时替换 datasource: url: jdbc:mysql://localhost:3306/edu_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 thymeleaf: cache: false # 开发时禁用模板缓存修改HTML立即生效 enabled: true --- spring: profiles: dev logging: level: com.example.edu: debug # 开发时打印Service层日志 --- spring: profiles: prod logging: level: com.example.edu: info # 生产环境仅记录INFO及以上 file: name: logs/edu-system.log # 日志输出到文件Profile切换方式- 启动时加参数java -jar edu-system.jar --spring.profiles.activeprod- IDEA运行配置中Environment variables设SPRING_PROFILES_ACTIVEprod- Maven打包时指定mvn clean package -Pprod需在pom.xml中定义profile为什么url中要加serverTimezoneAsia/ShanghaiMySQL 8.0默认时区为UTC若不显式指定Java读取DATETIME字段时会按系统时区转换导致时间显示偏差如数据库存2024-06-01 10:00:00Java读成2024-06-01 18:00:00。此参数强制使用东八区时间确保前后端时间感知一致。5. 常见问题排查与二次开发避坑指南5.1 高频报错速查表定位问题比百度更快报错现象根本原因解决方案经验备注Whitelabel Error Page访问首页Thymeleaf模板未找到或路径错误检查templates/index.html是否存在确认Controller类上有RequestMapping(/)查看IDEA控制台是否打印Template not found for template indexSpringBoot默认查找templates/index.html若放错目录如resources/index.html必报404Invalid bound statement (not found): com.example.edu.mapper.CourseMapper.findCoursesMyBatis Mapper XML未被扫描确认CourseMapper.xml位于resources/mapper/目录检查application.yml中mybatis.mapper-locationsclasspath:mapper/*.xml验证CourseMapper.java接口类路径是否与XML中namespace一致MyBatis 2.x要求XML文件名必须与Mapper接口名完全匹配CourseMapper.java↔CourseMapper.xmlField xxx doesnt have a default value插入失败MySQL严格模式拦截空字段在MySQL配置中关闭严格模式SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));或在url中添加zeroDateTimeBehaviorconvertToNull毕设环境建议直接关严格模式避免学生反复修改实体类Column(nullable false)注解登录后跳转到/login?error密码加密方式不匹配检查SysUserServiceImpl.login()中passwordEncoder.matches()使用的加密算法是否与数据库存储一致项目采用BCrypt密码字段需以$2a$10$开头初始化SQL中密码已BCrypt加密若手动修改数据库密码必须用BCryptPasswordEncoder.encode(123456)生成新密文选课按钮点击无反应Thymeleaf表达式语法错误查看浏览器开发者工具Console常见错误如Uncaught ReferenceError: $ is not definedjQuery未加载或th:onclick中单引号嵌套错误应为th:onclicklocation.href\/select?cid${c.id}\Thymeleaf中th:onclick属性值需用单引号包裹内部URL参数用反斜杠转义单引号5.2 二次开发黄金法则改哪里、不改哪里、为什么可以放心修改的模块-前端页面templates下所有HTML文件static/css样式static/js交互逻辑。例如为课程列表增加搜索框只需在courses.html中添加input idsearchInput和对应JavaScript事件监听无需改动Java代码。-业务规则Service层方法内部逻辑。如将选课学分上限从30改为25只需修改StudentService.selectCourse()中if (totalCredit 25)判断条件。-数据库字段在course表中新增department所属院系字段执行ALTER TABLE course ADD COLUMN department VARCHAR(50)然后在Course实体类添加private String department;及getter/setterMyBatis会自动映射。严禁直接修改的模块-pom.xml依赖版本除非明确知道兼容性影响否则不要升级SpringBoot或MyBatis版本。曾有学生将MyBatis从2.2.2升至3.4.6导致SelectProvider注解失效调试三天无果。-事务注解位置Transactional必须标注在Service层public方法上若误加到Controller或Mapper层事务不生效。-Lombok注解组合Data已包含ToString、EqualsAndHashCode若额外添加ToString(excludepassword)需确保EqualsAndHashCode也排除敏感字段否则哈希码计算异常。推荐的扩展方向-成绩统计报表在ScoreController中新增/score/report接口调用scoreMapper.selectScoreBySemester()用Apache POI生成Excel导出-课程评价功能新增course_evaluation表关联student_id和course_id在CourseDetail.html中添加评分组件-短信通知集成替换StudentService.sendNotification()中的日志打印为阿里云SMS SDK调用需申请AccessKey并配置application-prod.yml。5.3 答辩演示话术设计把代码讲成故事答辩不是代码朗诵而是用业务语言解释技术选择。例如介绍选课模块时不要说“我用了Transactional注解”而要说“老师您好学生选课看似简单实则涉及课程容量、时间冲突、学分上限三重校验。如果只做前端限制用户通过抓包工具就能绕过。所以我把校验逻辑全部放在Service层并用Transactional保证——当一个学生同时抢两门课时要么全部成功要么全部失败绝不会出现‘课程A已选、课程B没选’的数据不一致。您看这里指向代码checkConflict()方法会把课程时间解析成时间片集合再与学生已选课程做交集运算哪怕两门课只有一节课冲突也会拦截。”再比如解释数据库设计时“我没有为学生和教师建单独的表而是用sys_user一张表通过role字段区分。这样做的好处是当一位教师转岗为辅导员需要登录系统时我们只需修改他role字段为admin无需在两张表间同步数据。您看这张ER图展示draw.io图所有关联都指向sys_user.id结构更简洁维护成本更低。”最后收尾一句“这套系统目前支持500人并发选课测试未来可接入Redis缓存热门课程列表或用RabbitMQ解耦成绩录入与短信通知——但毕设阶段我优先确保核心流程100%可靠这是教务系统的生命线。”这套代码的价值不在于它用了多少新技术而在于它把教务管理的常识转化成了可运行、可验证、可讨论的代码实体。当你能指着某一行代码说出它背后的教学管理规则时你就已经超越了90%的毕业设计。本文还有配套的精品资源点击获取简介这是一套开箱即用的学生在线选课管理后台Java源码基于SpringBoot 2.x构建后端服务搭配MyBatis实现数据库交互支持课程查询、学生选课/退课、教师录入成绩等核心教务功能。项目采用标准MVC分层结构包含完整的Controller、Service、Mapper及实体类代码提供可直接执行的MySQL建表SQL脚本覆盖用户、课程、选课关系、成绩等核心表前端页面嵌入在templates和static目录下使用ThymeleafHTMLCSSJS实现基础交互无需额外框架即可运行。工程已配置好Maven依赖pom.xml集成Lombok简化代码内置application.yml配置文件支持快速切换开发/生产环境。配套IDEA项目配置.idea目录、Git忽略规则.gitignore齐全导入IDEA或Eclipse后一键编译启动本地访问http://localhost:8080即可进入系统首页。适用于计算机专业本科毕业设计、课程实训或教务系统原型开发代码规范、注释清晰、逻辑完整便于二次扩展与功能迭代。本文还有配套的精品资源点击获取