
简介这是一套面向Java Web初学者与课程设计开发者的学生选课管理系统完整项目资料围绕高校教学管理场景解决教师发布课程、统计选课与学生自主选课、退课、成绩查询等实际需求。资源包共5个文件包含2个zip源码与项目压缩包、2个mp4部署与功能演示视频、1个sql数据库脚本整体约208.74MB覆盖从代码到数据库再到部署上线的完整链路。项目采用Java语言结合Spring Boot、MySQL与前端动态交互技术按表现层、业务逻辑层、数据访问层三层架构组织涉及登录注册、课程管理、并发选课冲突处理、退课、成绩管理与角色权限控制等核心模块。已有152人学习适合作为毕业设计、课程设计或Java Web入门练手参考借助源码、建库脚本与部署录屏读者可快速还原运行环境理解实际项目的开发流程与技术应用并在此基础上进行二次开发与功能扩展。1. 从一份 zip 说起学生选课管理系统到底该长成什么样每年毕业季计算机专业的课程设计题目里「基于 Java 的学生选课管理系统」出现的频率高得离谱。但真正动手做过的人都知道这东西远不是「增删改查」四个字能打发的。一份典型的交付物是一个 zip 包里面塞着源代码、数据库脚本、部署视频看起来齐全可拿到手的人往往卡在第一步数据库连不上、Tomcat 起不来、选课冲突逻辑写反了。我见过太多人把源码导入 IDE 之后对着满屏红色报错发呆也见过部署视频里作者操作流畅、自己照着做却处处翻车的情况。这篇笔记不讲空话就围绕这个标题拆开讲一个能跑起来、能答辩、逻辑站得住的学生选课管理系统它的数据库该怎么设计Java 后端该怎么分层选课冲突和容量控制这些核心逻辑该怎么写部署时又有哪些血泪坑。适合正在做课程设计的学生也适合想拿一个完整项目练手 Java Web 的初学者。读完你至少能自己搭出一套可演示、可扩展的系统而不是对着别人的源码改个名字交差。2. 数据库设计选课系统的地基怎么打2.1 五张核心表与字段取舍学生选课管理系统的数据库设计说复杂也复杂说简单也简单。核心就五张表学生表、教师表、课程表、选课记录表、用户表。但每张表的字段怎么定直接决定了后面代码好不好写。先看学生表。很多教程里学生表就一个 id、name、password这在实际选课场景里是不够的。你至少需要学号student_no作为业务主键、姓名、性别、班级、专业、入学年份。学号要加唯一索引因为选课记录关联的是学号而不是自增 id这样数据导出和核对的时候不容易乱。课程表是重点。课程编号、课程名称、学分、授课教师、上课时间、上课地点、容量capacity、已选人数selected_count、课程状态。这里有个关键决策已选人数是实时 count 选课记录算出来还是在课程表里冗余一个字段我一般会冗余一个 selected_count 字段因为选课高峰期频繁 count 选课记录表性能很差而且并发选课时需要靠这个字段做乐观锁或悲观锁。代价是每次选课/退课都要同步更新但这个代价值得。选课记录表是关联表字段包括记录 id、学号、课程编号、选课时间、状态已选/已退。状态字段很重要退课不要物理删除而是改状态这样能保留操作痕迹答辩时老师问起来也有话说。用户表单独拆出来存登录账号、密码、角色学生/教师/管理员。角色字段用枚举或字典表都行但不要用魔法数字否则三个月后你自己都忘了 1 代表什么。CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) NOT NULL DEFAULT 0 COMMENT 学分, teacher_id INT NOT NULL COMMENT 授课教师ID, class_time VARCHAR(50) COMMENT 上课时间, class_place VARCHAR(50) COMMENT 上课地点, capacity INT NOT NULL DEFAULT 50 COMMENT 容量上限, selected_count INT NOT NULL DEFAULT 0 COMMENT 已选人数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1开放 0关闭, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_teacher (teacher_id), INDEX idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程表;这段建表语句里selected_count冗余字段和status状态字段是后面并发控制的基础。idx_status索引用于筛选开放状态的课程选课页面查询时走索引比全表扫快得多。字符集用 utf8mb4 而不是 utf8因为课程名称里可能出现生僻字或特殊符号utf8 存不进去会直接报错这个坑我踩过。2.2 选课记录表与唯一约束选课记录表的设计有一个容易被忽略的点同一个学生不能重复选同一门课。这个约束最好在数据库层面就加上而不是只靠 Java 代码判断。因为并发场景下两个请求同时查「有没有选过」都查到没有然后都插入就出现重复记录了。CREATE TABLE course_selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL COMMENT 学号, course_no VARCHAR(20) NOT NULL COMMENT 课程编号, select_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, status TINYINT NOT NULL DEFAULT 1 COMMENT 1已选 0已退, UNIQUE KEY uk_student_course (student_no, course_no), INDEX idx_student (student_no), INDEX idx_course (course_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课记录表;uk_student_course这个唯一索引是关键。注意它约束的是 student_no 和 course_no 的组合不是单独某一列。有了它即使 Java 代码判断失误数据库也会拒绝重复插入抛 DuplicateKeyException你在代码里捕获这个异常就能给用户返回「您已选过该课程」。这比先查后插可靠得多。但这里有个细节如果学生退课再选同一门课唯一索引会冲突。所以退课不要删记录而是把 status 改成 0重新选课时先 update 状态而不是 insert。这个逻辑后面代码部分会展开。2.3 教师表与用户表的关联方式教师表和用户表的关系常见做法有两种一种是教师表里直接存登录账号密码另一种是教师表只存业务信息登录走统一的用户表。我推荐后者因为管理员、学生、教师三种角色登录入口可能不同但认证逻辑可以统一。用户表里存 user_id、username、password、role教师表里存 teacher_id、teacher_no、name、title、user_id外键关联用户表。这样设计的好处是将来加一个「教务秘书」角色只需要在用户表加一条记录不用动教师表结构。密码存储用 BCrypt 哈希不要明文也不要 MD5。MD5 已经被证明不安全而且很多学校答辩时会问密码加密方式说 MD5 会被扣分。BCrypt 在 Java 里用BCryptPasswordEncoder就行Spring Security 自带不引入额外依赖。3. Java 后端分层从 Controller 到 Mapper 的落地写法3.1 项目结构与依赖选型一个能跑的学生选课管理系统后端用 Spring Boot MyBatis 是最稳的组合。Spring Boot 版本选 2.7.x 或 3.x 都行但要注意 3.x 要求 Java 17如果你的环境还是 Java 8就老老实实用 2.7。MyBatis 比 JPA 更适合课程设计因为 SQL 可控答辩时老师让你解释某条查询怎么写的你能直接指出 XML 里的语句用 JPA 自动生成 SQL 反而说不清。项目结构按标准分层controller、service、service.impl、mapper、entity、dto、vo、config、util。不要把所有类堆在一个包下答辩时老师看结构就知道你有没有工程意识。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency /dependenciesMyBatis starter 的版本要和 Spring Boot 版本匹配2.3.x 对应 Spring Boot 2.7。MySQL 驱动 8.0.x 对应 MySQL 8如果你学校机房还是 MySQL 5.7驱动要换成 5.1.x否则连接会报时区错误。这个版本对应关系是新手最容易翻车的地方部署视频里作者用的是什么版本你最好先确认自己的环境一致。3.2 选课核心逻辑并发下的容量控制选课系统最核心也最容易出问题的就是容量控制。假设一门课容量 50 人当前已选 49 人两个学生同时点选课如果代码写成先查 selected_count 再判断再更新两个请求都查到 49都判断小于 50都执行更新结果变成 51 人超员了。正确的做法是用数据库的行锁或乐观锁。我一般用乐观锁在 course 表加一个 version 字段更新时带上版本号条件。Service public class SelectionServiceImpl implements SelectionService { Autowired private CourseMapper courseMapper; Autowired private SelectionMapper selectionMapper; Override Transactional(rollbackFor Exception.class) public String selectCourse(String studentNo, String courseNo) { // 1. 先查课程当前状态 Course course courseMapper.findByCourseNo(courseNo); if (course null || course.getStatus() ! 1) { return 课程不存在或已关闭; } // 2. 乐观锁更新已选人数version 作为条件 int updated courseMapper.increaseSelectedCount(courseNo, course.getVersion()); if (updated 0) { return 选课人数已满或系统繁忙请重试; } // 3. 插入选课记录唯一索引兜底防重复 try { selectionMapper.insert(studentNo, courseNo); } catch (DuplicateKeyException e) { // 重复选课回滚人数增加 throw new RuntimeException(您已选过该课程); } return 选课成功; } }increaseSelectedCount对应的 SQL 是UPDATE course SET selected_count selected_count 1, version version 1 WHERE course_no #{courseNo} AND version #{version} AND selected_count capacity。注意 WHERE 条件里带了selected_count capacity这样即使版本号碰巧一致容量满了也更新不了。Transactional保证人数更新和记录插入在同一个事务里任何一步失败都回滚。捕获DuplicateKeyException是因为唯一索引会拦截重复选课这时候要抛异常触发回滚否则人数加了但记录没插进去数据就不一致了。3.3 退课与课程查询的接口实现退课逻辑比选课简单但有个坑退课要把选课记录 status 改成 0同时课程 selected_count 减 1。这两步也必须在一个事务里。而且减 1 的时候要判断 selected_count 大于 0否则可能出现负数。Override Transactional(rollbackFor Exception.class) public String dropCourse(String studentNo, String courseNo) { // 1. 查询选课记录确认状态为已选 Selection record selectionMapper.findByStudentAndCourse(studentNo, courseNo); if (record null || record.getStatus() ! 1) { return 您未选该课程或已退课; } // 2. 更新选课记录状态为已退 selectionMapper.updateStatus(record.getId(), 0); // 3. 课程已选人数减一带大于0条件 courseMapper.decreaseSelectedCount(courseNo); return 退课成功; }decreaseSelectedCount的 SQL 是UPDATE course SET selected_count selected_count - 1, version version 1 WHERE course_no #{courseNo} AND selected_count 0。selected_count 0这个条件防止减成负数。退课不删记录而是改状态这样学生重新选同一门课时唯一索引不会冲突但要注意重新选课时应该 update 那条 status0 的记录为 1而不是 insert 新记录否则唯一索引会报错。课程查询接口一般分两种学生查可选课程列表带已选人数、剩余容量教师查自己教的课程。学生查询要过滤 status1 且 selected_count capacity 的课程或者显示全部但标记已满。我一般显示全部开放课程前端根据剩余容量置灰按钮这样学生能看到哪些课满了体验更好。4. 部署与联调从本地跑通到答辩演示4.1 数据库脚本导入与连接配置拿到 zip 包后第一步是导入数据库脚本。脚本通常是 .sql 文件用 Navicat 或命令行mysql -u root -p init.sql导入。导入前先建库字符集选 utf8mb4排序规则选 utf8mb4_general_ci。如果脚本里没有 CREATE DATABASE 语句你要手动建否则导入会报「Unknown database」。连接配置在 application.yml 里重点看 url、username、password 三项。url 里要带serverTimezoneAsia/Shanghai否则 MySQL 8 会报时区错误。useSSLfalse也建议加上本地开发不需要 SSL不加会有警告。spring: datasource: url: jdbc:mysql://localhost:3306/course_selection?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driverdriver-class-name在 MySQL 8 里是com.mysql.cj.jdbc.Driver不是老的com.mysql.jdbc.Driver。写错了会报驱动加载失败。密码不要用纯数字有些环境会解析成数字类型导致认证失败加个字母最稳。4.2 前后端联调与跨域处理如果前端是单独部署的 Vue 或 React 项目联调时会遇到跨域问题。后端加一个 CORS 配置类就行不要在每个 Controller 上加CrossOrigin那样重复且容易漏。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }allowedOriginPatterns(*)和allowedOrigins(*)的区别在于前者允许携带 cookie 时使用通配符后者不允许。如果你的登录用 session 而不是 token就必须用allowedOriginPatterns否则跨域请求带不上 cookie登录状态丢失。这个坑我在三个项目里都遇到过每次都要查一遍。4.3 部署视频没告诉你的环境差异部署视频里作者往往用的是自己电脑上的环境JDK 版本、Maven 版本、Tomcat 版本都和你不一样。最常见的翻车是 JDK 版本不匹配源码用 Java 8 编译你环境是 Java 17启动时报Unsupported class file major version。解决办法是看 pom.xml 里的java.version标签把本地 JDK 换成对应版本或者改 pom 里的版本号重新编译。另一个坑是 Maven 依赖下载失败。国内网络环境下中央仓库经常超时。在 settings.xml 里配阿里云镜像速度会快很多。如果公司或学校有内网仓库优先用内网的。依赖下载不全时IDEA 右侧 Maven 面板点刷新看控制台哪个 jar 包报红单独去仓库下载放进本地仓库对应目录。5. 避坑与排查那些让答辩翻车的细节5.1 选课人数超限但数据库没报错现象压测时发现某门课 selected_count 超过了 capacity但程序没抛异常。原因更新语句的 WHERE 条件里漏了selected_count capacity只靠 Java 代码判断并发下判断失效。解决把容量判断下沉到 SQL 的 WHERE 条件里让数据库来保证不超限Java 层只负责处理更新失败的情况。5.2 退课后重新选课报唯一约束冲突现象学生退了一门课再选同一门课后台报 DuplicateKeyException。原因退课只是把 status 改成 0记录还在重新选课时执行的是 insert触发唯一索引。解决重新选课前先查有没有 status0 的记录有就 update 状态为 1没有才 insert。或者用INSERT ... ON DUPLICATE KEY UPDATE语句但 MyBatis 里写起来麻烦不如先查后判断。5.3 中文课程名存入数据库变成问号现象课程名「高等数学」存进数据库变成「????」。原因数据库连接 url 没加characterEncodingutf8或者数据库、表的字符集不是 utf8mb4。解决url 加useUnicodetruecharacterEncodingutf8建库建表都用 utf8mb4连接驱动用 8.0.x。三个地方都对了才不会乱码。5.4 部署到服务器后登录 session 丢失现象本地登录正常部署到服务器后登录成功但下一个请求就提示未登录。原因跨域配置里allowCredentials(true)和allowedOrigins(*)冲突浏览器拒绝携带 cookie。解决改用allowedOriginPatterns(*)或者把前端域名写死。另外检查前端请求有没有设置withCredentials: trueaxios 默认是不带的。5.5 答辩演示时数据库连接池耗尽现象演示时多人同时点选课系统卡死日志报Connection is not available。原因默认连接池最大连接数太小或者代码里有连接没关闭。解决在 application.yml 里配 HikariCP 的maximum-pool-size一般设 20 够用。更重要的是检查 MyBatis 的 SqlSession 有没有正确关闭用 Spring 管理的话一般不会泄漏但手动获取 Connection 的代码要加 finally 关闭。6. 进阶技巧把课程设计变成能写进简历的项目如果你想让这个学生选课管理系统不只是交个作业而是能写进简历、面试时聊得起来有几个方向可以加。第一个是加 Redis 缓存课程列表。选课页面是读多写少的场景每次查数据库压力大把开放课程列表缓存到 Redis设置 5 分钟过期选课/退课时主动删缓存。这样面试时你能聊缓存穿透、缓存雪崩怎么处理。第二个是加选课时间窗口控制。不是所有课程任何时候都能选可以给课程加一个选课开始时间和结束时间到点自动开放或关闭。用 Spring 的Scheduled定时任务扫描课程状态或者直接在查询时判断当前时间是否在窗口内。这个功能不复杂但能体现你对业务的理解。第三个是操作日志。每次选课、退课都记一条日志包括操作人、时间、IP、结果。用 AOP 切面统一处理不侵入业务代码。答辩时老师问「怎么追溯谁改了数据」你把这个日志表一亮比说「我没做」强得多。Aspect Component public class LogAspect { Autowired private LogMapper logMapper; Around(annotation(com.example.annotation.OperationLog)) public Object around(ProceedingJoinPoint point) throws Throwable { long start System.currentTimeMillis(); Object result point.proceed(); // 记录方法名、参数、耗时、结果 String method point.getSignature().getName(); Object[] args point.getArgs(); logMapper.insert(method, Arrays.toString(args), System.currentTimeMillis() - start); return result; } }这个切面配合自定义注解OperationLog使用在需要记录的方法上加注解即可。参数用Arrays.toString简单拼接生产环境建议用 JSON 序列化但课程设计够用了。日志表数据量大了要定期清理加个定时任务删 30 天前的记录。我自己做这类系统最大的教训是不要一上来就写代码。先把数据库表画清楚把选课、退课、查询三个核心流程的时序想明白再动手。否则写到一半发现表结构不对改起来牵一发动全身。另外部署视频可以看但不要照抄环境配置一定要先确认自己机器的 JDK、MySQL、Maven 版本版本对不上视频里再流畅的操作到你这里都是报错。希望帮到你。本文还有配套的精品资源点击获取