资讯动态

数据库课程设计选课系统:MySQL表结构设计与Java事务实现避坑指南

发布时间:2026/10/9 12:50:46 来源:尧图企业网站定制
简介这份数据库课程设计资源面向高校计算机相关专业学生与课程设计指导教师围绕学生选课信息管理系统展开采用Java语言与MySQL数据库、C/S架构实现适合作为数据库原理、Java程序设计等课程的期末大作业或实训参考。系统按学生、教师、管理员三类角色划分权限学生可修改个人信息、查询课程、选课退课、查询成绩并打印成绩单、查看奖惩信息教师可维护个人信息、查看所授课程与学生选课情况、录入成绩管理员则负责学生、教师、课程、选课、成绩、院系及奖惩信息等综合管理。压缩包为rar格式大小约1.99MB内含源代码与配套设计报告建表合理规范字段设计清晰便于理解实体关系与业务逻辑。目前已有6064人学习下载读者可借此获得完整可运行的课程设计案例、规范的数据库表结构设计思路以及分角色功能实现的参考方案适合对照学习与二次开发。1. 数据库课程设计选课系统为什么“能跑”和“能过”之间差着三张表每年到了数据库课程设计季总有一批人卡在同一个地方学生选课信息管理系统。表面上看这个题目简单到像是送分——学生、课程、选课三个实体两张关联一个 CRUD 就完事。但真正动手写 MySQL 建表语句的时候很多人会发现事情没那么简单。选课人数超了怎么办同一门课重修怎么记退课之后名额怎么释放这些问题不解决代码能跑答辩必挂。这篇笔记面向的是正在做数据库课程设计、需要交源代码和设计报告的读者。我会按一线开发的思路把 MySQL 表结构设计、Java 后端实现、选课核心业务逻辑、以及设计报告里必须写清楚的关键点一层层拆开。不堆概念只讲能直接抄进项目的做法以及那些我踩过的坑。如果你手头正好有这个题目跟着走一遍至少能省掉三天来回改表结构的时间。2. 表结构设计从 ER 图到 MySQL 建表语句的完整落地选课系统的数据库设计核心不是画 ER 图而是把 ER 图翻译成能扛住并发选课、支持退课重选、能查历史记录的物理表。很多课程设计报告里 ER 图画得漂亮一到建表就露馅——字段类型乱选、外键约束缺失、索引该加不加。这一章把表结构定死后面 Java 代码才有根基。2.1 五张核心表与字段类型选型理由一个能通过答辩、也能实际跑起来的选课系统最少需要五张表学生表、课程表、选课记录表、教师表、开课表。注意课程和开课是两回事——课程是“数据结构”这门课本身开课是“2025 春季学期某教师周三 3-4 节容量 60 人”这个具体教学班。很多课设把这两个混成一张表结果同一门课多个老师开课就抓瞎。学生表student的学号用VARCHAR(20)而不是INT因为学号可能带字母或前导零。姓名VARCHAR(50)性别用ENUM(男,女)或CHAR(1)入学年份YEAR。课程表course的课程号同样VARCHAR(20)课程名VARCHAR(100)学分用DECIMAL(3,1)因为可能是 2.5 学分。选课记录表sc是核心字段包括学号、开课号、选课时间、成绩、状态。状态用TINYINT标记1 表示已选2 表示已退3 表示已修完。这样退课不删记录保留操作痕迹答辩时老师问“怎么查退课历史”你就能答上来。开课表teaching_class需要包含课程号、教师工号、学期、上课时间、上课地点、容量、已选人数。已选人数这个字段是冗余的但必须加——每次选课都去COUNT一遍选课记录表并发一上来就锁表。用冗余字段配合事务更新是课程设计里性价比最高的优化。-- 学生表学号做主键避免自增ID在导入数据时冲突 CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender ENUM(男,女) DEFAULT 男 COMMENT 性别, enroll_year YEAR NOT NULL COMMENT 入学年份, major VARCHAR(100) COMMENT 专业, class_name VARCHAR(50) COMMENT 班级 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 课程表课程号做主键学分用DECIMAL支持0.5学分 CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY COMMENT 课程号, course_name VARCHAR(100) NOT NULL COMMENT 课程名, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 学分, hours INT DEFAULT 32 COMMENT 学时 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 开课表教学班容量和已选人数是关键字段 CREATE TABLE teaching_class ( class_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 开课号, course_id VARCHAR(20) NOT NULL COMMENT 课程号, teacher_id VARCHAR(20) NOT NULL COMMENT 教师工号, semester VARCHAR(20) NOT NULL COMMENT 学期如2025-2026-1, class_time VARCHAR(50) COMMENT 上课时间, location VARCHAR(50) COMMENT 上课地点, capacity INT NOT NULL DEFAULT 60 COMMENT 容量, selected_count INT NOT NULL DEFAULT 0 COMMENT 已选人数, FOREIGN KEY (course_id) REFERENCES course(course_id), INDEX idx_semester (semester) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 选课记录表状态字段保留退课痕迹成绩可空 CREATE TABLE sc ( id INT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20) NOT NULL, class_id INT NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, score DECIMAL(5,1) DEFAULT NULL COMMENT 成绩, status TINYINT NOT NULL DEFAULT 1 COMMENT 1已选 2已退 3已修完, FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (class_id) REFERENCES teaching_class(class_id), UNIQUE KEY uk_student_class (student_id, class_id, status), INDEX idx_student (student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表语句有几个细节值得说。sc表上的唯一索引uk_student_class包含status是为了允许同一个学生先选后退再选——如果只对(student_id, class_id)做唯一约束退课记录还在重新选课就插不进去了。teaching_class上的idx_semester索引是因为查“本学期开了哪些课”是最频繁的查询。所有外键都显式命名方便后面排错时直接定位。2.2 选课人数冗余字段与事务更新的配合selected_count这个冗余字段是选课系统里最容易出问题的地方。我见过太多课设代码选课逻辑是先SELECT查已选人数判断小于容量再INSERT选课记录最后UPDATE已选人数加一。这个流程在单线程测试时永远正确一放到多人同时选课的场景就超卖——两个请求同时查到 59 人都判断小于 60都插入成功最后 61 人。正确做法是把判断和更新合并到一条 SQL 里利用 InnoDB 的行锁-- 选课核心一条UPDATE完成容量校验和人数递增 UPDATE teaching_class SET selected_count selected_count 1 WHERE class_id ? AND selected_count capacity;这条语句执行后检查受影响行数。如果返回 0说明要么容量满了要么开课号不存在。返回 1 才继续插入sc记录。整个过程包在一个事务里sc插入失败就回滚selected_count自动恢复。这个写法比SELECT ... FOR UPDATE再判断要简洁也避免了间隙锁带来的额外开销。退课逻辑对称处理先把sc记录状态改为 2再执行UPDATE teaching_class SET selected_count selected_count - 1 WHERE class_id ? AND selected_count 0。注意退课不需要判断容量但需要判断已选人数大于 0防止数据异常时减成负数。2.3 建表后的自检清单与索引验证建完表别急着写 Java先跑几条 SQL 自检。第一查外键是否生效尝试插入一条student_id不存在的选课记录应该报外键约束错误。第二查唯一索引是否生效同一学生同一教学班插入两条status1的记录应该报唯一键冲突。第三用EXPLAIN看关键查询的执行计划-- 检查“查某学生本学期选课”是否走索引 EXPLAIN SELECT tc.class_id, c.course_name, tc.class_time FROM sc JOIN teaching_class tc ON sc.class_id tc.class_id JOIN course c ON tc.course_id c.course_id WHERE sc.student_id 2025001 AND sc.status 1;如果sc表的type列显示ALL说明没走索引需要检查idx_student是否建对。正常应该显示ref或range。这一步很多课设报告里不写但答辩时老师如果懂数据库一定会问“你的查询走索引了吗”。提前跑一遍把执行计划截图放进报告比写十行“优化了查询性能”都有说服力。3. Java 后端实现JDBC 连接、DAO 层与选课接口的代码骨架表结构定好之后Java 这边的工作量其实不大但结构必须清晰。课程设计最忌讳把所有逻辑塞进一个main方法或者一个 Servlet 里。我一般按entity→dao→service→controller四层来组织每层只干一件事。这样代码好调试报告里画架构图也好看。3.1 数据库连接池配置与 JDBC 工具类不要用DriverManager.getConnection()每次新建连接课程设计虽然并发低但养成用连接池的习惯报告里能多写一节“连接池选型”。用 HikariCP 最省事Maven 加一个依赖写一个工具类// DBUtil.javaHikariCP连接池单例 import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { private static final HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/course_selection?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8); config.setUsername(root); config.setPassword(your_password); config.setMaximumPoolSize(10); // 课设够用生产环境按需调大 config.setMinimumIdle(2); config.setConnectionTimeout(3000); // 3秒拿不到连接就报错避免界面卡死 config.setPoolName(CourseSelectionPool); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }maximumPoolSize设 10 是因为课程设计通常本机跑MySQL 默认最大连接数 15110 个连接足够。connectionTimeout设 3000 毫秒是为了在数据库没启动时快速失败而不是让界面转圈半分钟。serverTimezone必须加否则 MySQL 8 驱动会报时区错误这是新手最常见的翻车点之一。3.2 选课接口的 Service 层事务控制选课业务逻辑放在 Service 层一个方法对应一个完整事务。下面是最小可用版本// CourseSelectionService.java选课与退课的事务方法 public class CourseSelectionService { /** * 选课容量校验 插入记录 更新人数三步一个事务 * return 0成功1容量已满2重复选课-1系统异常 */ public int selectCourse(String studentId, int classId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 第一步原子更新已选人数同时校验容量 String updateSql UPDATE teaching_class SET selected_count selected_count 1 WHERE class_id ? AND selected_count capacity; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, classId); int affected ps.executeUpdate(); if (affected 0) { conn.rollback(); return 1; // 容量已满或开课号不存在 } } // 第二步插入选课记录 String insertSql INSERT INTO sc (student_id, class_id, status) VALUES (?, ?, 1); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setString(1, studentId); ps.setInt(2, classId); ps.executeUpdate(); } conn.commit(); return 0; } catch (SQLIntegrityConstraintViolationException e) { // 唯一索引冲突说明已经选过这门课 rollbackQuietly(conn); return 2; } catch (SQLException e) { rollbackQuietly(conn); e.printStackTrace(); return -1; } finally { closeQuietly(conn); } } private void rollbackQuietly(Connection conn) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } } private void closeQuietly(Connection conn) { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } } }这段代码的关键在于容量校验和人数递增合并在一条UPDATE里利用数据库行锁保证原子性。插入sc记录时如果触发唯一索引冲突捕获SQLIntegrityConstraintViolationException并返回“重复选课”。事务回滚后第一步的selected_count加一操作也会撤销不会出现“人数加了但没选上课”的脏数据。退课方法对称实现先UPDATE sc SET status 2 WHERE student_id ? AND class_id ? AND status 1检查受影响行数如果为 1 再UPDATE teaching_class SET selected_count selected_count - 1 WHERE class_id ? AND selected_count 0。同样包在事务里。3.3 查询接口与分页用 LIMIT 还是查全量课程设计里查询接口通常有三种查学生已选课程、查某教学班学生名单、查可选课程列表。前两种数据量小直接查就行。第三种“可选课程列表”如果课程多需要分页。MySQL 分页用LIMIT offset, size但注意offset越大越慢因为要扫描前 offset 行。课设数据量通常几百条无所谓但报告里可以提一句“生产环境可用游标分页优化”显得有深度。-- 查某学生本学期已选课程带课程名和教师名 SELECT c.course_name, c.credit, tc.class_time, tc.location, t.name AS teacher_name FROM sc JOIN teaching_class tc ON sc.class_id tc.class_id JOIN course c ON tc.course_id c.course_id JOIN teacher t ON tc.teacher_id t.teacher_id WHERE sc.student_id ? AND sc.status 1 AND tc.semester ? ORDER BY sc.select_time DESC;这条查询走了sc.idx_student索引然后回表关联teaching_class和course。如果数据量大可以在sc表上建联合索引(student_id, status, class_id)覆盖更多条件。课设阶段不用过度优化但报告里写清楚“预期走 idx_student 索引”就够了。4. 避坑与排查选课系统课设里最容易翻车的五个地方这一章记录的是我在帮人看课设代码时反复遇到的真实问题。每个都按“现象 → 原因 → 解决”写你对着自己的项目排查一遍能省掉大量调试时间。4.1 中文乱码从数据库到控制台到浏览器现象选课记录里的课程名显示成????或者æ•°æ®ç»“æž„。原因通常有三层MySQL 建库时没指定utf8mb4JDBC 连接串没加characterEncodingutf8Tomcat 响应没设Content-Type。解决建库语句写CREATE DATABASE course_selection DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;JDBC URL 加characterEncodingutf8Servlet 里response.setContentType(text/html;charsetutf-8)。三层都对齐乱码基本绝迹。4.2 选课超卖并发测试时人数超过容量现象用 JMeter 或手写多线程模拟 10 个人同时选同一门容量 5 的课最后selected_count变成 7 或 8。原因先查后改的写法在并发下失去原子性。解决改用第 3 章那条UPDATE ... WHERE selected_count capacity的原子语句配合事务。测试时开两个命令行窗口同时执行选课 SQL观察受影响行数能直观看到行锁生效。4.3 外键约束导致删数据失败现象想删一门课程报Cannot delete or update a parent row: a foreign key constraint fails。原因teaching_class表引用了course表直接删course记录被外键挡住。解决要么先删关联的开课记录和选课记录要么在建表时给外键加ON DELETE CASCADE。课设里我建议不加级联删除手动按顺序删更安全避免误删学生选课历史。如果非要加只在sc表的外键上加ON DELETE CASCADE删开课记录时自动清理选课记录。4.4 连接池耗尽界面卡死无响应现象点了几次查询之后界面一直转圈后台日志报Connection is not available, request timed out。原因DAO 层某个方法拿了连接没关闭或者异常路径下conn.close()没执行。解决所有数据库操作都用 try-with-resources或者像第 3 章那样在finally里closeQuietly。检查方法在DBUtil.getConnection()里加一行日志打印当前活跃连接数跑一遍功能就能定位哪个方法漏了关闭。4.5 事务未提交导致数据“消失”现象选课提示成功但刷新页面查不到记录重启程序后又出现了。原因Connection的autoCommit被设成false之后忘了commit()数据留在事务里没落盘。解决Service 层每个写操作方法末尾必须显式conn.commit()异常分支必须conn.rollback()。用 AOP 统一管理事务是进阶做法课设阶段手动控制更直观也方便在报告里画事务流程图。5. 设计报告怎么写让答辩老师一眼看到数据库设计功力的三个技巧设计报告不是代码的翻译而是把你做决策的过程讲清楚。同样一个选课系统报告写得好不好直接决定答辩是被追问还是被表扬。这一章给三个具体技巧每个都对应报告里的一节内容。5.1 用“反例对比”展示表结构演进过程不要只放最终的五张表建表语句。在报告里加一小节“为什么不用三张表”先画出最简化的三表方案学生、课程、选课然后指出问题同一门课多个老师开课无法表达退课记录无法保留容量控制没有落脚点。再给出五表方案用表格对比两种方案的字段和适用场景。这种写法让老师看到你不是照抄模板而是真的想过。对比项三表方案五表方案教师开课课程表加教师字段无法支持多教师独立开课表支持一课多师退课记录直接删除选课记录无历史status 字段保留痕迹容量控制无字段可存需实时 COUNTselected_count 冗余字段学期管理无学期概念开课表带 semester 字段5.2 把事务隔离级别和锁的验证写进测试章节报告里通常有“测试”一节大部分人只写“功能正常”。你可以加一个“并发测试”小节用两个线程同时选同一门容量 1 的课记录两个线程的返回值和最终selected_count。预期结果是一个返回成功、一个返回容量已满selected_count等于 1。把测试代码和输出截图放上去再补一句“InnoDB 默认 REPEATABLE READ 隔离级别下UPDATE 语句对匹配行加排他锁保证判断和更新的原子性”。这一节能让报告从“功能实现”上升到“有数据库理论支撑”。5.3 索引设计说明每个索引都要有存在理由不要只写“在 student_id 上建了索引”。用表格列出每个索引对应的查询场景索引名所在表字段支撑的查询idx_studentscstudent_id查学生已选课程idx_semesterteaching_classsemester查学期开课列表uk_student_classscstudent_id, class_id, status防止重复选课再加一句“未对 course_name 建索引因为课程名查询使用 LIKE %xx% 无法走索引且课程数量少全表扫描成本可接受”。这种“知道为什么不建”比“建了什么”更能体现设计能力。5.4 报告里必须出现的三张图与两个表三张图ER 图用 Chen 表示法标出基数、系统架构图展示 Controller-Service-DAO 分层、选课流程图泳道图标出事务边界。两个表表结构汇总表字段名、类型、约束、说明、接口清单表接口名、入参、出参、对应 SQL。这两张表放在报告附录答辩时老师翻到就能快速定位不用在正文里大海捞针。最后说一个我自己的习惯报告写完打印出来用红笔把每一处“我认为老师会问为什么”的地方圈出来然后补一句话解释。通常能圈出七八处补完这些答辩时被问倒的概率就很小了。数据库课程设计这个题目代码量不大但表结构和事务逻辑是硬功夫把这两块吃透报告自然有东西写。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