☰
MySQL+Java实现学生选课系统:从ER图到JDBC实战全解析
2026/10/13 1:52:52 网站建设 项目流程

简介:面向数据库课程设计场景的完整实践资源,提供基于MySQL与Java的学生选课信息管理系统源代码及配套设计报告。系统采用C/S架构,围绕学生、教师、管理员三类用户构建权限分明的业务模块:学生端具备个人信息维护、课程查询、选课退课、成绩查询、成绩单打印与奖惩信息查看;教师端支持所授课程信息查看、选课名单获取及成绩录入;管理员端覆盖学生、教师、课程、选课、成绩、院系设置、奖惩信息及操作审计等集中管理功能。数据库表设计合理规范,代码结构清晰,附带详细设计报告,便于对照理解从需求分析到表关系设计再到功能实现的完整流程。压缩包整体约1.99MB,以Java源文件、SQL脚本及设计文档为主,可直接用于课程设计提交或作为原型改造基础。目前已有6064人学习/下载,能帮助高校计算机相关专业学生高效完成选题、设计与文档撰写,也适合开发者快速掌握教务类管理系统的分层实现思路。

1. 学生选课信息管理系统:数据库课程设计为什么绕不开 MySQL+Java 这条路

每个学期的数据库课程设计,题库里最不缺的就是学生选课信息管理系统:学生、课程、选课记录三张表,配上登录、选课、退课、查成绩这些动作。系统看着简单,老师反而更抠细节——外键怎么设、并发选课怎么办、设计报告的数据字典全不全,全是扣分点。

这篇笔记聊的是最成熟的那条路线:MySQL 建库建表,Java 用 JDBC 写业务逻辑,设计报告按规范组织。适合正在做课设、需要参考源代码,或者想搞懂每一行代码为什么这么写的人。

按这条路线走完,你手里不只是一个能跑的系统,而是一套能对着老师讲清楚的设计思路。

2. 先把 ER 图画对再建表:选课系统的三张核心表与 MySQL 约束设计

不少同学拿到题目直接开写 CREATE TABLE,结果表建完才发现多对多关系落不了地,或者选课记录根本没法防重复。数据库课程设计的第一步永远是画 ER 图,不是写代码。ER 图画清楚,关系模式转换就有了依据,设计报告里也自然有了一整章能写的内容。选课系统虽然只有三个实体,但画图、建表的顺序错一步,后面全得返工。

2.1 实体拆解:学生、课程、选课记录之间的多对多关系

学生和课程是典型的多对多:一个学生可以选多门课,一门课可以被多个学生选。关系型数据库不能直接在两张表之间表达多对多,标准做法是引入一张中间表,把多对多拆成两个一对多——学生到选课记录是一对多,课程到选课记录也是一对多。这张中间表就是选课记录,同时持有学生主键和课程主键作为外键,还带着成绩、选课时间这两个属于「选课」联系本身的属性。

ER 图里要体现的实体和属性先列清楚:学生实体有学号、姓名、密码、专业、入学年份;课程实体有课程号、课程名、教师、学分、容量;「选课」这个联系有成绩、选课时间两个属性。这些属性最终都会落到 selection 表里,所以选课记录表不是凭空加了 score 和 select_time 两列,而是 ER 图到关系模式的直接映射。

中间表的主键设计有两种常见方案。方案一是直接用 (student_id, course_id) 联合主键,方案二是加一列自增 id 做主键、再给两端加联合唯一索引。课设里我一般选方案二:自增 id 的价值在扩展性,后面如果要写补考记录、重修记录,单一业务主键不够灵活;联合唯一索引则保证同一个学生选同一门课只可能有一条记录,这是防重复的第一道闸。方案一在纯选课场景够用,但如果你在报告里写了扩展需求,方案二更好圆。

学生表和课程表的主键直接用学号、课程号这类业务主键,VARCHAR 类型省去关联映射的麻烦。要注意主键列不能可空、不能随意 UPDATE,因为选课记录的外键都指着它。

