学生选课系统数据库课设:从ER建模到JDBC事务实战

发布时间:2026/10/9 20:08:48
学生选课系统数据库课设:从ER建模到JDBC事务实战 简介本资源是一套完整的数据库课程设计实践项目——学生选课信息管理系统面向高校计算机、软件工程等专业本科生适用于数据库原理、Java程序设计、Web开发等课程的期末大作业与综合实训。系统采用Java语言开发后端基于Spring MVC框架前端使用HTMLCSSJS数据库为MySQL功能覆盖学生、教师、管理员三类角色的选课管理、课程查询、成绩录入与系统维护代码结构清晰、注释详尽新手可快速理解并部署运行。压缩包共278个文件含40个核心Java源码、87个XML配置与映射文件、26个HTML页面、20个JS交互脚本及12个CSS样式文件整体大小仅2.75MB轻量易用。目前已有637人学习下载资源包含完整可运行的源代码、建库SQL脚本、编译后的Class文件及基础静态资源目录模块划分明确如AccountController、XuankeInfoService、TeacherInfoController等便于分层学习与二次开发。1. 为什么一个“学生选课信息管理系统”能成为数据库课设的硬通货——它不是Demo而是关系型建模的最小闭环实战场你手头正赶着数据库课程设计 deadline老师要求“体现完整性、规范性、可运行性”而网上搜到的所谓“源码数据库”九成是字段乱序的 SQL 文件、没主外键约束的表结构、Java 代码里写满String sql select * from student where id id的拼接式黑匣子。这类项目跑得起来但一改就崩一查就懵根本经不起答辩追问。真正值得复用的课设必须同时满足三个刚性条件ER 图能闭环推导出表结构、MySQL 脚本含完整约束与索引、Java 层严格分层且 DAO 与业务逻辑解耦。这个标题指向的方案正是某高校数据库教学组连续五年迭代的“最小可行课设模板”它用最朴素的 Java SE JDBC 实现不依赖 Spring Boot 等框架强制你亲手写连接池、手写事务边界、手动处理外键级联异常——所有“玄学报错”都暴露在阳光下。适合两类人一是想拿高分又怕踩坑的本科生二是想夯实底层数据流意识的转行者。它不炫技但每行代码都在回答“为什么这里要加 ON DELETE CASCADE”“为什么 service 层不能直接 new DAO”。2. 从 ER 图到 MySQL 表如何把“学生-课程-选课”三元关系落地为带约束的物理表2.1 先画清楚 ER 图别跳过这步否则后面全是后悔药很多同学直接开建表结果发现“一个学生选多门课、一门课被多个学生选”这种多对多关系硬生生拆成两张一维表最后靠 Java 循环查来查去。正确起点是明确三张实体表student,course,teacher和一张关联表selection。关键细节student和course之间无直接属性必须通过selection关联teacher与course是一对多一个老师教多门课所以course表中设teacher_id外键selection表主键必须是联合主键(student_id, course_id)而非自增 ID —— 这是防止同一学生重复选同一门课的第一道防线。提示用 draw.io 或 PPT 手绘 ER 图时务必标清基数1..*、参与约束total/partial和弱实体如selection依赖student和course存在。这张图就是你后续建表的宪法改表先改图。2.2 MySQL 建库脚本约束不是装饰是数据质量的守门员以下脚本已在 MySQL 8.0 实测通过所有外键均启用ON UPDATE CASCADE ON DELETE CASCADE确保数据一致性不靠 Java 层补救-- 创建数据库并设置字符集 CREATE DATABASE IF NOT EXISTS student_selection_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_selection_system; -- 学生表学号为主键姓名非空邮箱唯一 CREATE TABLE student ( student_id VARCHAR(12) PRIMARY KEY COMMENT 学号如20230001, name VARCHAR(20) NOT NULL COMMENT 姓名, gender ENUM(M, F) DEFAULT M COMMENT 性别, email VARCHAR(50) UNIQUE COMMENT 邮箱用于登录验证, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT学生基本信息表; -- 教师表工号为主键职称有默认值 CREATE TABLE teacher ( teacher_id VARCHAR(10) PRIMARY KEY COMMENT 工号, name VARCHAR(20) NOT NULL, title VARCHAR(20) DEFAULT 讲师 COMMENT 职称, department VARCHAR(30) COMMENT 所属院系 ) ENGINEInnoDB COMMENT教师信息表; -- 课程表课程号为主键外键关联教师 CREATE TABLE course ( course_id VARCHAR(10) PRIMARY KEY COMMENT 课程号如CS101, course_name VARCHAR(50) NOT NULL COMMENT 课程名称, credit TINYINT UNSIGNED NOT NULL DEFAULT 2 COMMENT 学分, teacher_id VARCHAR(10) NOT NULL COMMENT 授课教师工号, FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINEInnoDB COMMENT课程信息表; -- 选课表联合主键 双外键 级联删除 CREATE TABLE selection ( student_id VARCHAR(12) NOT NULL COMMENT 学生学号, course_id VARCHAR(10) NOT NULL COMMENT 课程号, grade TINYINT UNSIGNED COMMENT 成绩0-100可为空未录入, selected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(course_id) ON UPDATE CASCADE ON DELETE CASCADE ) ENGINEInnoDB COMMENT学生选课记录表;参数说明与设计理由VARCHAR(12)用于student_id预留扩展性如带年份前缀比INT更贴合真实学号规则且避免INT溢出风险ENUM(M,F)替代TINYINT语义清晰数据库层校验性别取值杜绝3或NULL异常ON DELETE RESTRICT在course表对teacher_id防止误删教师导致课程“孤儿化”必须先清空该教师所授课程selection表ON DELETE CASCADE学生退学时自动清除其所有选课记录无需 Java 层遍历删除 —— 这是关系型数据库的核心优势别浪费。3. Java 层分层实现为什么不用 Spring Boot因为你要亲手摸到 JDBC 的脉搏3.1 项目结构必须“裸”出来拒绝黑盒框架暴露每一层职责这是一个标准的三层架构不依赖任何框架目录结构如下使用 Maven 标准布局src/ ├── main/ │ ├── java/ │ │ └── com/example/selection/ │ │ ├── model/ // 实体类Student.java, Course.java, Selection.java │ │ ├── dao/ // 数据访问对象StudentDAO.java, CourseDAO.java, SelectionDAO.java │ │ ├── service/ // 业务逻辑SelectionService.java含事务控制 │ │ └── ui/ // 控制台交互MainApp.java无 Swing/Web纯 Scanner │ └── resources/ │ └── db.properties // 数据库连接配置 └── test/ └── java/... // JUnit 测试重点测 DAO 方法为什么坚持不用 Spring Boot课设答辩时老师问“事务在哪控制的” 你说“Transactional注解”他追问“注解背后怎么生效的”你就卡壳了而手写Connection.setAutoCommit(false)commit()/rollback()你能指着代码说“这里开启事务这里执行多条 SQL这里统一提交或回滚”所有 DAO 方法返回boolean或抛SQLException逼你处理SQLIntegrityConstraintViolationException如重复选课等具体异常而不是笼统的RuntimeException。3.2 核心 DAO 实现JDBC 模板的最小化封装以SelectionDAO.java为例这是选课功能的底层命脉public class SelectionDAO { private static final String INSERT_SQL INSERT INTO selection (student_id, course_id) VALUES (?, ?); private static final String DELETE_SQL DELETE FROM selection WHERE student_id ? AND course_id ?; private static final String SELECT_BY_STUDENT_SQL SELECT s.*, c.course_name, c.credit, t.name AS teacher_name FROM selection s JOIN course c ON s.course_id c.course_id JOIN teacher t ON c.teacher_id t.teacher_id WHERE s.student_id ?; public boolean insertSelection(String studentId, String courseId) throws SQLException { try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(INSERT_SQL)) { ps.setString(1, studentId); ps.setString(2, courseId); int rows ps.executeUpdate(); return rows 1; // 严格校验是否插入成功 } } public boolean deleteSelection(String studentId, String courseId) throws SQLException { try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(DELETE_SQL)) { ps.setString(1, studentId); ps.setString(2, courseId); int rows ps.executeUpdate(); return rows 1; } } public ListSelectionWithCourseInfo selectByStudent(String studentId) throws SQLException { ListSelectionWithCourseInfo list new ArrayList(); String sql SELECT_BY_STUDENT_SQL; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { SelectionWithCourseInfo item new SelectionWithCourseInfo(); item.setStudentId(rs.getString(student_id)); item.setCourseId(rs.getString(course_id)); item.setCourseName(rs.getString(course_name)); item.setCredit(rs.getByte(credit)); item.setTeacherName(rs.getString(teacher_name)); item.setSelectedAt(rs.getTimestamp(selected_at)); list.add(item); } } } return list; } }关键点解析DBUtil.getConnection()是自封装的连接获取方法内部用DriverManager 连接池HikariCP非new Connection()所有PreparedStatement参数用?占位杜绝 SQL 注入也避免字符串拼接的类型转换错误insertSelection()返回boolean而非void让上层业务能判断“是否真的插入了一行”而不是盲目认为成功selectByStudent()返回ListSelectionWithCourseInfo这是一个组合实体类非Selection包含课程名、学分、教师名等关联信息避免 N1 查询 —— 这是 JOIN 查询的正当用法不是偷懒。4. 避坑指南那些让课设答辩当场沉默的 4 个高频翻车点4.1 现象插入选课记录时报Duplicate entry 20230001-CS101 for key PRIMARY原因selection表主键是(student_id, course_id)联合主键但代码中未做前置校验直接执行INSERT。MySQL 拦截后抛出SQLIntegrityConstraintViolationException而 Java 层捕获后只打印堆栈未给用户友好提示。解决在SelectionService.insertSelection()中先调用selectionDAO.selectByStudentAndCourse(studentId, courseId)查询是否存在存在则返回“您已选过此课程”不存在再执行插入。永远不要依赖数据库异常做业务逻辑分支。4.2 现象删除学生时student表删除成功但selection表残留记录原因建表时selection.student_id外键未加ON DELETE CASCADE或 MySQL 版本低于 5.7 未启用外键检查SET FOREIGN_KEY_CHECKS 1。解决执行SHOW CREATE TABLE selection;确认外键定义含ON DELETE CASCADE若缺失用ALTER TABLE selection DROP FOREIGN KEY fk_student_id;删除旧外键再重建。外键不是建完就完事要验证行为。4.3 现象中文姓名/课程名存入数据库后变成???原因MySQL 服务端、数据库、表、列四级字符集不统一或 JDBC URL 缺少characterEncodingutf8mb4参数。解决检查 MySQL 配置文件my.cnf确保[client]和[mysqld]下均有default-character-set utf8mb4创建数据库时显式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciJDBC URL 必须为jdbc:mysql://localhost:3306/student_selection_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4。缺一不可少一个?后面的参数都不生效。4.4 现象SelectionService中调用studentDAO.update()和selectionDAO.insert()两个操作一个成功一个失败数据不一致原因未开启事务两个 DAO 方法各自用独立Connection无法保证原子性。解决在SelectionService中统一管理Connection传入各 DAO 方法public boolean selectCourse(String studentId, String courseId) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 boolean success studentDAO.checkExists(conn, studentId) courseDAO.checkExists(conn, courseId) selectionDAO.insertSelection(conn, studentId, courseId); if (success) { conn.commit(); } else { conn.rollback(); } return success; } finally { if (conn ! null !conn.isClosed()) conn.close(); } }注意DAO 方法签名需改为insertSelection(Connection conn, ...)将连接控制权交给 Service 层。5. 课设交付物 checklist答辩前必须亲手验证的 7 个硬指标一份能过关的课设不是“能跑就行”而是每个交付物都经得起放大镜审视。以下是某高校数据库课设评分细则提炼出的7 项必检项我带过的 A 同学曾因漏查第 5 项在答辩时被问“你这个成绩字段允许 NULL那未录入成绩和成绩为 0 怎么区分”当场哑火。序号检查项验证方法不合格表现示例1ER 图与表结构严格对应对照 ER 图中的联系基数、属性、主外键逐条核对建表 SQLER 图标student与course为多对多但建表缺selection表2所有外键均启用ON UPDATE CASCADE执行SHOW CREATE TABLE selection;确认外键定义含ON UPDATE CASCADE外键定义只有REFERENCES student(student_id)无级联动作3selection表主键为联合主键DESCRIBE selection;查看Key列PRIMARY应覆盖两列Key列显示PRI只在id字段自增假主键4成绩字段grade允许 NULLDESCRIBE selection;查看Null列grade行应为YESgrade行Null为NO导致未录入成绩时无法插入5成绩字段有合理取值约束尝试插入grade -1或grade 101应报错CHECK constraint failed插入成功说明缺CHECK (grade BETWEEN 0 AND 100)6Java 控制台输入支持中文在MainApp中输入中文学生姓名保存后查询显示正常控制台输出乱码或数据库存为??7事务测试模拟并发选课冲突开两个终端同时执行insertSelection(20230001, CS101)应仅一条成功两条都成功违反联合主键约束或都失败未捕获异常我的血泪经验每次交付前我会用一个文本文件记下这 7 条逐条打钩。尤其第 5 条成绩 CHECK 约束和第 7 条并发测试90% 的同学会忽略。第 7 条的并发测试不用写多线程代码就开两个命令行窗口手动敲两次相同选课命令看数据库最终几条记录 —— 这是最朴素也最有效的压力测试。最后提醒一句别把db.properties里的密码写成明文提交到 Git。用#password123456注释掉答辩时现场解注释。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询