ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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用JavaMySQL落地学生选课系统直击数据库课程设计三大硬需求很多同学拿到“学生选课信息管理系统”课题时第一反应是百度搜源码、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 入学年份 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 所属院系 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 课程表课程编号唯一但同一课程可多学期开设 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 课程简介 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 开课表解决“同一课程多学期”问题记录具体开班信息 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 同一课程同一学期只能开一班 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 按开课班查成绩时加速 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 entry3. Java层核心实现用JDBCHikariCP构建可验证的事务边界3.1 为什么不用Spring Boot课程设计要暴露底层细节课程设计评分标准明确要求“体现数据库连接、事务控制、SQL编写能力”。若直接用Spring Data JPA老师会质疑“你写的Repository接口底层SQL谁生成的事务传播行为怎么配置的”因此本方案采用原生JDBC HikariCP连接池所有SQL手写事务手动控制确保每行代码都可追溯。3.2 数据库连接与连接池配置src/main/resources/db.properties文件内容明文配置便于调试jdbc.urljdbc:mysql://localhost:3306/student_course?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.passwordyour_password jdbc.maxPoolSize10 jdbc.minIdle2 jdbc.connectionTimeout30000 jdbc.idleTimeout600000 jdbc.maxLifetime1800000Java初始化连接池代码DatabaseConnection.javapublic 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, 张三, 男, zhangsanuniv.edu, 计算机科学, 2022), (2022000002, 李四, 女, lisiuniv.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_id1 mysqlslap -u root -p --concurrency2 --iterations1 \ --queryINSERT INTO enrollment (stu_id, offering_id) VALUES (2022000003, 1), (2022000004, 1); \ --create-schemastudent_course注意执行前需确保class_offering中offering_id1的max_capacity设为1。若事务正确最终enrollment表中只会有一条记录另一条因违反唯一约束或名额超限被回滚证明并发控制有效。5. 课程设计避坑指南从环境配置到答辩话术5.1 MySQL 8.0安装与字符集陷阱很多同学在Windows上安装MySQL 8.0后遇到中文乱码根源在于默认字符集非utf8mb4。必须在my.ini中显式配置[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci [client] default-character-setutf8mb4重启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不可用。正确配置顺序下载JDK 11课程设计推荐JDK 11兼容性好于17设置系统变量JAVA_HOME为C:\Program Files\Java\jdk-11.0.x不含bin编辑PATH最前面添加%JAVA_HOME%\bin命令行执行java -version和javac -version版本号必须一致。5.3 答辩高频问题应答模板老师可能追问技术细节提前准备简明回答Q为什么用HikariCP而不是DruidAHikariCP是目前性能最高的连接池启动快、CPU占用低且配置项精简课程设计聚焦数据库本身无需Druid的监控埋点等企业级特性。Qcurrent_enrollment字段冗余如何保证它和enrollment表数据一致A所有修改都通过EnrollmentService.enrollStudent()方法该方法在同一个数据库事务中完成INSERT和UPDATE利用ACID特性保证强一致性避免用触发器增加复杂度。Q如果学生退课怎么处理A退课是选课的逆操作需在事务中1. 删除enrollment记录2. 将class_offering.current_enrollment减13. 若成绩已录还需在成绩表中标记“已退课”。这部分代码已在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连接、事务控制到业务逻辑的全链路已打通——这才是数据库课程设计最扎实的完成态。本文还有配套的精品资源点击获取
返回列表