2.2 建表 SQL 落地:外键、联合唯一约束与 utf8mb4 字符集

ER 图确认后,落库顺理成章。下面的建表脚本是这条路线最常见的一套,可以直接抄,但每个参数都要知道为什么。

CREATE DATABASE course_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE course_selection; CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, student_name VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, major VARCHAR(50), enroll_year INT ); CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY, course_name VARCHAR(100) NOT NULL, teacher_name VARCHAR(50), credit DECIMAL(3,1) NOT NULL DEFAULT 0.0, capacity INT NOT NULL DEFAULT 50, selected_count INT NOT NULL DEFAULT 0 ); CREATE TABLE selection ( id INT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20) NOT NULL, course_id VARCHAR(20) NOT NULL, score DECIMAL(5,2), select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_sel_student FOREIGN KEY (student_id) REFERENCES student(student_id), CONSTRAINT fk_sel_course FOREIGN KEY (course_id) REFERENCES course(course_id) );

字符集选 utf8mb4 而不是 utf8,这是最容易忽视的坑:MySQL 的 utf8 最多存 3 个字节,emoji 和部分生僻字会存不进去,utf8mb4 才是完整的 UTF-8 编码,课程名里万一出现特殊字符也不会翻车。collation 用 general_ci 够课试用,不区分大小写,排序规则也简单。

course 表的 capacity 和 selected_count 是一对配套字段:capacity 是课程容量,selected_count 是当前已选人数。选课逻辑里靠这对字段做容量判断,第 3.2 节的事务代码会用到。这里有个设计细节:更新人数时用 UPDATE 自增而不是「先 SELECT 旧值再 +1」,能减少并发下的脏数据。selection 表的 UNIQUE KEY 防重复选课,外键约束保证不会出现指向不存在学生或课程的记录——数据完整性交给数据库兜底,Java 代码只负责业务判断。

score 和 credit 都用 DECIMAL 而不是 FLOAT,因为成绩、学分是精确数值,FLOAT 是浮点近似,0.1 这种小数在浮点运算里会积累误差,成绩场景不可接受。DECIMAL(5,2) 表示最多 5 位数字、小数点后 2 位,成绩够用;DECIMAL(3,1) 最多 3 位、1 位小数,学分够用。这两个小选择,老师检查表结构时经常专门盯。

建表顺序也有讲究:先建 student 和 course,再建 selection,外键引用的父表必须提前存在,反了直接报错。如果中途要改表结构,用 ALTER TABLE 而不是删了重建——父表被删时子表的外键会拦着,这就是第 5 章要展开的翻车现场之一。

表结构定稿前,再用三范式快速过一遍:student 表所有字段都不可再分、都完全依赖主键学号、没有传递依赖,满足 3NF;course 表同理;selection 表的成绩依赖的是(学生,课程)这个组合关系,在自增主键方案下也满足 3NF。这一小节在报告里 200 字就能写完,但能体现你有规范化意识,是「设计合理性」评分项的实打实证据。

提示:建表脚本建议单独存成 init.sql 随源码交付,报告里附一段执行说明。老师拿到项目能一键重建数据库,这个印象分很值。

3. 用 Java JDBC 跑通选课业务:连接参数、PreparedStatement 与事务边界

建表只是把地基打好,业务逻辑才是课设的躯干。Java 连 MySQL 最常用的路线是 JDBC,先写一个工具类统一管理连接,再在各功能模块里调用。这一章给出最小可跑的代码骨架,并讲清楚每个参数为什么这么设、每个写法踩过什么坑。

3.1 JDBC 连接工具类:驱动版本、URL 参数与连接池选型

先看连接工具类,这是全套代码的地基,也是配置感最重的一个文件:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 连接串:协议 + 主机端口 + 库名 + 四个关键参数 private static final String URL = "jdbc:mysql://localhost:3306/course_selection" + "?useSSL=false" + "&serverTimezone=Asia/Shanghai" + "&characterEncoding=utf8" + "&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { // MySQL 8.x 用 com.mysql.cj.jdbc.Driver,5.x 用 com.mysql.jdbc.Driver Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

