首页 / 资讯中心 / 文章详情

基于SpringBoot的学生选课系统:从表设计到并发控制全解析

基于SpringBoot的学生选课系统:从表设计到并发控制全解析 ★ FEATURED ARTICLE
简介这是一份基于SpringBoot框架的学生网上选课系统毕业设计论文面向高校计算机相关专业学生及需要完成同类课题的开发者。文档从传统人工选课管理效率低、易出错等痛点切入系统论述了需求分析、可行性研究、总体设计以及基于Eclipse、MySQL、Tomcat的详细实现过程。功能模块覆盖用户管理、新闻公告、课程管理、选课操作与数据统计数据库设计包含用户表、课程表、选课表等核心表结构并给出了系统测试与结论展望。资源包内含1个DOC格式文档大小1.29MB已有92人浏览学习。对于正在筹备毕业设计或学习SpringBoot实战的读者这套资料不仅能作为论文结构与写作的范本也能帮助理解选课业务逻辑、数据建模以及SpringBoot项目开发思路具备较强的参考与复用价值。1. 基于SpringBoot的学生网上选课系统这个毕业设计题目到底在考你什么每年毕业季都能看到大量“基于SpringBoot的学生网上选课系统”这类题目乍一看是一个CRUD管理系统实际做下来你会发现它比想象中多一层东西。这个标题背后隐藏着完整的业务闭环学生登录、浏览课程、选课、退课教师维护课程信息、录入成绩管理员管理学生和教师账号、审核课程还要处理选课冲突、容量限制、时间冲突这些真实业务规则。也就是说它不只是“增删改查”而是带状态流转和业务约束的小型管理系统。从技术选型来看SpringBoot是这个题目的绝对核心围绕它的还有MyBatis-Plus或JPA做持久层、MySQL存数据、Thymeleaf或Vue做前端页面如果做得更完整一些会加Spring Security或JWT做登录认证。整套东西做下来你会发现它实际上是一个标准的企业级单体应用原型具备权限控制、事务处理、数据校验、异常处理这些真实开发中天天要面对的问题。这篇笔记就按我做这类项目的实际路径来讲先拆表结构设计再讲接口和权限怎么定然后重点讲选课这个核心业务怎么写代码最后把最容易踩的坑和答辩时的高频追问整理出来。你要是正在做这个题目或者想接这类单子照着这条路径走能把翻车概率压到最低。2. 选课系统的表结构设计五张核心表与三个容易埋雷的字段2.1 从业务反推数据模型为什么至少要五张表学生网上选课系统的业务角色很清晰学生、教师、管理员核心业务是选课和退课。按这个反推最基础的表至少要有这几张student学生表学号、姓名、专业、年级、密码、已选学分teacher教师表工号、姓名、学院、职称、密码course课程表课程编号、课程名、学分、授课教师、上课时间、上课地点、容量、已选人数student_course选课记录表选课ID、学号、课程号、选课时间、成绩admin管理员表账号、密码如果想把系统做得更像回事还要加一张 semester学期表用来区分不同学期的开课计划但很多毕设做单学期的简化版本也够用。核心在于 student_course 这张关联表它是学生和课程的多对多关系的载体所有核心业务——选课、退课、查成绩——都围绕它展开。这里我一般建议用逻辑外键代替物理外键就是只存关联ID不建 FOREIGN KEY 约束。原因很简单毕设阶段经常要批量导入测试数据物理外键在删除和导入时会带来一堆顺序上的麻烦逻辑外键在代码层控制引用完整性就足够了。这一点后面在避坑章节会详细说。CREATE TABLE student_course ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id BIGINT NOT NULL COMMENT 学生学号, course_id BIGINT NOT NULL COMMENT 课程ID, choose_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, score DECIMAL(5,2) DEFAULT NULL COMMENT 成绩未录入时为NULL, status TINYINT DEFAULT 0 COMMENT 0-正常 1-退课 2-结课, UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课记录表;这张表里有个容易被忽略的点选课记录用 status 字段做软删除/状态标记而不是直接 DELETE 删除记录。退课这个行为在业务上需要留痕比如管理员要能查“谁在什么时候退过课”而且统计维度上退课后该学生选课名额要释放。用状态字段替代物理删除是最常见的做法。2.2 容量字段与已选人数一个字段的两种设计思路课程表里“容量”和“已选人数”这两个字段看起来简单实际上有两种设计路径。第一种是只存容量已选人数通过 COUNT(*) 实时算出来第二种是把已选人数也存成一个字段选课时用 UPDATE ... SET selected_count selected_count 1 WHERE selected_count capacity 这样的原子操作来更新。实时 COUNT 的做法写起来简单但每次选课都要对 student_course 按 course_id 做聚合数据量上来后性能会明显变差而且并发选课的时候会产生超选。维护一个冗余字段的做法常见于真实生产系统配合数据库的行锁或乐观锁可以很好解决并发问题代价是每次选课/退课都要同时更新这个字段多一层一致性维护。ALTER TABLE course ADD COLUMN selected_count INT DEFAULT 0 COMMENT 已选人数;我个人推荐把 selected_count 字段加上。理由是这种带容量限制的抢课场景用整型字段做条件更新是最高效的并发控制手段。完整实现下一章会展开讲这里先把表结构定好。2.3 时间字段的三个细节为什么要把时间字段单独拎出来讲课表里“上课时间”这个字段是最容易做错的。第一种做法是存字符串比如“周一 3-4节”第二种是存成星期几加节次编号两个数字字段。我做的话会选择后者course_week TINYINT 存1到7代表周一到周日course_start TINYINT 和 course_end TINYINT 存起始节次和结束节次。这样做的直接好处是排课冲突检测可以用数字比较来完成比如判断两门课是否时间重叠时不用去解析中文字符串。另一个细节是 student 和 teacher 的密码字段很多初稿会把密码明文存进去。这个题目的要求虽然不会卡得特别严但答辩时如果你主动说出“密码用 BCrypt 加密存储”印象分会明显不一样。Spring Security 自带的 BCryptPasswordEncoder 一行代码就能完成加密和校验投入产出比很高。第三个细节是 create_time 和 update_time 这类审计字段用 MyBatis-Plus 的自动填充注解或者数据库的 DEFAULT CURRENT_TIMESTAMP 都能解决不要每个插入语句手动传时间既丑又容易漏。2.4 字符集与存储引擎两个看起来没技术含量但直接影响体验的配置建表时 DEFAULT CHARSET 一定要用 utf8mb4 而不是 utf8。utf8 在 MySQL 里是 utf8mb3 的别名存不了 emoji 和部分生僻字学生姓名里万一有生僻字就直接插入报错。另外建议统一用 InnoDB 引擎——如果你在毕设里用了 MyISAM一旦答辩老师问“为什么不支持事务”就是给自己挖坑。InnoDB 支持事务和外键行锁在并发选课时是必要的。还有一点值得多说每张表都建议加一个自增主键 id而不是直接用 student_id / course_id 当主键。逻辑业务主键用 UNIQUE KEY 约束保证唯一性自增主键只做物理定位用。这种“代理主键业务唯一键”的方式对后续分页、关联查询、批量删除都更友好也是生产环境的主流实践。3. 用户认证与权限设计三个角色如何共用一套登录入口3.1 登录认证选型JWT还是Session毕设场景哪个更合适登录认证这块一开始我建议不要引入太重的方案。最常见的做法是 SpringBoot Interceptor 做登录拦截用 Session 保存登录状态简单直观答辩也好讲。但如果你想要更好的前后端分离体验或者想在简历里多提一个亮点JWT 方案是更合适的选择。JWT 的本质是把用户信息加密后发给前端之后每次请求由后端解密验证。它的好处是服务端无状态缺点是 token 过期和注销处理要自己写。Spring Security JWT 的完整配置涉及过滤器链、SecurityContextHolder、自定义认证入口这部分代码量不小。在做这个选课系统时我建议走一条折中路用 Spring MVC 的拦截器实现角色鉴权用 JWT 或 Session 做登录态管理两种方案都不需要引入 Spring Security 全家桶但能讲出清晰的认证流程。Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从请求头获取token也可以从session获取这里演示JWT方式 String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } // 解析token得到用户角色 try { Long userId JwtUtil.parseToken(token.replace(Bearer , )); request.setAttribute(userId, userId); return true; } catch (Exception e) { response.setStatus(401); return false; } } }拦截器的逻辑不复杂但位置关键preHandle 里做身份验证和角色判断放行后 Controller 才能拿到当前用户信息。这里要注意拦截器只做“是否登录”的检查角色判断建议在拦截器里根据请求路径前缀做区分比如 /student/** 和 /teacher/** 分别限制对应角色访问。3.2 注册登录接口的常见翻车点密码加密与登录态续期密码加密是登录接口最常翻车的地方。用 MD5 加密在今天的视角看来基本等于没过脑——MD5 撞库成本极低随便一个彩虹表都能把简单密码还原出来。这个环节我用 BCrypt 来做它是 Spring Security 自带的密码哈希算法自带盐值同密码每次哈希结果不同暴力破解成本比 MD5 高很多。PostMapping(/login) public Result? login(RequestBody LoginRequest request) { // 先查数据库找到用户再比对密码 Student student studentMapper.selectByStudentNo(request.getAccount()); if (student null || !new BCryptPasswordEncoder().matches(request.getPassword(), student.getPassword())) { return Result.error(账号或密码错误); } // 登录成功后生成token MapString, Object claims new HashMap(); claims.put(userId, student.getId()); claims.put(role, student); String token JwtUtil.createToken(claims, 24 * 3600 * 1000L); return Result.success(token); }这段代码里最容易忽略的是登录接口的“用户类型判断”很多系统把学生、教师、管理员登录拆成三个接口前端维护三套入口。做成交互简单一点的常见做法是让用户选择身份或者自动识别账号前缀——这些设计要提前想好否则后面联调时前端会找你改接口。登录态过期问题也值得注意。JWT 设置的过期时间是 24 小时学生选课把页面挂了一晚上第二天提交选课直接 401体验很差。常见的处理办法是前端在收到 401 时自动跳转登录页或者后端提供 refresh token 接口做续期。毕设做简单版的话设 12 到 24 小时的 token 有效期并让前端在过期前静默续签就够了。3.3 三个角色的权限矩阵该怎么落地权限控制最容易做成一团浆糊的地方是在 Controller 层堆 if-else。标准做法是用拦截器按路径前缀做粗粒度控制再在需要细粒度控制的接口里取当前登录用户的ID进行数据属主校验。比如学生只能看自己的选课记录教师只能改自己的课程信息这些校验一旦漏掉就会出现“学生调用接口修改了别人的选课记录”这种低级问题。具体落地路径定义一个 WebConfig 注册拦截器设置 addPathPatterns 和 excludePathPatterns。登录接口、注册接口、静态资源放行其余全部进拦截器。然后定义三个子路径前缀/api/student/、/api/teacher/、/api/admin/分别在拦截器里校验 JWT 中的角色字段是否匹配前缀。这样比在 Controller 层加注解更直观答辩时也容易讲清楚。数据属主校验是权限设计的第二层。教师修改课程时要校验 course 表中的 teacher_id 是否等于当前登录用户的ID否则一个教师能改所有人的课程。很多初稿在这一层漏掉系统看起来能跑但接口是被“裸奔”的。4. 选课核心业务从事务到并发控制这块代码决定你的项目档次4.1 选课接口的需求拆解容量检查、时间冲突、重复选课选课是整套系统的核心业务也是最容易写出“看起来能用、一并发就挂”的代码的地方。选课接口至少要完成四件事校验课程是否存在、校验是否已选过、校验课程容量是否已满、校验上课时间是否和其他已选课程冲突。Transactional public Result? chooseCourse(Long studentId, Long courseId) { // 1. 查课程信息加行锁防止并发超选 Course course courseMapper.selectByIdForUpdate(courseId); if (course null) { return Result.error(课程不存在); } // 2. 检查是否已经选过这门课 int count studentCourseMapper.countByStudentIdAndCourseId(studentId, courseId); if (count 0) { return Result.error(不能重复选课); } // 3. 时间冲突检测查出该学生所有已选课程的时间段 ListCourse selectedCourses courseMapper.selectByStudentId(studentId); for (Course selected : selectedCourses) { if (isTimeConflict(course, selected)) { return Result.error(上课时间冲突 selected.getCourseName()); } } // 4. 容量检查用条件更新原子扣减 int updated courseMapper.reduceCapacity(courseId); if (updated 0) { return Result.error(课程已满); } // 5. 插入选课记录 StudentCourse record new StudentCourse(); record.setStudentId(studentId); record.setCourseId(courseId); record.setStatus(0); studentCourseMapper.insert(record); return Result.success(选课成功); }这段代码是选课接口的标准实现关键有两点整个方法加 Transactional 保证原子性任何一步失败都整体回滚容量检查用 UPDATE 的条件更新而不是先查再判断避免两个并发请求同时读到容量有余量。4.2 乐观锁与悲观锁的取舍超选问题的两种解法超选问题是选课系统的经典技术点。所谓超选指的是并发场景下最后一个人选的课程超过容量。解决办法有两种路线悲观锁方案查询课程时用 SELECT ... FOR UPDATE 把这一行锁住其他事务必须等当前事务提交后才能继续操作。好处是逻辑简单缺点是并发性能差而且锁粒度大同一门课的选课请求会排队。 乐观锁方案在 course 表加一个 version 字段每次更新时带上版本号条件更新成功说明没人改过更新失败说明数据已经变了需要重新处理。UPDATE course SET selected_count selected_count 1, version version 1 WHERE id #{courseId} AND selected_count capacity;这条 SQL 是乐观锁的常见变体——不引入 version 字段直接用“当前已选人数小于容量”作为更新条件。affected rows 为 0 说明没抢到名额为 1 说明更新成功。它比纯 version 方案少一次查询在容量扣减这个场景里是更优雅的解决方式。我自己的实践是两种都写一下选课接口用 UPDATE 条件更新的乐观锁方案做容量扣减同时在查询课程时配合 FOR UPDATE 做余额读取的一致性保证。答辩时主动把这个设计讲出来然后解释为什么弃用纯悲观锁能明显体现出对并发问题的理解深度。4.3 排课冲突检测用数字比较替代字符串解析时间冲突检测的核心是把“上课时间”从文本形式结构化。前面建表时设计了 course_week、course_start、course_end 三个数字字段冲突检测就变成一个简单的区间相交判断public boolean isTimeConflict(Course newCourse, Course existedCourse) { // 不在同一天上课肯定不冲突 if (!newCourse.getCourseWeek().equals(existedCourse.getCourseWeek())) { return false; } // 判断区间是否重叠新课程开始节次小于已有课程结束节次 // 且新课程结束节次大于已有课程开始节次 return newCourse.getCourseStart() existedCourse.getCourseEnd() newCourse.getCourseEnd() existedCourse.getCourseStart(); }这个判断等价于两个闭区间 [start, end] 是否有交集。需要注意边界条件第3节到第4节和第4节到第5节是否算冲突按照实际排课逻辑相邻节次之间如果课间不休息算连上算冲突如果课间休息则不算。在数据录入时统一规则即可。大部分毕设的实现里第4节到第5节之间有课间休息可以不冲突。4.4 退课接口释放名额与保留记录退课接口是选课的反向操作但要注意的细节更多。退课时要更新选课记录的状态为“已退课”释放 course 表的已选人数同时要判断课程是否已经结束或者老师是否已录入成绩。Transactional public Result? dropCourse(Long studentId, Long courseId) { // 查选课记录确认这条记录存在且状态正常 StudentCourse record studentCourseMapper.selectByStudentIdAndCourseId(studentId, courseId); if (record null || record.getStatus() ! 0) { return Result.error(选课记录不存在或已退课); } // 防止退已录入成绩的课程 if (record.getScore() ! null) { return Result.error(该课程已录入成绩无法退课); } // 状态改为退课 record.setStatus(1); studentCourseMapper.updateById(record); // 释放容量 courseMapper.increaseCapacity(record.getCourseId()); return Result.success(退课成功); }这里的业务规则是“教师录入成绩后不能退课”这个约束在需求文档里经常不写但答辩时如果被问到你会怎么处理能答出来是加分项。另外退课释放容量时要确认释放的确实是之前选课占用的名额这一点因为退课只改变状态不删除记录所以不会发生“释放了别人的名额”的问题。5. 从零跑通SpringBoot项目手把手搭建到生成器写码5.1 环境准备与项目初始化开始动手前先确认环境JDK 8 或 JDK 11、Maven 3.6、MySQL 5.7。IDE 用 IntelliJ IDEA 或 Eclipse 都行IDEA 的 Spring Initializr 可以直接从菜单新建 SpringBoot 项目选择 Web、MyBatis、MySQL Driver 这几个依赖即可。这里有一个常见分歧用 Spring Boot 2.x 还是 3.x。2.x 基于 javax 命名空间网上资料最多兼容性最好直到今天仍是国内教学的主力版本。3.x 基于 jakarta 命名空间要求 JDK17部分老教程的代码直接粘过去会报错。做毕设缺乏试错时间的话选 2.7.x 是最稳的。pom.xml 里除了 Web 和 MySQL 驱动还要加 MyBatis-Plus 和 Lombok前者把单表 CRUD 的样板代码省掉后者解决实体类 getter/setter 的冗长问题。这两个依赖加入之后你不用写一行 SQL 就能完成对五张表的增删改查选课、退课、登录这些核心接口的 SQL 再手写。5.2 三种代码生成路径手写、逆向工程与代码生成器实体类和 Mapper 的代码生成有两种主流方案。第一种是手写——先从数据库表字段推导 Java 属性再写 Mapper 接口再写 XML 的 SQL。五张表大约 50 个属性手写大概一小时能完成但容易在字段名映射上出低级错误。第二种方案是使用 MyBatis-Plus 的代码生成器。它根据数据库表结构自动生成实体类、Mapper 接口、Service、ControllerSpringBoot 项目里配置一个生成器主函数运行一次就能把基础代码全部产出。生成出来的代码可以直接跑也可以在此基础上改业务逻辑。public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create(jdbc:mysql://localhost:3306/course_system, root, password) .globalConfig(builder - builder.author(developer).outputDir(src/main/java)) .packageConfig(builder - builder.parent(com.example.course)) .strategyConfig(builder - builder.addInclude(student, teacher, course, student_course, admin)) .execute(); } }代码生成器能省时间但生成的代码是“能用”而不是“好看”。比如分页查询的默认实现是简单的 Page 对象多表关联查询需要手写 Join SQL。你要在这里投入修改时间给生成的 Service 补业务校验给 Controller 补参数校验在生成代码的基础上加业务逻辑而不是把生成结果直接当作成品。就我经验来看项目里真正值得手写的是三块选课相关的 Mapper 方法需要用 FOR UPDATE、条件 UPDATE统计类的聚合 SQL已选学分、选课人数以及所有带状态流转的更新语句。其他纯单表查询交给 MyBatis-Plus 的 QueryWrapper 足够。5.3 配置文件的边界哪些参数必须调、哪些不用动application.yml 的配置看似琐碎有几个参数不调会直接影响开发和联调体验spring: datasource: url: jdbc:mysql://localhost:3306/course_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: status logic-delete-value: 1 logic-not-delete-value: 0datasource 的 URL 里 serverTimezoneAsia/Shanghai 是必须的否则 MySQL 8.x 连接时区报错characterEncodingutf8 保证中文写入不乱码。mybatis-plus 的 log-impl 配成 StdOutImpl控制台会打印每条 SQL联调阶段排错效率翻倍上线前再关掉。逻辑删除的配置可以省——上一章我们用 status 字段做手动状态管理如果配置了全局逻辑删除MyBatis-Plus 的所有查询会自动带上 status 0 条件反而会影响正常查询。这里我给的建议是别配全局逻辑删除在需要状态过滤的查询里手动加条件看得更清楚。5.4 运行与验证启动后第一件事是测登录还是测选课项目启动后不要急着点前端页面先按顺序验证核心链路。第一步用 curl 或 Postman 调登录接口拿 token第二步拿 token 调学生列表接口确认认证拦截生效第三步选一门容量充足的课程第四步选同一门课确认重复选课拦截生效第五步用一个学生账号同时选两门时间冲突的课确认冲突检测生效。这五步走完系统的核心骨架就是健康的。用 curl 验证是最快的路径curl -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {account:2021001,password:123456,role:student} curl -X POST http://localhost:8080/api/student/choose \ -H Content-Type: application/json \ -H Authorization: Bearer eyJhbGciOi... \ -d {courseId:1}这里要提一个很多新人会卡住的点前端如果直接用浏览器地址栏访问选课接口因为没带 Authorization 头会被拦截器 401 挡回来。这不代表系统有问题而是说明认证生效了。你还需要做的是测试 token 过期、错误 token、无 token 三种异常路径确保拦截器对非法请求的响应是一致的。6. SpringBoot选课系统最常见的五个坑现象、原因与解决方案6.1 中文乱码出现在 JSON 响应或写入数据库现象前端页面显示的课程名称是“???”或者登录接口返回的提示信息变成乱码。原因一般是两个数据库连接串没有 characterEncodingutf8或者数据库表本身是 latin1 字符集。解决路径是先看数据库表和字段的字符集确保是 utf8mb4再改连接串加 characterEncodingutf8 参数。如果都没问题看一下 SpringBoot 的 server.servlet.encoding 配置是否强制 UTF-8。排查顺序从底层往上查先数据库再连接串再应用层。6.2 选课人数超出课程容量导致 0 人名单现象课程容量 30 人但 student_course 表里对应课程有 32 条记录。原因几乎是必然的选课接口没有用事务包裹容量检查和插入或者没有用条件 UPDATE 扣减容量。两个并发请求同时读到容量还有 1 个名额都通过检查都执行插入。解决方法是回到第四章的选课代码把容量扣减改成 UPDATE ... WHERE selected_count capacity 的原子操作并且方法上必须有 Transactional。6.3 拦截器放行了登录接口却拦截了静态资源现象页面加载时 CSS、JS 全部 404或验证码图片加载不出来。原因WebConfig 里只配置了登录接口放行没有放行 /static/、/css/、/js/** 这些路径拦截器把所有请求拦下来后静态资源直接响应 401。解决方法是把静态资源路径加入 excludePathPatterns另外如果前后端分离Swagger 或 Knife4j 的接口文档路径也要在开发环境放行否则没法在浏览器里调试接口。6.4 前端分页显示正常但搜索功能查不到数据现象按课程名称模糊搜索时关键字带中文能查到带英文查不到或者百分比符号 % 被当成通配符导致查询结果异常。原因MyBatis 的 XML 里拼 SQL 时用了 ${keyword} 而不是 #{keyword}或者模糊查询时没有对 % 和 _ 做转义。解决方法是模糊查询统一用 CONCAT(%, #{keyword}, %) 方式拼接不要用字符串直接拼到 SQL 里——既能防止 SQL 注入也能避免通配符污染查询条件。6.5 事务没生效明明加了 Transactional 但选课记录还是插入了现象选课逻辑里故意抛异常测试回滚发现选课记录还是写进去了。原因最典型的两种情况一是 SpringBoot 启动类或配置类没有加 EnableTransactionManagement 注解Spring Boot 2.x 其实默认开启了但如果你自定义了 DataSource 就需要显式声明二是 Transactional 加在了同一个类内部调用的私有方法上——Spring AOP 基于代理内部调用不走代理事务自然失效。解决方法是把事务方法放到独立的 Service 类里并且确保方法被外部调用不要同类调用同类。排查事务是否生效有个简单办法在选课方法里故意写一行 throw new RuntimeException()如果选课记录没有插入说明事务生效如果插入成功了说明代理没起作用。7. 答辩问不倒的进阶优化缓存、压测与经典追问准备做到这一步系统该有的功能都已经能跑了但答辩时老师很少会满足于“能跑”。准备几个进阶话题能明显拉开和其他同学的差距。第一个话题是缓存优化。选课高峰期首页会展示所有课程列表每次请求都查数据库压力不小。用 Spring Cache 或 Caffeine 给课程列表加个本地缓存设置 30 秒过期可以显著降低数据库压力。要注意的是缓存只用于查询接口选课和退课这类写操作必须走数据库。Cacheable(value courseList, key all) public ListCourse getAllCourses() { return courseMapper.selectList(new QueryWrapperCourse().orderByAsc(course_id)); }CacheEvict(value courseList, allEntries true) public Result? chooseCourse(Long studentId, Long courseId) { // 选课逻辑... }缓存的粒度要控制好课程列表可以缓存但“已选人数”这种高频变化数据不适合放缓存否则学生看到的名额和实际能选的会不一致产生超卖假象。这里只缓存课程基本信息列表已选人数保持实时读取是最稳妥的折中方案。第二个话题是并发压测。你可以在答辩时主动说“我用 JMeter 对选课接口做了并发测试50 个线程同时选同一门容量 30 的课程最终选课记录只有 30 条没有超选”。这个验证结果比任何口头解释都有说服力。做这个测试只需要 JMeter 创建一个线程组配置 HTTP 请求添加聚合报告查看响应数据不需要写任何代码。jmeter -n -t choose_course_test.jmx -l result.jtl -e -o report/第三条要准备的是经典追问的答法。老师常问的问题包括“你的登录状态是怎么保持的”“并发情况下怎么防止超选”“学生选了课但没退老师能不能改成绩”“数据库表是怎么设计的为什么这么拆”这些问题的答案在这篇笔记里都覆盖到了关键是自己能顺着代码讲出来而不是背概念。最后我自己的习惯是做完系统后抽一天时间从头删掉数据库重建严格按照 README 的步骤走一遍部署流程。这一步能发现所有“我自己电脑上明明能跑”的问题——比如忘记初始化 SQL 脚本、密码写死在配置里、第三方依赖没打进去。一个能照着文档复制粘贴跑通的系统和一个只能在自己电脑上跑的系统答辩观感是完全不同的。希望这篇笔记能帮你在做这个题目的过程中少踩几个坑。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站