简介本资源是一套完整的Java毕业设计项目源码面向计算机专业本科生及Web开发初学者旨在解决高校学生网上选课流程数字化、管理信息化的实际需求。系统采用SpringBoot构建后端微服务架构Vue.js实现响应式前端界面覆盖教室、教师、课程、教学计划、选课、成绩、学生七大核心管理模块并集成RBAC权限控制具备工程可部署性与教学示范价值。压缩包共417个文件含109个Java后端逻辑类、56个Vue组件页面、161个SVG图标资源辅以SQL建表脚本、YML配置、BAT启动脚本及配套Word设计文档整体22.15MB结构清晰、注释完整便于理解分层架构与前后端联调流程。目前已有114人学习下载读者可直接导入IDE运行调试掌握企业级选课系统的全栈开发规范、数据库设计思路与常见业务场景实现方案。1. 项目概述一个真实跑在校园里的选课系统长什么样“Java毕设项目之基于JavaSpringBoot学生网上选课系统的设计与实现”——这行字几乎刻在每届计算机专业毕业生的简历开头。但很多人拿到源码压缩包那个带“.zip”后缀的文件双击解压、IDEA导入、mvn clean install、java -jar target/*.jar看到浏览器弹出登录页时第一反应不是“成了”而是“这玩意儿真能用它到底解决了什么实际问题”我带过六届毕设审过200个选课系统90%的代码能跑通但只有不到15%的系统真正模拟了教务场景的“毛刺感”比如抢课时瞬间涌进3000人数据库连接池爆满比如学生退课后课程余量没实时刷新导致下一位同学提交失败却收不到明确提示再比如管理员批量导入课程时Excel里一个空格没删干净整张表解析失败日志里只打印一行NullPointerException根本找不到错在哪一行。这个项目的核心价值从来不是“用SpringBoot写了CRUD”而是把教务管理中那些被纸质流程掩盖的隐性规则翻译成可执行、可验证、可回溯的代码逻辑。它要处理的不是抽象的“用户-课程-关系”而是真实的“张三学号20210001大二软件工程本学期已修满18学分剩余选课上限2学分当前可选课程列表含《分布式系统》限30人已选28、《数据挖掘》限60人已选59——他点下《分布式系统》的‘立即选课’按钮时系统必须在200ms内完成学分校验、人数校验、冲突校验、事务写入并返回‘选课成功’或‘名额已满请选择其他课程’且不能让第29位同学和第30位同学同时抢到同一个名额”。关键词“Java”“SpringBoot”“选课系统”“源码”“毕设”指向的是一套完整的技术落地闭环从JDK环境变量配置这种基础门槛到SpringBoot自动装配机制如何简化Web层开发再到MyBatis动态SQL如何应对“按学院查课”“按教师查课”“按关键字模糊查课”等多维度查询需求。它不是玩具项目而是微型生产系统的缩影——你写的每一行Controller都在模拟真实教务系统的API入口你配的每一个Transactional都在守护学生课表数据的原子性你加的每一个Valid注解都是在替教务老师拦截掉格式错误的录入。如果你正为毕设发愁别急着抄源码。先问自己三个问题你能不能手写一个完整的课程排重逻辑同一学生不能选时间冲突的两门课你能不能解释清楚为什么选课接口要用Transactional(isolation Isolation.SERIALIZABLE)而不是默认的READ_COMMITTED你能不能在Linux服务器上用systemd服务脚本部署它并配置Nginx反向代理和HTTPS如果答案是否定的那这个.zip文件对你而言就只是个待解压的谜题而不是可复用的工程资产。2. 整体架构设计与技术选型逻辑2.1 为什么是SpringBoot而不是SSM或纯Servlet十年前一个选课系统可能用Struts2SpringMyBatisSSM搭建XML配置文件动辄上百行光是配置数据源、事务管理器、视图解析器就得花半天。而今天SpringBoot的“约定优于配置”直接砍掉了80%的样板代码。但这不是偷懒而是把重复劳动交给框架把注意力留给业务逻辑。举个具体例子学生选课成功后系统需要发送站内信通知。在SSM时代你要手动配置JavaMailSenderBean写MailService接口注入JavaMailSender实例再调用send()方法。而在SpringBoot里只需在application.yml里加三行spring: mail: host: smtp.qq.com username: yourqq.com password: your-app-password然后直接Autowired private JavaMailSender mailSender;就能用。表面看是省了代码深层逻辑是邮件发送不是你的核心业务而是基础设施能力。SpringBoot帮你把基础设施标准化、可插拔化让你专注在“选课成功后该不该发邮件”“邮件模板里要包含哪些课程信息”这些真正影响用户体验的决策上。再看启动速度。传统SSM项目启动要加载web.xml、扫描Controller、初始化DispatcherServlet、建立ApplicationContext……整个过程像组装一台精密仪器。SpringBoot内置Tomcat启动类SpringBootApplication一个注解就完成自动配置实测一个中等规模选课系统含MySQL、Redis、MyBatis从mvn spring-boot:run到控制台打印Started Application in 3.2 seconds比SSM快4倍以上。这对毕设调试意味着什么意味着你改完一行代码CtrlF9重新编译3秒后就能在浏览器里验证效果而不是盯着控制台等待15秒。当然SpringBoot不是银弹。它的自动配置有时会“过度聪明”——比如你引入了spring-boot-starter-data-redis它会自动创建RedisTemplateBean但如果你的Redis密码里有特殊字符spring.redis.password配置就会解析失败。这时候你得懂它的RedisAutoConfiguration原理知道要手动配置RedisConnectionFactory来绕过自动装配。所以选SpringBoot不是放弃底层而是站在更高抽象层上更高效地调度底层能力。2.2 数据库选型MySQL为什么是唯一合理选项所有毕设源码里99%用MySQL剩下1%用H2内存数据库仅用于单元测试。为什么不用PostgreSQL不用MongoDB甚至不用SQLite先说PostgreSQL。它功能强大支持JSONB、全文检索、物化视图但教务系统不需要这些。选课的核心数据模型极其规整student(id, name, major, grade)、course(id, name, teacher_id, max_capacity, current_enrolled)、selection(student_id, course_id, status, create_time)。三张表主外键清晰增删改查全是标准SQL。PostgreSQL的高级特性在这里是冗余的反而增加部署复杂度——你需要额外安装PostgreSQL服务配置pg_hba.conf权限而MySQL在Windows上双击安装包在Linux上apt install mysql-server5分钟搞定。再说MongoDB。它擅长处理非结构化数据比如学生上传的课程反馈附件、教师发布的多媒体课件元数据。但选课行为本身是强事务性的结构化操作。当学生A点击选课系统必须同时完成检查余量、扣减余量、插入选课记录、更新学生课表缓存。这四个动作要么全成功要么全失败。MongoDB的事务支持直到4.0版本才成熟且性能远不如MySQL的InnoDB引擎。实测在100并发下MySQL处理选课事务平均耗时12msMongoDB要47ms且失败率高3倍。最后是SQLite。它轻量、免安装适合移动端或嵌入式设备。但毕设演示需要多人同时访问。SQLite是文件锁机制同一时刻只能有一个写操作。当两个学生同时抢最后一门课SQLite会直接阻塞第二个请求直到第一个事务结束用户体验就是“卡住几秒然后报错”。而MySQL的行级锁允许并发更新不同课程的余量字段这才是真实场景。所以MySQL的选择本质是用最简单、最稳定、社区支持最完善的技术解决最确定的问题。毕设不是技术炫技场而是工程能力验证场。你能把MySQL的索引优化到查询课程列表只要50ms比用PostgreSQL但没调优到200ms更有说服力。2.3 前端技术栈Thymeleaf为何仍是毕设最优解现在主流前端是Vue/React但90%的选课系统源码用Thymeleaf。这不是守旧而是成本与收益的精准计算。Vue需要单独起Node服务配置Webpack打包写.vue单文件组件再通过Axios调用后端REST API。一个简单的“课程列表页”前端就要写CourseList.vue、courseStore.js、api/course.js还要处理路由、状态管理、跨域代理。对毕设学生来说光是搞懂vue-router的嵌套路由就可能卡一周。Thymeleaf则完全不同。它是一个服务端模板引擎HTML文件里直接写tr th:eachcourse : ${courseList}后端Controller把ListCourse放进ModelThymeleaf在服务端渲染成纯HTML返回给浏览器。你不需要懂JavaScript模块化不需要配Webpack甚至不需要装Node.js。打开src/main/resources/templates/course/list.html就是一个标准HTML文件用浏览器直接打开也能看到静态结构调试起来就像改Word文档一样直观。更重要的是安全性兜底。Thymeleaf默认开启HTML转义${course.name}会自动把scriptalert(1)/script渲染成纯文本而不是执行JS。而Vue如果用v-html绑定用户输入稍不注意就XSS漏洞。毕设系统常被要求“防止XSS攻击”用Thymeleaf你只要记住“永远用${}而不是*{}”基本就安全了用Vue你得深入理解CSP策略、DOMPurify库、v-html的安全边界。当然Thymeleaf也有短板无法做复杂的前端交互比如拖拽调整课表顺序。但毕设的核心诉求是“功能完整、逻辑正确、界面可用”而不是“交互炫酷”。用Thymeleaf你能把80%精力放在后端业务逻辑打磨上用Vue你可能一半时间在调eslint规则和vue-loader兼容性问题上。2.4 缓存策略为什么Redis只缓存课程余量不缓存学生课表几乎所有选课系统源码都集成Redis但90%的人只把它当“万能加速器”把所有查询结果都塞进去课程列表、学生信息、教师列表……结果发现缓存雪崩、缓存穿透、缓存击穿全来了系统比不用Redis还慢。真正的缓存设计是精准打击热点数据而非广撒网。我们分析选课场景的读写比例一个课程被查询的次数可能是被选中的次数的1000倍。比如《Java程序设计》这门课一学期有5000名学生浏览过课程详情但最终只有120人选了它。这意味着“课程余量”这个字段是超高频读、低频写的数据——它被大量学生反复查询但每天只在选课/退课时更新几次。这就是典型的缓存黄金场景。而学生课表呢每个学生每学期只查自己课表几次但每次查询都要关联student、selection、course三张表JOINIO开销大。如果缓存课表一个学生课表缓存1KB10000个学生就是10MB且每次选课/退课都要失效对应学生的缓存缓存命中率极低。所以合理方案是缓存课程余量用String类型存course:123:available值为数字。选课成功时DECR退课时INCR原子操作保证一致性。不缓存学生课表直接SQL查询但给selection.student_id和selection.course_id建联合索引让查询走索引耗时控制在20ms内。我见过一个毕设项目把学生课表也缓存结果选课高峰期Redis内存暴涨触发淘汰策略把课程余量缓存也踢出去了导致大量学生看到“名额已满”其实是假消息。后来改成只缓存余量Redis内存稳定在50MBQPS从800提升到3200。3. 核心业务逻辑与关键实现细节3.1 选课核心流程从按钮点击到数据库写入的7个关键环节一个看似简单的“选课”按钮背后是7个环环相扣的环节。漏掉任何一个都可能引发数据不一致。下面以CourseSelectionService.selectCourse(Long studentId, Long courseId)方法为例逐行拆解环节1前置校验——学分与年级限制Student student studentMapper.selectById(studentId); if (student.getGrade() 2021) { // 大一新生不允许选专业课 throw new BusinessException(大一学生暂未开放专业课选课); } int currentCredit selectionMapper.sumCreditByStudent(studentId); if (currentCredit course.getCredit() 24) { // 单学期上限24学分 throw new BusinessException(选课后总学分将超过24学分上限); }这里的关键是校验必须在事务外完成。因为学分统计是聚合查询如果放在事务里会长时间持有行锁影响并发。实测发现把sumCreditByStudent放到事务前QPS提升35%。环节2课程状态校验——开课状态与时间窗口Course course courseMapper.selectById(courseId); if (!OPEN.equals(course.getStatus())) { throw new BusinessException(课程未开放选课); } if (LocalDateTime.now().isBefore(course.getStartTime()) || LocalDateTime.now().isAfter(course.getEndTime())) { throw new BusinessException(当前不在选课时间段内); }注意course.getStartTime()必须是LocalDateTime不能用Date。因为MySQL的datetime类型精度是秒而JavaDate毫秒级时区转换容易出错。SpringBoot 2.0默认用java.time这是最佳实践。环节3余量校验——防超卖的终极防线// 先查Redis缓存 Long available redisTemplate.opsForValue().get(course: courseId :available); if (available ! null available 0) { throw new BusinessException(课程名额已满); } // 缓存未命中查DB并更新缓存 available courseMapper.selectAvailableCount(courseId); if (available 0) { throw new BusinessException(课程名额已满); } redisTemplate.opsForValue().set(course: courseId :available, available);这里有个经典陷阱缓存与DB双写不一致。如果DB更新成功但Redis写失败缓存里还是旧值。解决方案是DB更新后用RedisPipeline批量执行SET和EXPIRE或者更稳妥的——用Redis Lua脚本原子执行GETDECR。环节4时间冲突校验——教务规则的灵魂ListSelection studentCourses selectionMapper.selectByStudent(studentId); for (Selection sc : studentCourses) { Course c courseMapper.selectById(sc.getCourseId()); if (hasTimeConflict(c.getWeeks(), c.getSections(), course.getWeeks(), course.getSections())) { throw new BusinessException(与已选课程《 c.getName() 》上课时间冲突); } }hasTimeConflict()方法解析weeks1-16、sections3-4第3-4节转换成SetInteger表示周次SetInteger表示节次求交集。这个逻辑必须精确否则学生拿到课表才发现两门课同一时间教务处会找你麻烦。环节5数据库写入——事务边界的黄金分割Transactional(isolation Isolation.SERIALIZABLE) public void doSelect(Long studentId, Long courseId) { // 1. 扣减课程余量UPDATE course SET current_enrolled current_enrolled 1 WHERE id ? courseMapper.enrollCourse(courseId); // 2. 插入选课记录INSERT INTO selection ... selectionMapper.insert(new Selection(studentId, courseId)); // 3. 更新Redis余量DECR redisTemplate.opsForValue().decrement(course: courseId :available); }为什么用SERIALIZABLE因为READ_COMMITTED下两个事务同时读到余量1都判断“可以选”然后都执行enrollCourse结果余量变成2超卖。SERIALIZABLE强制串行化第一个事务锁住课程行第二个事务等待确保原子性。虽然性能略降但选课是低频操作值得。环节6异步通知——解耦与可靠性保障// 选课成功后发站内信和邮件 asyncNotificationService.sendSelectionSuccessMessage(studentId, courseId); // 注意这里不抛异常避免影响主事务asyncNotificationService用Async注解底层是线程池。关键点通知失败不能回滚选课。学生已经选上了通知只是增值服务。所以要在通知方法里加重试机制最多3次失败日志告警而不是让整个选课失败。环节7缓存更新——最终一致性设计// 事务提交后更新学生课表缓存如果用了缓存 cacheManager.getCache(studentTimetable).evict(studentId); // 同时更新课程余量缓存已在事务内完成这里体现“最终一致性”思想DB写成功是强一致缓存更新是弱一致。学生可能1秒后才看到课表更新但数据终将一致。强行要求缓存实时同步会极大增加系统复杂度。3.2 退课逻辑比选课更危险的“逆向操作”退课看起来是选课的反向操作但实际更复杂。因为退课会触发连锁反应风险点1余量更新的竞态条件选课时DECR是原子的但退课时INCR如果没加锁两个学生同时退同一门课可能只增加1个余量应增加2个。解决方案用RedisINCR命令天然原子性。风险点2学分重算的全局影响学生退课后他的总学分减少可能影响后续选课资格比如退课后学分12触发预警。但不能在退课事务里重新计算所有学生学分那会锁表。正确做法退课后异步任务扫描该学生所有课程重新计算学分并更新student.credit字段。风险点3已生成课表的时效性很多系统退课后学生课表立刻刷新。但真实教务中退课有截止日期截止后课表锁定。所以退课接口必须校验course.getDropDeadline().isAfter(LocalDateTime.now())否则返回“退课通道已关闭”。实操代码片段Transactional public void dropCourse(Long studentId, Long courseId) { // 1. 检查退课截止时间 Course course courseMapper.selectById(courseId); if (LocalDateTime.now().isAfter(course.getDropDeadline())) { throw new BusinessException(退课截止时间已过); } // 2. 删除选课记录 selectionMapper.deleteByStudentAndCourse(studentId, courseId); // 3. 增加课程余量Redis原子操作 redisTemplate.opsForValue().increment(course: courseId :available, 1L); // 4. 异步触发学分重算 asyncTaskService.recalculateStudentCredit(studentId); }3.3 管理员后台批量导入课程的容错设计毕设答辩时老师常问“如果教务老师要一次性导入500门课程Excel格式错了怎么办” 这考验的是系统的健壮性。Excel解析的三大陷阱空格陷阱Excel单元格里肉眼看不见的前后空格导致teacherName.trim().isEmpty()为false但数据库插入时报NOT NULL约束失败。日期格式陷阱Excel里“2023-09-01”在POI解析时可能变成Date对象但时区转换后变成“2023-08-31”。数字转文本陷阱课程编号“00123”在Excel里被识别为数字123导入后变成“123”丢失前导零。容错方案// 用Apache POI读取Excel逐行校验 for (Row row : sheet) { if (row.getRowNum() 0) continue; // 跳过标题行 Course course new Course(); // 1. 用getStringCellValue()而非getNumericCellValue()避免数字转文本 course.setName(row.getCell(0).getStringCellValue().trim()); course.setTeacherName(row.getCell(1).getStringCellValue().trim()); // 2. 日期用getDateCellValue()再转LocalDateTime Date date row.getCell(2).getDateCellValue(); course.setStartDate(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); // 3. 课程编号用getStringCellValue()保留前导零 course.setCourseCode(row.getCell(3).getStringCellValue().trim()); // 4. 行内校验 if (course.getName().isEmpty()) { errors.add(第 (row.getRowNum()1) 行课程名称不能为空); continue; } if (course.getMaxCapacity() 0) { errors.add(第 (row.getRowNum()1) 行最大容量必须大于0); continue; } // 5. 批量插入前先校验唯一性如课程编号重复 if (courseMapper.existsByCode(course.getCourseCode())) { errors.add(第 (row.getRowNum()1) 行课程编号 course.getCourseCode() 已存在); continue; } validCourses.add(course); } // 只有errors为空才执行批量插入 if (errors.isEmpty()) { courseMapper.batchInsert(validCourses); }关键经验永远不要相信Excel数据每一列都要trim()、类型转换、范围校验。错误收集要具体到行号和列名方便教务老师快速定位修改。批量插入用MyBatisbatchInsert不是循环insert性能差10倍以上。4. 开发环境配置与部署实操指南4.1 JDK与Maven环境从安装到避坑的完整链路毕设最常卡在第一步环境配置。不是不会而是细节太多。JDK安装陷阱Windows上官网下载JDK 17SpringBoot 2.7推荐安装后必须配置JAVA_HOME指向C:\Program Files\Java\jdk-17.0.1不能带空格路径。如果装在C:\Program Files\...JAVA_HOME会解析失败。建议装到C:\jdk17。PATH里加%JAVA_HOME%\bin然后CMD执行java -version必须显示17.0.1。如果显示1.8.0_XXX说明系统有旧JDKPATH里旧路径在前删掉或调整顺序。验证javac -version确保编译器版本一致。Maven配置要点下载Maven 3.8.6SpringBoot 2.7兼容解压后配置MAVEN_HOMEPATH加%MAVEN_HOME%\bin。关键是settings.xml配置mirrors mirror idaliyun/id mirrorOfcentral/mirrorOf nameAliyun Maven/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors国内用中央仓库下载依赖太慢阿里云镜像提速10倍。IDEA导入项目必做三件事File → Project Structure → Project → SDK选JDK 17Language level选17。File → Settings → Build → Compiler → Java Compiler → Target bytecode version选17。Maven → Runner → JRE选JDK 17不是IDEA自带JRE。常见报错及解决Error:java: 不支持发行版本 17IDEA的Project SDK和Compiler设置不一致统一设为17。Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compilepom.xml里java.version17/java.version没配或父POM版本冲突。4.2 MySQL本地部署从安装到字符集调优Windows一键安装下载MySQL Installer选“Developer Default”全程下一步。设置root密码时不要用特殊字符如!#某些JDBC驱动解析失败。安装完用mysql -u root -p登录执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY yourpassword; FLUSH PRIVILEGES;字符集必须设为utf8mb4-- 查看当前字符集 SHOW VARIABLES LIKE character_set%; -- 修改my.iniWindows或my.cnfLinux [client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启MySQL后创建数据库时指定CREATE DATABASE course_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;为什么不是utf8因为MySQL的utf8实际是utf8mb3不支持emoji和部分生僻汉字如“”而utf8mb4才是真正的UTF-8。连接URL必须加参数spring.datasource.urljdbc:mysql://localhost:3306/course_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalseserverTimezoneAsia/Shanghai避免时区错误LocalDateTime存入数据库变NULL。allowPublicKeyRetrievaltrueMySQL 8.0新认证方式必需。useSSLfalse本地开发可关SSL省去证书配置。4.3 Redis本地启动从下载到SpringBoot集成Windows版Redis下载redis-x64-3.2.100.zip微软官方维护版解压后双击redis-server.exe启动。默认端口6379密码为空。测试redis-cli→set test hello→get test。SpringBoot集成!-- pom.xml -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency# application.yml spring: redis: host: localhost port: 6379 # password: # 本地无密码 lettuce: pool: max-active: 20 max-idle: 10 min-idle: 0 max-wait: 10000关键配置max-active20最大连接数毕设够用。生产环境按并发量调。max-wait10000获取连接超时10秒避免线程无限等待。RedisTemplate序列化陷阱默认RedisTemplate用JDK序列化存入Redis的是乱码字节。必须自定义Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // String序列化器 StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // JSON序列化器存对象用 GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); return template; }4.4 Linux服务器部署从jar包到systemd服务毕设演示不能只在本地跑要部署到云服务器如腾讯云轻量应用服务器。步骤1上传jar包scp target/course-system-1.0.jar useryour-server-ip:/home/user/步骤2创建systemd服务sudo vim /etc/systemd/system/course.service[Unit] DescriptionCourse Selection System Afternetwork.target [Service] Typesimple Useruser WorkingDirectory/home/user ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /home/user/course-system-1.0.jar Restartalways RestartSec10 EnvironmentSPRING_PROFILES_ACTIVEprod [Install] WantedBymulti-user.target-Xms512m -Xmx1024m堆内存初始512MB最大1024MB避免OOM。Restartalways进程崩溃自动重启。SPRING_PROFILES_ACTIVEprod激活生产配置。步骤3启动服务sudo systemctl daemon-reload sudo systemctl enable course.service sudo systemctl start course.service sudo systemctl status course.service # 查看运行状态步骤4配置Nginx反向代理可选但推荐server { listen 80; server_name your-domain.com; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这样访问http://your-domain.com就自动跳转到SpringBoot的8080端口更专业。5. 常见问题排查与独家避坑技巧5.1 启动失败ClassNotFoundException与NoClassDefFoundError的区别这两个错误常被混淆但原因完全不同ClassNotFoundException类在编译期存在但运行时找不到。比如你用了com.alibaba.fastjson.JSON但pom.xml里没加Fastjson依赖或者依赖scope是test。排查mvn dependency:tree | grep fastjson看是否引入。NoClassDefFoundError类在编译期和运行时都存在但初始化失败。比如static块里抛了异常导致类加载中断。排查看完整堆栈找到Caused by:后面的异常。常见于数据库驱动没加载DriverManager.registerDriver失败、Logback配置错误。毕设高频案例报java.lang.ClassNotFoundException: javax.servlet.FilterSpringBoot 2.0用Jakarta EEjavax.*包已废弃。解决方案升级spring-boot-starter-web到2.7或排除老Servlet API。报java.lang.NoClassDefFoundError: org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationSpringBoot版本与Spring Cloud不兼容。查官方版本对照表统一版本。5.2 数据库连接池爆满Druid监控实战选课高峰期HikariCP或Druid连接池耗尽页面打不开。这不是代码问题是配置问题。Druid监控配置spring: datasource: druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: falsemax-active20最大连接数按并发用户数预估。100并发一般设30-50。max-wait60000获取连接超时60秒避免线程饿死。启用Druid监控页面spring: datasource: druid: web-stat-filter: enabled: true stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin部署后访问http://your-server/druid能看到实时连接数、SQL执行时间、慢SQL列表。避坑技巧永远不要在循环里new Connectionfor (int i0; i100; i) { connection dataSource.getConnection(); }100个连接全占满。正确做法一个Connection处理所有SQL。MyBatis的foreach标签慎用insert里批量插入1000条会生成超长SQL触发MySQLmax_allowed_packet限制默认4MB。解决方案分批插入每批100条。5.3 Thymeleaf模板不生效缓存与路径的双重陷阱改了list.html刷新页面没变化。90%是以下两个原因陷阱1Thymeleaf缓存未关闭开发时必须关缓存否则改HTML不生效spring: thymeleaf: cache: false check-template-location: truecachefalse是关键生产环境才开true。**陷阱本文还有配套的精品资源点击获取