连接串里四个参数,每一个都是踩坑踩出来的。useSSL=false 关掉 SSL 握手,课设是本地开发环境,开着 SSL 只会增加报错面;serverTimezone=Asia/Shanghai 是 MySQL 8.0 的硬要求,不设会报 Server returns invalid timezone;characterEncoding=utf8 保证中文在传输层不乱码;allowPublicKeyRetrieval=true 对应 8.0 的 caching_sha2_password 认证插件,不开会报 Public Key Retrieval is not allowed。这四行缺一个,运行时报错都是玄学级别的,排查半天还不知道问题出在配置还是代码。

驱动版本必须和 MySQL 版本匹配:5.7 配 5.x 驱动,8.0 配 8.x 驱动。最常见的翻车是把 5.x 的 jar 扔给 8.0,报 ClassNotFoundException,或者连上后部分语法不兼容。下载驱动时别只看文件名,确认 jar 包里的实现类路径。

课设这种单机规模,DriverManager 直接拿连接完全够用,不一定上连接池。如果想让报告好看一点,可以引入 HikariCP,它是当前 Java 生态最常见的连接池,初始化代码不到二十行,答辩时能讲出「连接复用、减少创建开销」这套话术。但注意别为了用而用,单人本地跑的课设,连接池在性能上没有任何可感知的差别。

3.2 选课退课查成绩:PreparedStatement 与事务的完整写法

选课是整套系统最核心的业务,代码里藏着三个要点:PreparedStatement 防注入、事务保证原子性、容量检查防超选。直接看选课方法的骨架:

