Java学生选课系统源码解析:从环境部署到答辩改造全指南
简介这是一份基于Java Swing与MySQL实现的学生选课系统项目源码包面向需要完成毕业设计或课程设计的计算机专业学生也适合正在学习Java桌面应用开发与数据库编程的初学者。资源包共包含168个文件主要有Java源文件、class字节码文件、界面设计form文件、png及jpg图片素材、jar依赖库、sql数据库脚本、doc/docx说明文档和avi操作演示视频压缩后总大小约58.87MB目录组织结构较清晰便于按模块查阅。项目实现了登录验证、课程查询与选课、退课管理、课程信息维护等典型业务功能配合文档和视频讲解可帮助读者快速掌握Swing界面布局、MySQL数据库连接配置以及增删改查操作的具体实现思路。目前该资源已被862人浏览学习参考价值较高适合以完整项目为范本进行二次开发或学习实战开发流程。1. 学生选课系统Java源码包拿到手先想清楚这三件事再动手很多同学从网上下载了学生选课系统的源码压缩包解压后第一件事就是导入集成开发环境结果大部分人的第一步就卡住了。不是代码有问题而是环境没配对。这个源码包实际包含的是一个完整的Java Web毕业设计项目后端业务代码、数据库建表脚本、配置文件、前端页面和简单文档目标是覆盖学生登录、浏览课程、选课退课、成绩查询这个完整闭环。它适合正在准备毕业设计、需要一个能跑通全流程的参考项目并且愿意花一两天时间把技术脉络理清的同学。这篇笔记会按技术栈拆解、本地部署、功能逻辑、常见排错、改造升级五个方向把从下载到答辩的路走一遍。2. 技术栈与架构拆解读懂选课系统源码先看这四层解压源码包之后第一反应通常是直接用集成开发环境打开看到一屏报错就开始慌张。这很正常。但我建议先花十分钟搞清楚这套代码长什么样再动手运行。这一章从技术选型、交互链路、数据表、目录结构四个角度看清楚一套学生选课系统的骨架。2.1 技术选型Spring Boot 与 SSM 两套方案的取舍打开源码包先找pom.xml文件。这个文件决定了整套代码的依赖、版本和生命周期。学生选课系统作为经典Java毕业设计课题市面上能拿到的源码绝大多数走在两条路线上。第一条是Spring Boot一条龙Spring Boot MyBatis Thymeleaf MySQL。特点是由spring-boot-starter-parent统一管理依赖版本内嵌Tomcat运行时不依赖外部容器打包后一个可执行jar包就能启动。对毕业生来说演示环境换到任何一台电脑都是同一套操作省去单独配置Tomcat的麻烦。第二条是SSM经典三层Spring SpringMVC MyBatis JSP MySQL。特点是结构传统JSP页面里可以直接写Java代码和JSTL标签很多高校的Java课程设计还在用这种模式。但部署时需要外置Tomcat并且JDK版本和Tomcat版本需要单独匹配出问题的概率明显更高。怎么快速判断手头源码是哪一套看两个地方pom.xml里如果没有spring-boot-starter-parent同时src/main/webapp/WEB-INF/目录下有web.xml文件就是SSM反过来有spring-boot-starter-parent、并且没有web.xml就是Spring Boot。我个人的建议是如果条件允许优先选Spring Boot 2.x JDK1.8或11 MySQL5.7的组合。这个组合的教材、博客、报错案例最多不管是你自己排查还是找别人帮忙覆盖率最高。Spring Boot 3.x需要JDK17起步不少毕设源码还是基于JDK8写的直接跑3.x大概率翻车。另外看pom.xml时顺手确认Lombok依赖。很多源码的实体类上标了Data注解IDE没装Lombok插件就一直编译报错看起来像代码残缺其实是插件缺失。这个坑几乎每个宿舍都能踩到。2.2 前后端交互链路一次选课请求经历了什么学生点击“选课”按钮后浏览器发起的是一个普通表单POST请求完整链路是浏览器Form表单 → 内嵌Tomcat接收 → DispatcherServlet分发 → CourseController接收参数 → CourseService执行选课逻辑 → CourseMapper映射SQL → MySQL返回结果 → 响应重定向或渲染视图这条链路里Controller层是入口Service层是核心Mapper层是数据出口。很多源码把业务逻辑写在Controller里这不是不能跑但答辩的时候老师问到“你的事务边界在哪”就会比较被动。看一个典型的Controller代码来自一套常规的学生选课系统Controller RequestMapping(/course) public class CourseController { Autowired private CourseService courseService; PostMapping(/select) public String selectCourse(Integer studentId, Integer courseId, Model model) { boolean success courseService.selectCourse(studentId, courseId); if (success) { return redirect:/course/myList?studentId studentId; } model.addAttribute(error, 选课失败请检查课程余量或是否已选); return course/select; } }逻辑说明Controller注解把类注册为SpringMVC控制器处理浏览器请求。RequestMapping(/course)定义了类级别的访问前缀所以这个方法对外路径是/course/select。PostMapping限定只接受POST请求防止直接用浏览器地址栏GET访问触发选课逻辑。selectCourse方法接收两个页面参数studentId和courseId方法入参名与表单字段名对应Spring会自动绑定。返回一个redirect选课成功后让浏览器重新发起GET请求跳到我的课程列表页避免刷新时重复提交表单。参数说明studentId和courseId在前端表单里用隐藏域或下拉框携带。如果页面里没有这两个参数Controller会直接收到null后续判断会全部失效。Model对象用于携带错误信息到视图页面展示适合选课失败时的提示场景。Thymeleaf模板的对应关系也要留意。Controller返回course/select时Spring Boot会去templates/course/目录下找select.html。如果文件不存在或者文件名大小写对不上启动不报错但访问页面会白屏。2.3 核心数据表设计学生、课程、选课记录三张表的依赖关系学生选课系统再多花哨功能数据库层面核心就是三张表。看懂这三张表的关系整个系统的业务就理解了一半。下面是一个常规的建表脚本字段命名和常见源码基本一致。CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学生ID, student_no VARCHAR(20) UNIQUE NOT NULL COMMENT 学号, password VARCHAR(64) NOT NULL COMMENT 登录密码, name VARCHAR(32) NOT NULL COMMENT 姓名, major VARCHAR(64) COMMENT 专业, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程ID, course_no VARCHAR(20) UNIQUE NOT NULL COMMENT 课程编号, name VARCHAR(64) NOT NULL COMMENT 课程名称, teacher VARCHAR(32) NOT NULL COMMENT 授课教师, credit INT DEFAULT 2 COMMENT 学分, capacity INT NOT NULL COMMENT 课程容量, selected_count INT DEFAULT 0 COMMENT 已选人数, schedule VARCHAR(64) COMMENT 上课时间如周一第1-2节, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course_selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) DEFAULT NULL COMMENT 成绩NULL表示未录入, selected_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_sel_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_sel_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明course_selection表是全系统最关键的一张表。一行数据代表“某个学生选了某门课”只有这张表里存在记录才算真正的选课成功。联合唯一索引uk_student_coursestudent_id, course_id在数据库层面挡住重复选课。有些源码没建这个约束导致程序漏判时数据里出现重复记录答辩时很容易被老师追问。外键约束保证学生和课程的完整性学生被删除时对应选课记录会受外键约束影响。实际源码里不少为了省事不建外键业务层自己维护一致性但这张表建议保留因为它是多对多关系的中间表。参数说明capacity表示课程容量selected_count表示已选人数这两个字段是实现“选满了不能选”的基础。score字段可空学生选课后还没有成绩时是NULL教师录入成绩后才更新。这个设计在源码里比较常见。上课时间schedule字段是字符串比如“周一第3-4节”如果要做“同一时间不能选两门课”的冲突校验靠字符串正则解析会很别扭。这个属于后面升级改造的点。三张表的关联关系可以这样理解student和course之间是多对多一个学生可以选多门课一门课可以被多个学生选course_selection就是它们之间的纽带表。2.4 项目目录结构源码包里每个文件夹是干什么的Spring Boot工程的目录结构有很强的约定。看清目录之后出现任何报错都能快速定位到对应层级。一套标准选课系统的目录长这样src/main/java/com/example/courseselect/ ├── controller/ # 接收请求、返回视图 │ ├── StudentController.java │ ├── CourseController.java │ └── LoginController.java ├── service/ # 业务逻辑层 │ ├── StudentService.java │ ├── CourseService.java │ └── impl/ ├── mapper/ # MyBatis接口层 │ ├── StudentMapper.java │ └── CourseMapper.java ├── entity/ # 实体类对应数据库表 │ ├── Student.java │ ├── Course.java │ └── CourseSelection.java ├── config/ # 配置类拦截器、跨域等 │ └── WebConfig.java └── CourseSelectApplication.java # 启动类 src/main/resources/ ├── mapper/ # MyBatis的XML文件 │ ├── StudentMapper.xml │ └── CourseMapper.xml ├── static/ # CSS、JS、图片等静态资源 ├── templates/ # Thymeleaf模板页面 │ ├── login.html │ ├── student/ │ └── course/ └── application.yml # 核心配置文件逻辑说明controller层只做参数接收和视图跳转不写业务逻辑。如果某个Controller里出现拼接SQL或者直接操作数据库的代码这个项目大概率结构混乱。service接口加impl实现类的写法在课程设计里经常被认为是有设计的体现。虽然简单项目不拆也行但答辩时讲得出“面向接口编程”能加分。mapper目录下同时存在接口和XML是MyBatis的常规用法。XML文件里写的是具体SQL接口里只写方法签名。templates目录下的login.html、student/index.html等对应Thymeleaf模板。页面文件名和Controller返回的视图名一一对应。实体类与数据库字段的映射也值得看一眼。如果实体类里是studentNo数据库字段是student_no需要在配置文件里开启map-underscore-to-camel-case否则查询结果里studentNo永远为null。这个配置在下一章的application.yml里会出现。3. 本地跑通学生选课系统从JDK到浏览器的完整部署步骤理解了骨架之后就可以动手部署。这一章按环境准备、数据库导入、配置修改、启动验证四步走。每一步说明命令含义和参数改法照着做基本能跑通。3.1 环境准备JDK、Maven、MySQL 的版本匹配环境匹配是最容易出问题的一步。很多同学拿到源码先在IDE里看到一堆飘红第一反应是代码有问题实际上九成是依赖环境不对。推荐环境组合JDK版本1.8或11。如果pom.xml里的parent版本是Spring Boot 2.xJDK8和11都能用如果代码用了比较新的语法优先JDK11。Maven3.6以上。Maven3.8和JDK8在某些配置下会有插件兼容警告但一般不影响构建。MySQL5.7或8.0。这两个版本在驱动、字符集和建表语法上有细微差别源码里如果是com.mysql.cj.jdbc.Driver适配8.0如果是com.mysql.jdbc.Driver适配5.7。IDE装上Lombok插件。实体类上的Data注解没有插件直接编译失败。打开命令行先验证环境java -version mvn -v mysql --version逻辑说明java -version输出包含版本号。如果显示的是17或21先别急后面会在IDEA里指定编译版本。mvn -v输出Maven版本和它使用的JDK版本。如果这里显示的JDK和你期望的不一致说明系统环境变量里的JDK和IDE里配置的不是同一个这是最常见的环境翻车点。mysql --version输出MySQL版本。记下这个版本号后面导入数据和匹配驱动时要用。参数说明如果系统中装了多个JDK建议在IDEA的Project Structure里明确指定Project SDK为1.8或11同时把Settings里的Maven Runner的JRE也切到同一个版本。两个地方不一致Maven编译就会报错。IDE里导入项目的具体操作File → New → Project from Existing Sources选中解压后的pom.xmlMaven会自动识别。导入后先执行一次mvn clean compile让Maven把依赖下载下来。国内网络环境下首次下载可能很慢或者卡在某个依赖上可以先配置阿里云镜像后面第5章会讲到。3.2 导入数据库建库脚本与初始数据的关键参数源码包的sql目录或根目录下一般有个以.sql结尾的脚本文件里面包含建库、建表和初始数据。不要直接在IDE里用可视化工具执行先打开脚本看里面的CREATE DATABASE语句确认数据库名。命令行导入方式mysql -u root -p --default-character-setutf8mb4 /path/to/course_select.sql逻辑说明--default-character-setutf8mb4指定客户端字符集防止脚本里的中文注释和初始数据在导入时变成乱码。 符号是Linux和Windows命令行都支持的重定向符把sql文件内容作为mysql命令的标准输入。执行后出现Enter Password提示输入MySQL的root密码即可。脚本如果没有报错数据库就建好了。参数说明-u root指定登录用户-p表示执行时提示输入密码。如果MySQL没有密码可以不写-p直接回车。如果脚本文件路径含空格在Linux或Mac下用反斜杠转义Windows下用双引号包住路径。导入完成后验证一下USE course_select; SHOW TABLES; SELECT COUNT(*) FROM student;如果student表返回10条以内的初始数据说明导入成功。很多同学在这一步卡住是因为脚本里包含的建库语句和本地已有数据库重名MySQL会报database exists拒绝执行。解决方式是先DROP DATABASE或者手动编辑脚本把库名改掉。3.3 修改配置文件数据源、端口、日志三个必改项Spring Boot项目的配置文件在src/main/resources/application.yml里。打开这个文件修改三个部分。server: port: 8080 servlet: context-path: /courseselect spring: datasource: url: jdbc:mysql://localhost:3306/course_select?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.courseselect.entity configuration: map-underscore-to-camel-case: true logging: level: com.example.courseselect.mapper: debug逻辑说明server.port是Tomcat启动端口。如果本地8080被占用改成8081或9090。spring.datasource.url里的几个参数都很关键useUnicodetrue和characterEncodingutf8解决中文写入乱码useSSLfalse避免MySQL8.0握手时SSL警告serverTimezoneAsia/Shanghai是MySQL8.0必加的参数不加会报时区错误。mybatis.map-underscore-to-camel-case的作用是把数据库字段student_no自动映射为Java属性studentNo不用手写大量resultMap。logging.level把mapper包日志调到debug可以在控制台看到MyBatis执行的具体SQL。选课功能不正常时这一步能直接看到SQL去哪儿了。参数说明password要和本机MySQL实际密码一致。如果本机MySQL密码为空直接写password:即可或者填root。有的源码把数据库账号密码写在properties文件里或者用${}占位符引用外部配置找不到的时候全局搜索jdbc:mysql就能定位。改完配置重启应用不用重新打包IDEA里重新Run一次即可。另外提一句application.yml和application.properties只是格式不同功能等价。如果你拿到的是properties后缀把上面内容按keyvalue格式改写就行比如spring.datasource.urljdbc:mysql://...。两种格式混在一起写进同一个配置文件会解析失败。3.4 启动与验证三条命令和一个冒烟测试环境就绪后启动应用。推荐先用命令行验证一次再回到IDE里用按钮启动这样能区分是代码问题还是IDE配置问题。cd /path/to/course-select mvn spring-boot:run看到类似Tomcat started on port(s): 8080的日志后打开浏览器http://localhost:8080/courseselect/login冒烟测试按这个顺序走一遍用初始账号密码登录。初始账号一般在sql脚本里比如admin/123456或student/123456注意脚本注释里有没有说明。进入课程列表页点选课确认列表中出现该课程。进入我的课程页找到那条记录点退课确认记录消失。用教师账号登录录入一个成绩再用学生账号查看确认成绩能看到。逻辑说明mvn spring-boot:run会先编译再启动第一次执行时Maven下载依赖耗时比较长属于正常现象。如果访问的是根路径/而不是/context-path会得到404。context-path配置了/courseselect访问路径就必须带上。参数说明如果确认端口被占用先查占用进程netstat -ano | findstr 8080Windows或lsof -i:8080Mac/Linux然后kill掉或者直接改端口。启动日志里如果有Error creating bean with name dataSource说明数据库连接配置有问题回到3.3节检查url、账号和密码。到这里系统已经在本地跑起来了。但跑起来只是第一步答辩前你还需要真正理解每个功能模块是怎么实现的。下一章拆开看登录、选课、成绩三个模块的代码路径。4. 登录、选课、成绩三个核心模块从代码层面看懂系统怎么跑系统能跑起来之后你得能跟老师讲清楚每个功能是怎么走通的。我把最容易被老师追问的三个模块拆开来讲登录与权限控制、选课与退课的事务处理、成绩录入与查询。这三个模块能讲清楚基本就能应对答辩。4.1 登录与权限控制拦截器里藏着最容易改错的地方选课系统的用户角色至少有两种学生和教师或者管理员。登录流程本身不复杂就是查表比对用户名密码难点在登录之后的权限控制。如果只用Session里存一个用户对象不做拦截任何页面都能通过URL直接访问这在答辩时会是个硬伤。常规做法是用Spring MVC的拦截器统一拦截未登录请求。以一个HandlerInterceptor实现为例Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login); return false; } request.setAttribute(loginUser, user); return true; } }然后在配置类里注册拦截规则Configuration public class WebConfig implements WebMvcConfigurer { Autowired private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /doLogin, /css/**, /js/**, /images/**); } }逻辑说明preHandle方法在请求进入Controller之前执行。返回false表示拦截请求不会到达业务代码。从Session里取loginUser取不到就重定向到登录页。这里有个细节重定向路径用request.getContextPath()拼接适配了context-path配置直接写/login会404。excludePathPatterns放行登录页、登录接口和静态资源。如果忘记放行/css/**页面样式会全部丢失看起来像HTML没渲染一样。参数说明addPathPatterns(/)表示拦截所有路径。如果只想保护学生和教师功能可以写/course/、/student/**等。权限细分场景如果学生和教师功能混在一个Controller里建议在preHandle里再从Session中的用户类型判断一次放行或拒绝。这个拦截器最大的坑是放行路径写错。有同学把/login写成/doLogin导致登录接口本身被拦截提交表单后马上被重定向回登录页形成死循环。现象很像是登录失败实际是拦截逻辑的问题。密码存储也是一个能讲两句的点。很多源码密码是明文存数据库的答辩时老师问起来比较尴尬。改进方向是使用BCryptPasswordEncoder对密码做单向哈希登录时把用户输入明文加密后再比对。改动量不大但属于安全层面的亮点。4.2 选课与退课事务边界与并发冲突处理选课的代码路径是学生提交课程ID系统判断课程是否还有余量、学生是否已经选过然后写入选课记录同时把course表里的selected_count加一。这三个动作必须在一个事务里否则可能出现数据不一致。常见源码里的事务写法Service public class CourseServiceImpl implements CourseService { Autowired private CourseMapper courseMapper; Autowired private CourseSelectionMapper selectionMapper; Transactional(rollbackFor Exception.class) public boolean selectCourse(Integer studentId, Integer courseId) { Course course courseMapper.selectById(courseId); if (course null || course.getSelectedCount() course.getCapacity()) { return false; } int count selectionMapper.countByStudentAndCourse(studentId, courseId); if (count 0) { return false; } selectionMapper.insert(studentId, courseId); courseMapper.increaseSelectedCount(courseId); return true; } }逻辑说明Transactional把整个方法包进一个数据库事务。任何一个操作抛异常前面的插入操作也会回滚不会出现“选课记录写了但余量没减”的脏数据。rollbackFor Exception.class很重要。默认情况下Spring只对RuntimeException回滚如果代码里catch了异常又抛出一个普通Exception事务不会回滚。写rollbackFor可以避免这个坑。先查余量再插入再更新余量这套顺序在并发场景下仍然有缝隙。两个学生同时选最后一门课可能都通过余量判断最后超卖。毕业设计不要求做分布式锁但至少应该用数据库唯一索引uk_student_course做兜底保证同一个人不能重复选。参数说明countByStudentAndCourse对应course_selection表里的联合唯一索引查询。如果Mapper里没有这个方法可以用自定义SQL或者直接基于唯一索引捕获DuplicateKeyException。increaseSelectedCount可以用一条原子SQL实现UPDATE course SET selected_count selected_count 1 WHERE id #{courseId}。不要先查再设置值会丢更新。退课逻辑和选课相反删除选课记录然后执行UPDATE course SET selected_count selected_count - 1。同样需要事务。很多源码退课后selected_count变成负数就是因为少了这个减法或者没放在一个事务里。处理并发超卖的另一种方式是乐观锁。在course表增加一个version字段更新时执行UPDATE course SET selected_count selected_count 1, version version 1 WHERE id #{courseId} AND version #{version}受影响行数为0说明有人抢先了一步。这个方案在答辩时讲出来老师会认为你确实考虑过并发问题。4.3 成绩录入与查询教师端与学生端的数据视角差异成绩是选课系统另一个高频功能点。教师登录后看到的是“某个学生选了某门课”可以录入成绩学生登录后看到的是“我选的课成绩是多少”。同一个数据两个视角。成绩录入最常见的问题是权限校验缺失。如果课程表里有teacher字段但Controller只接收courseId和studentId没有校验当前登录用户是不是这门课的授课教师学生就能通过拼接URL给任意课程录入成绩。常规的录成绩Controller核心代码PostMapping(/score) public String addScore(Integer selectionId, BigDecimal score) { CourseSelection selection selectionMapper.selectById(selectionId); if (selection null) { return error; } selection.setScore(score); selectionMapper.updateScore(selection); return redirect:/teacher/courseList; }逻辑说明selectionId是course_selection表的主键定位唯一一条选课记录。score入参用BigDecimal接收对应数据库DECIMAL(5,2)类型。用double接收小数会有精度问题比如49.5存进去变成49.499999。这里少了一个关键校验当前登录教师和这门课的teacher字段是否匹配。源代码通常直接在service里补齐但有些简化源码完全没有需要在改造时自己加。学生端查询成绩就是简单的按studentId联表查询course_selection和course组装成成绩列表。如果SQL里不加WHERE student_id 当前登录用户就会出现水平越权任何一个学生能看全所有学生的成绩。这个点也是答辩的高频追问。对应的学生端查询SQL大致长这样SELECT c.name AS course_name, c.credit, cs.score, c.teacher FROM course_selection cs LEFT JOIN course c ON cs.course_id c.id WHERE cs.student_id #{studentId}参数说明这里必须用LEFT JOIN而不是INNER JOIN。成绩未录入时course_selection表里依然有选课记录只有成绩字段是NULL。INNER JOIN不会丢数据但语义上LEFT JOIN更准确表达“查的是选课记录成绩可能为空”。#{studentId}是MyBatis的参数占位符能防止SQL注入如果源码里写成${studentId}拼接会存在注入风险属于需要主动改掉的点。成绩模块能跑通只是最基础的建议侧重关注权限校验和事务回滚这两处。5. 选课系统源码常见问题排查跑不起来、白屏、数据乱码的六个坑前面几章把部署和功能讲完了但实际操作里几乎不可能一次通过。这一章整理六个我在帮别人调这套系统时经常遇到的坑每条按现象、原因、解决三步写直接照着排查。5.1 数据库版本不兼容导致建表失败现象执行sql脚本时报错You have an error in your SQL syntax或者建表成功但插入初始数据时报错Unknown column。原因最常见的两种。第一种是脚本用了MySQL8.0的新语法比如CHECK约束、窗口函数但本地MySQL是5.7或更低。第二种是本地MySQL是8.0但脚本里用了旧版本的timestamp默认值写法导致sql_mode里的NO_ZERO_DATE策略拒绝执行。解决先确认本地MySQL版本。然后把脚本里针对旧版或新版的语法做替换。比如把DATETIME DEFAULT 0000-00-00 00:00:00改成可空的字段把ENGINEMyISAM改成InnoDB。如果实在不想改脚本就升级本地MySQL到8.0以上或者改用5.7同时确认驱动版本同步。这个坑最容易让新手误判为“源码有问题”实际上源码在作者的MySQL环境下跑得好好的只是环境之间的版本差异。5.2 JDK版本过高导致启动报错现象mvn spring-boot:run时编译阶段报错Unsupported class file major version或者启动时直接抛NoSuchMethodError。原因Spring Boot 2.x对JDK版本上限没有明确限制但依赖里的老版本字节码库在高版本JDK上不兼容。更常见的是Maven使用系统默认JDK比如17编译而项目指定的编译目标版本是1.8出现release version mismatch。解决在IDEA的Project Structure里把Project SDK切到1.8或11再把IDEA设置里的Maven Runner JRE切到同一版本。命令行启动的话先export JAVA_HOME指向JDK8的安装路径再执行mvn spring-boot:run。判断JDK版本最直接的方式是看pom.xml里的java.version标签。如果写的是1.8你的编译和运行环境都使用JDK8最稳如果写的是11或17就按它的要求来。5.3 端口占用导致应用启动失败现象日志显示Web server failed to start. Port 8080 was already in use。应用起不来或者起来后访问的页面是另一个项目的页面。原因本地某个进程占用了8080端口可能是上次运行残留的Java进程也可能是其他开发工具。解决Windows下执行netstat -ano | findstr 8080拿到占用进程的PID任务管理器结束进程Mac/Linux下执行lsof -i:8080。如果确认是残留Java进程直接kill -9 PID。不想杀进程就改application.yml里的server.port为8081访问时记得加端口。有个容易忽视的细节改端口后如果访问的还是旧端口浏览器可能命中缓存。强制刷新或换浏览器无痕窗口再试。5.4 中文乱码从请求到响应三层编码不一致现象页面显示的中文变成问号或者往数据库插入的中文变成乱码但英文正常。原因三层编码不一致。第一层是数据库连接URL没加characterEncodingutf8第二层是数据库表和字段的字符集不是utf8mb4第三层是IDE里文件编码不是UTF-8。解决按顺序检查。application.yml的url里加上useUnicodetruecharacterEncodingutf8数据库表执行ALTER TABLE xxx CONVERT TO CHARACTER SET utf8mb4;IDEA右下角把File Encoding设置为UTF-8。改完重启应用并重新导入数据乱码一般就没了。还有一种情况是Thymeleaf模板本身编码不对。HTML文件里如果没写 或者IDE把文件存成了GBK页面展示时就会乱。用IDEA打开页面文件右下角能看到当前文件编码统一改成UTF-8。5.5 选课逻辑缺陷重复选课、时间冲突未校验现象同一个学生能反复选同一门课课程列表里出现多条相同记录或者选了两门上时间冲突的课系统也不提醒。原因代码里没做重复选课判断或者数据库没建联合唯一索引上课时间用字符串存储没有做冲突解析。解决数据库给course_selection表加上UNIQUE KEY uk_student_course (student_id, course_id)代码里捕获DuplicateKeyException提示“已选”。时间冲突需要把schedule字段拆分成星期几和第几节两个字段或者分别存start_section和end_section选课前用时间区间查询判断。代码层面重复选课的判重逻辑应该在Service层完成而不是只靠前端按钮禁用。前端禁用只是体验优化后端判断才是真正的防线。5.6 Maven依赖下载失败与本地仓库缓存问题现象pom.xml依赖飘红编译报错Could not find artifact或者换了一个新依赖后依然提示找不到。原因首次下载依赖时网络中断Maven本地仓库里留下了一个.lastUpdated后缀的损坏文件之后每次构建都会跳过重新下载。解决删除本地仓库中对应的损坏目录一般路径是~/.m2/repository/org/springframework/。然后在Maven的settings.xml里配置阿里云镜像镜像地址把central指向https://maven.aliyun.com/repository/public。重新执行mvn clean compile等依赖完整下载。这个办法比反复删仓库重装IDEA有效得多。6. 把源码改造成“自己的项目”答辩前必做的三个升级方向很多同学拿到源码、跑通、读懂然后直接交上去结果查重或答辩时被问得说不出话。源码只是起点改造才是让它变成“自己的毕业设计”的关键。第一个升级方向把选课时间窗口和冲突校验做成硬功能。原有逻辑只要课程有余量就能选这个太简单。把course表挂上选课开始时间、结束时间选课前判断当前时间是否在窗口内把上课时间的字符串拆成星期、开始节次、结束节次字段选课前查询同一学生是否存在时间重叠。这两个功能在答辩里属于业务完整性亮点。第二个升级方向把Controller里的业务代码抽到Service层并加上自定义异常。原有代码可能在Controller里直接写事务操作或者在Service里返回false。改成抛出统一的BusinessException用RestControllerAdvice做全局异常处理前端页面通过error消息展示。老师的典型追问是事务边界在哪、异常怎么处理这一步提前堵住。第三个升级方向如果还有时间把前端从服务端渲染改成前后端分离。保留Spring Boot作为纯API接口层返回JSON前端用Vue3加Element Plus重新写页面。这个改动量不小但收益也明显系统整体结构清晰面试时也拿得出手。注意跨域配置和登录态改为Token机制改造前先确认还有多少时间别把答辩前两周全砸在这上面。我做毕业设计那阵就是拿到一套选课系统源码之后只顾着让它跑起来最后被问异常安全怎么保证时只能回答用try catch场面一度很尴尬。后来我把所有Controller瘦身、把事务边界画清楚、把重复选课的兜底加上才真正弄明白这类系统的运转逻辑。希望这一步的经验能帮到你也希望你的毕设答辩顺利。本文还有配套的精品资源点击获取