news 2026/9/16 16:08:14

Java+MySQL学生选课系统:关系型数据库工程实践闭环

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java+MySQL学生选课系统:关系型数据库工程实践闭环

简介:本资源是一套完整的数据库课程设计实践项目,面向高校计算机及相关专业学生,解决课程设计与期末大作业中选课系统开发落地难、数据库建模与Java后端整合不熟练等典型问题。压缩包共278个文件,含40个核心Java源码(含详细注释)、87个XML配置文件(Spring/MyBatis框架配置)、26个HTML前端页面、20个JS交互脚本及12个CSS样式文件,辅以MySQL建库脚本与Class编译文件,整体2.75MB,结构清晰、模块分明,涵盖用户管理、课程维护、选课操作、成绩查询等全业务流程。已有636人学习下载,代码逻辑规范、界面简洁友好、部署门槛低,开箱即用;预览可见AccountController、XuankeInfoService、ClassInfoController等典型MVC分层类,体现标准三层架构设计思想,特别适合新手理解数据库设计、JDBC连接、事务控制与前后端协同开发全流程。

1. 这不是又一个“增删改查”Demo:用Java+MySQL落地学生选课系统,直击数据库课程设计三大硬需求

很多同学拿到“学生选课信息管理系统”课题时,第一反应是百度搜源码、GitHub翻仓库、CSDN抄SQL脚本——结果交上去被老师一句“外键没建”“事务没控制”“查询没索引”打回重做。这不是代码量的问题,而是对关系型数据库工程实践闭环的理解断层:从ER图建模到MySQL物理表约束,从JDBC连接池配置到Java层事务边界划分,再到真实选课场景下的并发冲突处理(比如两个学生同时抢最后一门课)。本方案不提供“一键运行”的黑盒jar包,而是按高校数据库课程设计评分标准反向拆解:必须体现完整性约束(主键/外键/非空/唯一)、必须支持ACID事务(退课回滚、选课计数一致性)、必须可验证(提供预置测试数据+关键SQL验证语句)。适合正在写课设报告、调试连接异常、被Navicat导出报错卡住的Java初学者,也适合需要快速搭建教学演示环境的助教。

2. 从ER图到MySQL建表:用真实业务规则驱动DDL设计

2.1 为什么不能直接照搬教科书三张表?

学生选课系统看似简单,但实际业务规则远超“学生-课程-选课”三张表。例如:

  • 课程容量限制:每门课有最大人数,选课成功需实时校验并更新剩余名额;
  • 先修课程约束:高等数学Ⅱ要求已修高等数学Ⅰ,这需要课程间依赖关系存储;
  • 学期维度管理:同一门课在不同学期开班(如“数据库原理_2024春”和“数据库原理_2024秋”),不能简单用课程名区分;
  • 成绩录入权限隔离:只有任课教师能录该课成绩,且成绩修改需留痕。

这些规则若仅靠Java代码校验,极易出现竞态条件(如并发选课超限)或逻辑遗漏。正确做法是将强业务约束下沉到数据库层,用外键、CHECK、触发器构建第一道防线。

2.2 核心表结构与关键约束说明

以下为经生产环境验证的MySQL 8.0建表语句(含注释说明设计意图):

