资讯详情 MySQL+Java实现学生选课系统:从ER图到JDBC实战全解析
📅 2026/10/13 1:52:56
简介面向数据库课程设计场景的完整实践资源提供基于MySQL与Java的学生选课信息管理系统源代码及配套设计报告。系统采用C/S架构围绕学生、教师、管理员三类用户构建权限分明的业务模块学生端具备个人信息维护、课程查询、选课退课、成绩查询、成绩单打印与奖惩信息查看教师端支持所授课程信息查看、选课名单获取及成绩录入管理员端覆盖学生、教师、课程、选课、成绩、院系设置、奖惩信息及操作审计等集中管理功能。数据库表设计合理规范代码结构清晰附带详细设计报告便于对照理解从需求分析到表关系设计再到功能实现的完整流程。压缩包整体约1.99MB以Java源文件、SQL脚本及设计文档为主可直接用于课程设计提交或作为原型改造基础。目前已有6064人学习/下载能帮助高校计算机相关专业学生高效完成选题、设计与文档撰写也适合开发者快速掌握教务类管理系统的分层实现思路。1. 学生选课信息管理系统数据库课程设计为什么绕不开 MySQLJava 这条路每个学期的数据库课程设计题库里最不缺的就是学生选课信息管理系统学生、课程、选课记录三张表配上登录、选课、退课、查成绩这些动作。系统看着简单老师反而更抠细节——外键怎么设、并发选课怎么办、设计报告的数据字典全不全全是扣分点。这篇笔记聊的是最成熟的那条路线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 表所有字段都不可再分、都完全依赖主键学号、没有传递依赖满足 3NFcourse 表同理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 ?useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8 allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { // MySQL 8.x 用 com.mysql.cj.jdbc.Driver5.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); } }连接串里四个参数每一个都是踩坑踩出来的。useSSLfalse 关掉 SSL 握手课设是本地开发环境开着 SSL 只会增加报错面serverTimezoneAsia/Shanghai 是 MySQL 8.0 的硬要求不设会报 Server returns invalid timezonecharacterEncodingutf8 保证中文在传输层不乱码allowPublicKeyRetrievaltrue 对应 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 表字段都依赖学号主键无部分依赖、无传递依赖满足 3NFcourse 表同理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 连接串没带 characterEncodingutf8、Java 源文件本身不是 UTF-8 编码。解决逐一排查。先在 MySQL 命令行里执行 SHOW CREATE TABLE student 看表字符集再用 SHOW VARIABLES LIKE character_set% 看库的默认字符集确认建库语句里有 DEFAULT CHARACTER SET utf8mb4接着看连接串是否带了 characterEncodingutf8最后确认 IDE 里项目文件编码是 UTF-8。三个对齐乱码基本绝迹。如果建库时已经用了 utf8 且表里有乱码数据别指望 ALTER TABLE 改字符集能救回旧数据建议把表删了重新执行建表脚本——这是后悔药都不好使的案例所以建库时字符集一定要一次选对。5.2 外键约束删不掉父表和子表的删除顺序问题现象执行 DELETE FROM course WHERE course_idC001 直接报错提示 Cannot delete or update a parent row: a foreign key constraint fails。原因selection 表里还有引用这门课的选课记录外键约束不允许删除仍被引用的父行这是 InnoDB 外键的默认行为防止出现指向不存在课程的孤儿记录。解决先删子表再删父表即先 DELETE FROM selection WHERE course_idC001再删 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 不同连接串缺参数就会触发。时区报错里那串乱码是中文时区名没被正确解析。解决连接串一次性补齐 useSSLfalse、serverTimezoneAsia/Shanghai、allowPublicKeyRetrievaltrue 三个参数。第 3.1 节的 DBUtil 已经是完整形态直接抄。5.7 环境不加也能跑但加了无害所以统一写全最省事。如果你看到别人写的 serverTimezoneGMT%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 图、再做范式检查、再落表用事务和约束保证数据完整。把这种习惯带到以后的工作里比这份源码本身值钱得多。希望帮到你。本文还有配套的精品资源点击获取