public boolean selectCourse(String studentId, String courseId) { String sqlCheck = "SELECT capacity, selected_count FROM course WHERE course_id = ?"; String sqlInsert = "INSERT INTO selection(student_id, course_id) VALUES (?, ?)"; String sqlUpdate = "UPDATE course SET selected_count = selected_count + 1 WHERE course_id = ?"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 PreparedStatement ps1 = conn.prepareStatement(sqlCheck); ps1.setString(1, courseId); ResultSet rs = ps1.executeQuery(); if (!rs.next()) return false; // 课程不存在 if (rs.getInt("selected_count") >= rs.getInt("capacity")) { return false; // 课程已满 } PreparedStatement ps2 = conn.prepareStatement(sqlInsert); ps2.setString(1, studentId); ps2.setString(2, courseId); ps2.executeUpdate(); PreparedStatement ps3 = conn.prepareStatement(sqlUpdate); ps3.setString(1, courseId); ps3.executeUpdate(); conn.commit(); // 提交事务 return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { // 释放资源:先关闭 ResultSet、PreparedStatement,最后关闭 Connection } }

三个要点逐一说。第一,所有 SQL 都用 PreparedStatement 的问号占位符绑定参数,而不是字符串拼接——这是防 SQL 注入的标准姿势,登录框、学号输入框都是注入重灾区,这个细节能直接写进报告的安全设计章节。第二,setAutoCommit(false) 之后,插入选课记录和更新已选人数两个操作要么一起成功、要么一起回滚,这就是事务的原子性。如果先插入记录、再更新人数时程序崩了,会出现选课表有条目但 course 表人数没变的脏数据,成绩统计全乱。第三,容量检查本身有竞态窗口:两个请求同时通过「未满」检查,就会双双插入成功,造成超选。严格做法是对 course 行加锁或把 UPDATE 写成条件更新(WHERE selected_count < capacity),课设答辩能被问到并发问题,能讲清楚这个窗口和解决思路,已经超出平均分了。

退课是选课的逆操作:删除 selection 记录,同时把 course 表 selected_count 减一,同样放进事务里。这里注意先删子表再减数,逻辑顺序和选课相反,但事务保证它们仍然是一体的。

查课程列表时,排序直接在 SQL 里做:

SELECT course_id, course_name, credit, capacity, selected_count FROM course ORDER BY credit DESC, course_id ASC;

ORDER BY 让 MySQL 自己排序,数据量小的课设毫无压力。别把数据全部拉到 Java 里再自己写冒泡排序——那是面试题考点,不是生产代码该干的事。到这里,增删改查的核心链路已经齐全:增加学生、课程是 INSERT,查询是 SELECT,退课是 DELETE,改成绩是 UPDATE。

4. 设计报告别写成代码粘贴板:数据字典、关系模式与测试用例的组织方法

源码能跑只是及格,设计报告才是拉分的部分。课设报告最怕两种写法:一种是通篇贴代码,另一种是照抄教材概念。真正有用的报告是把「怎么设计、为什么这么设计、怎么验证」三件事讲清楚。这一章给出报告核心章节的组织方式,照着搭框架,再填你自己的细节。

4.1 数据字典:老师最爱逐行检查的字段表怎么填

数据字典是整个报告里最容易做、也最容易被挑错的部分。每张表都要有一份字段级说明,格式统一,类型和长度必须和实际建表 SQL 完全一致。下面是 student 表的数据字典示例,照这个格式把三张表都写全:

字段名数据类型长度允许空键/约束默认值说明
student_idVARCHAR20否主键无学号,业务主键
student_nameVARCHAR50否无无学生姓名
passwordVARCHAR64否无无登录密码
majorVARCHAR50是无无专业
enroll_yearINT4是无无入学年份

写数据字典最常翻车的有三个点:字段类型和 SQL 对不上,比如 SQL 里 VARCHAR(20),报告里写 VARCHAR(50);允许空属性乱标,NOT NULL 的列标成可空;默认值不写。这些错误不需要运行代码就能被老师发现,属于白送的扣分项。还有个细节,主键、外键、唯一约束要用统一的标记列写清楚,别散落在说明文字里。

4.2 ER 图转关系模式:三范式检查与测试用例的组织

报告第二章一般要求画 ER 图并转关系模式,这一部分要写出转换的逻辑,而不是扔一张图了事。选课系统的转换过程是:学生和课程之间的多对多联系,转换为一张独立的选课记录表;选课记录表的主键由两端主键组合而成,但本文设计额外增加自增 id 作主键、两端主键作外键并加唯一约束。为什么要加自增 id,建表章节的理由这里再写一遍,前后呼应,老师会认为你是真理解而不是背稿。

三范式检查单独占一小节,说明每张表满足到第几范式、为什么。student 表字段都依赖学号主键,无部分依赖、无传递依赖,满足 3NF;course 表同理;selection 表的 score 依赖的是选课关系存在这一事实,在自增主键方案下同样满足 3NF。这段 200 字左右,把「规范化分析」四个字落到实处,是设计合理性的直接证据。

测试章节是另一个常见的拉分点。别只写一句「系统测试通过」,给一张测试用例表,每条用例对应一张运行截图:

用例编号测试项操作步骤预期结果实际结果
TC-01学生登录输入正确学号密码进入选课主界面与预期一致
TC-02学生登录输入错误密码提示用户名或密码错误与预期一致
TC-03选课成功选择未满的课程选课记录插入,已选人数+1与预期一致
TC-04重复选课再次选择同一门课提示已选,不插入记录与预期一致
TC-05退课退掉已选课程记录删除,已选人数-1与预期一致
TC-06课程已满选择容量已满的课程提示课程已满与预期一致

测试用例的目的不是证明系统没有 bug,而是展示你有测试意识、知道边界条件在哪里。容量已满、重复选课、错误密码这三条边界用例,比十条正常路径的用例更有说服力。截图要露出来操作前后的数据变化,比如选课前查一次 course 表、选课后查一次,对比 selected_count,比单纯截个成功弹窗值钱得多。

报告开头还会要求写需求分析,这里不用长篇大论,把角色分清楚就够了:学生角色能登录、选课、退课、查成绩;管理员角色能维护课程信息、录入成绩。每个角色配一两段用例描述。这块容易写飘,动不动「系统采用 B/S 架构」「界面友好」这种空话,不如直接写清楚每个页面有哪些功能、点击后发生什么。

运行环境章节要写清楚 JDK 版本、MySQL 版本、驱动版本、操作系统,而且必须和你实际跑通的环境一致。很多人买了别人的源码,本地跑通了但报告里写的是作者的环境,答辩时换台机器现场跑,当场露馅。版本一致这一条,是报告里最简单也最容易被抽查的细节。

5. 课设避坑指南:乱码、外键删不掉、时区报错——五个常见翻车现场

配环境、跑项目、改数据的过程中,绝大多数报错都集中在这五个点上。每条按「现象 → 原因 → 解决」的顺序写,出问题时直接对号入座。这里写的每一条,都是真实发生过很多次的踩坑记录。

5.1 中文乱码:控制台和页面全是问号,三个环节逐一排查

现象:插入的中文数据查询出来全是问号,Java 控制台打印乱码,页面也乱。

原因:三个环节至少有一个编码不一致——数据库字符集不是 utf8mb4、JDBC 连接串没带 characterEncoding=utf8、Java 源文件本身不是 UTF-8 编码。

解决:逐一排查。先在 MySQL 命令行里执行 SHOW CREATE TABLE student 看表字符集,再用 SHOW VARIABLES LIKE 'character_set%' 看库的默认字符集,确认建库语句里有 DEFAULT CHARACTER SET utf8mb4;接着看连接串是否带了 characterEncoding=utf8;最后确认 IDE 里项目文件编码是 UTF-8。三个对齐,乱码基本绝迹。如果建库时已经用了 utf8 且表里有乱码数据,别指望 ALTER TABLE 改字符集能救回旧数据,建议把表删了重新执行建表脚本——这是后悔药都不好使的案例,所以建库时字符集一定要一次选对。

5.2 外键约束删不掉:父表和子表的删除顺序问题

现象:执行 DELETE FROM course WHERE course_id='C001' 直接报错,提示 Cannot delete or update a parent row: a foreign key constraint fails。

原因:selection 表里还有引用这门课的选课记录,外键约束不允许删除仍被引用的父行,这是 InnoDB 外键的默认行为,防止出现指向不存在课程的孤儿记录。

解决:先删子表再删父表,即先 DELETE FROM selection WHERE course_id='C001',再删 course 里的记录。另一种做法是建外键时指定 ON DELETE CASCADE,删课程时自动级联删除选课记录。课设里我一般建议手动控制顺序,而不是用 CASCADE——级联是隐式删除,报告里写不明白,答辩被追问时容易说不清。删学生同理,想删一个学生,得先清理他全部选课记录,否则一样被外键拦着。

5.3 MySQL 8.0 连接报错:时区、SSL 与公钥检索三个参数

现象:JDBC 连 8.0 时报 The server time zone value '�й���ʱ��' is unrecognized,或者控制台刷 SSL 警告,或者报 Public Key Retrieval is not allowed。

原因:8.0 的认证插件从 mysql_native_password 换成了 caching_sha2_password,时区处理也和 5.7 不同,连接串缺参数就会触发。时区报错里那串乱码,是中文时区名没被正确解析。

解决:连接串一次性补齐 useSSL=false、serverTimezone=Asia/Shanghai、allowPublicKeyRetrieval=true 三个参数。第 3.1 节的 DBUtil 已经是完整形态,直接抄。5.7 环境不加也能跑,但加了无害,所以统一写全最省事。如果你看到别人写的 serverTimezone=GMT%2B8,效果一样,但比 Asia/Shanghai 难读,答辩时不方便解释,还是用前者更稳。

5.4 重复选课没拦住:唯一约束比 Java 检查更可靠

现象:同一个学生对同一门课出现了两条选课记录,课程表 selected_count 虚高,成绩录入不知道以哪条为准。

原因:代码里只做了「先查询再插入」的检查,两个请求在查询和插入之间并发进来,就穿过了检查;或者建表时压根没加唯一约束。

解决:selection 表必须有 (student_id, course_id) 的唯一索引,这是数据库层面的最后一道闸。Java 代码的检查是业务逻辑,唯一约束是数据完整性,两者缺一不可。插入时违反唯一约束会抛 Duplicate entry '2024001-C001' for key 'uk_student_course',代码里捕获这个异常:

catch (SQLIntegrityConstraintViolationException e) { return false; // 转成「你已选过这门课」的友好提示 }

这比先查一遍再插入更可靠,因为查和插之间没有竞态窗口。

5.5 MySQL 服务起不来:端口占用、安装配置与 Docker 替代路线

现象:安装 MySQL 后服务启动失败,或 3306 端口被占,或 mysql -u root -p 提示无法连接。Windows 上最常见的场景是装完 8.0 一启动就报错。

原因:3306 被别的程序占用是头号原因;8.0 安装后 root 用户默认 caching_sha2_password 认证,老版本客户端连不上;还有人是安装时没初始化数据目录。

解决:先在命令行执行 netstat -ano | findstr 3306 查占用,确认占用进程后换端口或停掉冲突服务;root 密码忘了或认证出问题,用 skip-grant-tables 模式进库重置,这是救急手段,改完必须重启并移除参数。如果本地怎么都装不好,Docker 容器跑 mysql:8.0 是省事路线,docker run 拉镜像几分钟,但容器端口映射和数据卷挂载必须配好,否则容器一删数据全没。docker 安装 mysql 失败的问题,九成出在端口映射冲突或数据卷权限上。

注意:skip-grant-tables 是临时救急手段,用完必须改回正常模式,否则数据库没有密码保护,等于裸奔。

6. 提交前的自测清单:演示数据脚本、验收流程与答辩高频问题

系统写完别急着打包提交。课设的验收标准不是「能跑」,而是「演示流畅、逻辑完整、讲得清楚」。最后我一般花一晚上做三件事:灌一套演示数据、按清单自测全部功能、把答辩可能被问的问题提前想好答案。

演示数据直接写成一个 SQL 脚本,随源码一起交付。数据要贴近真实,不要全是 test1、test2 这种一眼假的记录:

INSERT INTO student (student_id, student_name, password, major, enroll_year) VALUES ('2024001', '张伟', '123456', '计算机科学与技术', 2024), ('2024002', '李娜', '123456', '软件工程', 2024); INSERT INTO course (course_id, course_name, teacher_name, credit, capacity, selected_count) VALUES ('C001', '数据库原理', '王老师', 3.0, 50, 0), ('C002', 'Java 程序设计', '赵老师', 4.0, 40, 0), ('C003', '数据结构', '孙老师', 3.5, 45, 0);

六条数据已经覆盖登录、选课、退课、查成绩全部路径。演示时先登录、再选一门课、退掉、查成绩,每步操作后都切到 MySQL 命令行把对应表 SELECT 出来,前后对比 selected_count 的变化。这个动作很朴素,但比任何截图都有说服力,老师一眼就能看出你是真跑通了。

答辩高频问题基本集中在三处:表结构为什么这样设计、并发选课会不会超选、外键和事务怎么配合使用。答案在第 2、3 章已经写透,你只需要把每张表的设计理由、事务边界、唯一约束的兜底作用用自己的话讲一遍。注意别背代码,老师问的是思路。

这几年看过太多课设翻车,多数不是不会写代码,而是没把「为什么」想清楚。数据库课程设计真正训练的不是增删改查,而是建模思维——先画 ER 图、再做范式检查、再落表,用事务和约束保证数据完整。把这种习惯带到以后的工作里,比这份源码本身值钱得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询