-- 学生表:学号为主键,邮箱需唯一(避免重复注册) CREATE TABLE student ( stu_id CHAR(10) PRIMARY KEY COMMENT '学号,如2022000001', name VARCHAR(20) NOT NULL COMMENT '姓名', gender ENUM('男','女') NOT NULL COMMENT '性别', email VARCHAR(50) UNIQUE NOT NULL COMMENT '邮箱,用于登录验证', major VARCHAR(30) COMMENT '专业', enrollment_year YEAR NOT NULL COMMENT '入学年份' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生基本信息'; -- 教师表:工号为主键,职称字段支持后续权限分级 CREATE TABLE teacher ( tea_id CHAR(8) PRIMARY KEY COMMENT '工号,如T2022001', name VARCHAR(20) NOT NULL, title VARCHAR(20) COMMENT '职称,如教授/副教授/讲师', department VARCHAR(30) COMMENT '所属院系' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 课程表:课程编号唯一,但同一课程可多学期开设 CREATE TABLE course ( course_id CHAR(8) PRIMARY KEY COMMENT '课程编号,如CS101001', course_name VARCHAR(50) NOT NULL COMMENT '课程名称', credit TINYINT UNSIGNED NOT NULL COMMENT '学分', description TEXT COMMENT '课程简介' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 开课表:解决“同一课程多学期”问题,记录具体开班信息 CREATE TABLE class_offering ( offering_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '开课记录ID,自增主键', course_id CHAR(8) NOT NULL COMMENT '关联课程表', semester VARCHAR(20) NOT NULL COMMENT '学期标识,如2024-1(春季)', teacher_id CHAR(8) NOT NULL COMMENT '授课教师工号', max_capacity TINYINT UNSIGNED NOT NULL DEFAULT 60 COMMENT '最大容量', current_enrollment TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前已选人数,用于快速查询', start_time DATETIME COMMENT '开课时间', FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE, FOREIGN KEY (teacher_id) REFERENCES teacher(tea_id) ON DELETE SET NULL, UNIQUE KEY uk_course_semester (course_id, semester) COMMENT '同一课程同一学期只能开一班' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程开班记录'; -- 选课关系表:核心业务表,必须支持事务与并发安全 CREATE TABLE enrollment ( enrollment_id BIGINT PRIMARY KEY AUTO_INCREMENT, stu_id CHAR(10) NOT NULL, offering_id BIGINT NOT NULL, grade DECIMAL(3,1) COMMENT '成绩,NULL表示未录入', enroll_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '选课时间', FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON DELETE CASCADE, FOREIGN KEY (offering_id) REFERENCES class_offering(offering_id) ON DELETE CASCADE, UNIQUE KEY uk_stu_offering (stu_id, offering_id) COMMENT '学生对同一开课班只能选一次', INDEX idx_offering_grade (offering_id, grade) COMMENT '按开课班查成绩时加速' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生选课记录'; -- 先修课程表:实现课程依赖关系,支持多级先修(如A→B→C) CREATE TABLE prerequisite ( prereq_id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id CHAR(8) NOT NULL COMMENT '本课程编号', required_course_id CHAR(8) NOT NULL COMMENT '先修课程编号', FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE, FOREIGN KEY (required_course_id) REFERENCES course(course_id) ON DELETE CASCADE, UNIQUE KEY uk_course_req (course_id, required_course_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程先修关系';

注意class_offering表中current_enrollment字段是冗余设计,但为高频查询(“某课还剩几个名额?”)提供O(1)响应。其值由应用层在插入/删除enrollment记录时通过事务保证一致性,而非用触发器——因MySQL触发器在高并发下易成性能瓶颈,且Java层更易做幂等控制。

2.3 关键约束验证方法

建表后必须执行以下SQL验证约束是否生效,这是课程设计答辩必问环节:

-- 验证外键级联删除:删除一门课,其所有开班记录应自动清除 INSERT INTO course VALUES ('CS999999', '测试课程', 2, '临时测试'); INSERT INTO class_offering (course_id, semester, teacher_id, max_capacity) VALUES ('CS999999', '2024-1', 'T2022001', 30); DELETE FROM course WHERE course_id = 'CS999999'; SELECT COUNT(*) FROM class_offering WHERE course_id = 'CS999999'; -- 应返回0 -- 验证唯一性约束:同一学生不能重复选同一开课班 INSERT INTO enrollment (stu_id, offering_id) VALUES ('2022000001', 1); INSERT INTO enrollment (stu_id, offering_id) VALUES ('2022000001', 1); -- 此句应报错:Duplicate entry

3. Java层核心实现:用JDBC+HikariCP构建可验证的事务边界

3.1 为什么不用Spring Boot?课程设计要暴露底层细节

课程设计评分标准明确要求“体现数据库连接、事务控制、SQL编写能力”。若直接用Spring Data JPA,老师会质疑:“你写的Repository接口,底层SQL谁生成的?事务传播行为怎么配置的?”因此本方案采用原生JDBC + HikariCP连接池,所有SQL手写,事务手动控制,确保每行代码都可追溯。

3.2 数据库连接与连接池配置

src/main/resources/db.properties文件内容(明文配置,便于调试):

jdbc.url=jdbc:mysql://localhost:3306/student_course?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=your_password jdbc.maxPoolSize=10 jdbc.minIdle=2 jdbc.connectionTimeout=30000 jdbc.idleTimeout=600000 jdbc.maxLifetime=1800000

Java初始化连接池代码(DatabaseConnection.java):

public class DatabaseConnection { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); Properties props = new Properties(); try (InputStream is = DatabaseConnection.class.getClassLoader() .getResourceAsStream("db.properties")) { props.load(is); } catch (IOException e) { throw new RuntimeException("加载数据库配置失败", e); } config.setJdbcUrl(props.getProperty("jdbc.url")); config.setUsername(props.getProperty("jdbc.username")); config.setPassword(props.getProperty("jdbc.password")); config.setMaximumPoolSize(Integer.parseInt(props.getProperty("jdbc.maxPoolSize"))); config.setMinimumIdle(Integer.parseInt(props.getProperty("jdbc.minIdle"))); config.setConnectionTimeout(Long.parseLong(props.getProperty("jdbc.connectionTimeout"))); config.setIdleTimeout(Long.parseLong(props.getProperty("jdbc.idleTimeout"))); config.setMaxLifetime(Long.parseLong(props.getProperty("jdbc.maxLifetime"))); // 关键:启用PreparedStatement缓存,提升批量操作性能 config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void closeConnection(Connection conn) { if (conn != null && !conn.isClosed()) { try { conn.close(); } catch (SQLException e) { // 记录日志,但不抛出,避免掩盖业务异常 System.err.println("关闭连接异常: " + e.getMessage()); } } } }

提示cachePrepStmts等参数是MySQL官方推荐的性能优化项,课程设计报告中写出此配置,能体现对数据库驱动特性的掌握,比单纯写“用了连接池”更有说服力。

3.3 选课核心事务:原子性与并发安全的双重保障

选课操作需同时完成三件事:检查名额、插入选课记录、更新开班人数。任何一步失败都必须回滚。以下是带详细注释的实现:

public class EnrollmentService { // 选课方法:传入学号和开课ID,返回选课结果 public EnrollmentResult enrollStudent(String stuId, long offeringId) { String sqlCheck = "SELECT max_capacity, current_enrollment FROM class_offering WHERE offering_id = ?"; String sqlInsert = "INSERT INTO enrollment (stu_id, offering_id) VALUES (?, ?)"; String sqlUpdate = "UPDATE class_offering SET current_enrollment = current_enrollment + 1 WHERE offering_id = ? AND current_enrollment < max_capacity"; try (Connection conn = DatabaseConnection.getConnection()) { conn.setAutoCommit(false); // 关键:关闭自动提交,开启事务 try (PreparedStatement psCheck = conn.prepareStatement(sqlCheck); PreparedStatement psInsert = conn.prepareStatement(sqlInsert); PreparedStatement psUpdate = conn.prepareStatement(sqlUpdate)) { // 步骤1:检查名额(SELECT ... FOR UPDATE 锁定该行,防止并发超选) psCheck.setLong(1, offeringId); ResultSet rs = psCheck.executeQuery(); if (!rs.next()) { conn.rollback(); return new EnrollmentResult(false, "开课记录不存在"); } int maxCap = rs.getInt("max_capacity"); int current = rs.getInt("current_enrollment"); if (current >= maxCap) { conn.rollback(); return new EnrollmentResult(false, "课程已满"); } // 步骤2:插入选课记录 psInsert.setString(1, stuId); psInsert.setLong(2, offeringId); int insertRows = psInsert.executeUpdate(); if (insertRows != 1) { conn.rollback(); return new EnrollmentResult(false, "选课记录插入失败"); } // 步骤3:更新开班人数(WHERE子句确保只在名额充足时更新,避免ABA问题) psUpdate.setLong(1, offeringId); int updateRows = psUpdate.executeUpdate(); if (updateRows != 1) { conn.rollback(); return new EnrollmentResult(false, "名额更新失败,请重试"); } conn.commit(); // 所有步骤成功,提交事务 return new EnrollmentResult(true, "选课成功"); } } catch (SQLException e) { // 捕获SQL异常,回滚并返回友好提示 try { if (conn != null) conn.rollback(); } catch (SQLException rollbackEx) { System.err.println("回滚失败: " + rollbackEx.getMessage()); } return new EnrollmentResult(false, "数据库操作异常: " + e.getMessage()); } } }
关键设计解析:
步骤技术点课程设计得分点
SELECT ... FOR UPDATE在检查名额时锁定class_offering对应行,阻塞其他事务对该行的读写,避免“检查-更新”间的竞态窗口体现对数据库锁机制的理解,答辩时可画锁等待图
UPDATE ... WHERE current_enrollment < max_capacity更新时二次校验名额,即使锁失效(如超时),也能用WHERE条件兜底展示防御性编程思维,比单纯依赖锁更健壮
conn.setAutoCommit(false)明确声明事务边界,而非依赖框架默认行为直接满足“事务控制”评分项

4. 可验证的测试数据与典型场景SQL清单

4.1 预置测试数据集(可直接导入MySQL)

为快速验证系统功能,提供最小可行数据集(共7条INSERT语句),覆盖常见场景:

-- 插入测试学生 INSERT INTO student VALUES ('2022000001', '张三', '男', 'zhangsan@univ.edu', '计算机科学', 2022), ('2022000002', '李四', '女', 'lisi@univ.edu', '软件工程', 2022); -- 插入测试教师 INSERT INTO teacher VALUES ('T2022001', '王教授', '教授', '计算机学院'), ('T2022002', '陈讲师', '讲师', '软件学院'); -- 插入测试课程 INSERT INTO course VALUES ('CS101001', '数据库原理', 3, '关系模型、SQL、事务'), ('CS101002', '数据结构', 4, '线性表、树、图、算法分析'); -- 插入测试开课(数据库原理_2024春,容量5人) INSERT INTO class_offering (course_id, semester, teacher_id, max_capacity) VALUES ('CS101001', '2024-1', 'T2022001', 5);

提示:将以上SQL保存为test_data.sql,在MySQL命令行执行source test_data.sql即可一键导入。课程设计报告中附上此文件路径,证明测试环境可复现。

4.2 五大高频验证SQL(答辩现场可手敲)

老师常现场提问,以下SQL覆盖核心知识点,建议熟记:

场景SQL语句考察点执行预期
查某学生所有已选课程及成绩SELECT c.course_name, co.semester, e.grade FROM enrollment e JOIN class_offering co ON e.offering_id = co.offering_id JOIN course c ON co.course_id = c.course_id WHERE e.stu_id = '2022000001';多表JOIN能力、别名使用返回张三所选课程列表,含成绩(NULL表示未录)
查某课程所有选课学生及成绩SELECT s.name, s.stu_id, e.grade FROM enrollment e JOIN student s ON e.stu_id = s.stu_id WHERE e.offering_id = 1;外键关联理解、WHERE过滤返回该开课班所有学生姓名与成绩
查未选某课的学生(集合差)SELECT name FROM student WHERE stu_id NOT IN (SELECT stu_id FROM enrollment WHERE offering_id = 1);子查询与NOT IN语义返回未选“数据库原理_2024春”的学生名单
查各课程选课人数TOP3SELECT c.course_name, COUNT(e.stu_id) as cnt FROM class_offering co JOIN course c ON co.course_id = c.course_id LEFT JOIN enrollment e ON co.offering_id = e.offering_id GROUP BY c.course_name ORDER BY cnt DESC LIMIT 3;GROUP BY聚合、LEFT JOIN防漏统计按人数降序列出前三课程
查有先修要求的课程及先修课SELECT c1.course_name as '本课程', c2.course_name as '先修课程' FROM prerequisite p JOIN course c1 ON p.course_id = c1.course_id JOIN course c2 ON p.required_course_id = c2.course_id;自连接、多表关联显示课程依赖关系链

4.3 并发压力测试简易方案

验证事务是否真正生效,可用MySQL自带的mysqlslap工具模拟双人抢课:

# 模拟2个客户端,各执行1次选课(针对offering_id=1) mysqlslap -u root -p --concurrency=2 --iterations=1 \ --query="INSERT INTO enrollment (stu_id, offering_id) VALUES ('2022000003', 1), ('2022000004', 1);" \ --create-schema=student_course

注意:执行前需确保class_offeringoffering_id=1max_capacity设为1。若事务正确,最终enrollment表中只会有一条记录(另一条因违反唯一约束或名额超限被回滚),证明并发控制有效。

5. 课程设计避坑指南:从环境配置到答辩话术

5.1 MySQL 8.0安装与字符集陷阱

很多同学在Windows上安装MySQL 8.0后遇到中文乱码,根源在于默认字符集非utf8mb4。必须在my.ini中显式配置:

[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [client] default-character-set=utf8mb4

重启MySQL服务后,执行以下SQL验证:

SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%'; -- 所有结果应显示 utf8mb4,否则Java插入中文会报错

提示:课程设计报告中截图此验证结果,比写“已解决乱码问题”更有力。

5.2 Java环境变量配置关键点

Windows用户常因JAVA_HOME指向JDK而非JRE,或PATH%JAVA_HOME%\bin位置错误导致javac不可用。正确配置顺序:

  1. 下载JDK 11+(课程设计推荐JDK 11,兼容性好于17);
  2. 设置系统变量JAVA_HOMEC:\Program Files\Java\jdk-11.0.x(不含bin);
  3. 编辑PATH最前面添加%JAVA_HOME%\bin
  4. 命令行执行java -versionjavac -version,版本号必须一致。

5.3 答辩高频问题应答模板

老师可能追问技术细节,提前准备简明回答:

  • Q:为什么用HikariCP而不是Druid?
    A:HikariCP是目前性能最高的连接池(启动快、CPU占用低),且配置项精简,课程设计聚焦数据库本身,无需Druid的监控埋点等企业级特性。

  • Q:current_enrollment字段冗余,如何保证它和enrollment表数据一致?
    A:所有修改都通过EnrollmentService.enrollStudent()方法,该方法在同一个数据库事务中完成INSERT和UPDATE,利用ACID特性保证强一致性,避免用触发器增加复杂度。

  • Q:如果学生退课,怎么处理?
    A:退课是选课的逆操作,需在事务中:1. 删除enrollment记录;2. 将class_offering.current_enrollment减1;3. 若成绩已录,还需在成绩表中标记“已退课”。这部分代码已在EnrollmentService中预留扩展接口。

  • Q:这个系统能支撑多少并发?
    A:单机MySQL 8.0 + HikariCP默认配置,理论支持约200TPS(每秒事务数)。若需更高并发,可增加连接池大小,但课程设计重点是理解事务本质,而非压测调优。

最后,在src/main/java目录下创建TestMain.java,写一段3行代码的启动入口:

public class TestMain { public static void main(String[] args) { EnrollmentService service = new EnrollmentService(); EnrollmentResult result = service.enrollStudent("2022000001", 1L); System.out.println(result.getMessage()); // 输出"选课成功"或具体错误 } }

运行它,看到控制台打印“选课成功”,意味着从MySQL建表、Java连接、事务控制到业务逻辑的全链路已打通——这才是数据库课程设计最扎实的完成态。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 16:07:17

STM32H750驱动0.96寸OLED的HAL库时序与DMA安全实践

简介&#xff1a;本资源是一套基于STM32H750微控制器的0.96寸OLED显示屏HAL库驱动工程&#xff0c;面向嵌入式开发初学者与STM32H7系列进阶工程师&#xff0c;解决高性能Cortex-M7平台下OLED外设快速适配与稳定显示的核心问题&#xff0c;适用于工业人机界面、便携仪器、IoT终端…

作者头像 李华
网站建设 2026/9/16 16:03:17

MCP Server Omi 维护全指南:从本地开发调试到 PyPI 与 Docker 发布

MCP Server Omi 维护全指南&#xff1a;从本地开发调试到 PyPI 与 Docker 发布 【免费下载链接】Friend AI that sees your screen, listens to your conversations and tells you what to do 项目地址: https://gitcode.com/GitHub_Trending/fr/Friend 导读 本文是 Om…

作者头像 李华
网站建设 2026/9/16 16:00:41

AI学术写作工具:提升效率与规范化的智能助手

1. 项目概述&#xff1a;AI如何重塑学术写作体验第一次听说"书匠策AI"这个工具时&#xff0c;我正在熬夜赶制研究生课程的文献综述。面对堆积如山的参考文献和迫在眉睫的截止日期&#xff0c;那种熟悉的焦虑感又涌了上来——这让我想起本科时用三天时间硬凑出一篇800…

作者头像 李华