news 2026/10/9 20:08:29

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
学生选课系统数据库课设:从ER建模到JDBC事务实战

简介:本资源是一套完整的数据库课程设计实践项目——学生选课信息管理系统,面向高校计算机、软件工程等专业本科生,适用于数据库原理、Java程序设计、Web开发等课程的期末大作业与综合实训。系统采用Java语言开发,后端基于Spring MVC框架,前端使用HTML+CSS+JS,数据库为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 ) ENGINE=InnoDB 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 '所属院系' ) ENGINE=InnoDB 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 ) ENGINE=InnoDB 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 ) ENGINE=InnoDB 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 List<SelectionWithCourseInfo> selectByStudent(String studentId) throws SQLException { List<SelectionWithCourseInfo> 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()返回List<SelectionWithCourseInfo>:这是一个组合实体类(非Selection),包含课程名、学分、教师名等关联信息,避免 N+1 查询 —— 这是 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 缺少characterEncoding=utf8mb4参数。
解决:

  1. 检查 MySQL 配置文件my.cnf,确保[client]和[mysqld]下均有default-character-set = utf8mb4;
  2. 创建数据库时显式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  3. JDBC URL 必须为:jdbc:mysql://localhost:3306/student_selection_system?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4。缺一不可,少一个?后面的参数都不生效。

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。用#password=123456注释掉,答辩时现场解注释。希望帮到你。

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

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

数据库课设实战:小型超市管理系统源码与设计详解

简介&#xff1a;一套面向数据库课程设计、毕业设计及实训项目的小型超市管理系统完整工程&#xff0c;基于Java与Vue技术栈实现&#xff0c;涵盖前端页面、后端业务逻辑、数据库脚本与配置说明。项目经严格测试运行正常&#xff0c;答辩评审平均分达96分&#xff0c;适合需要快…

作者头像 李华
网站建设 2026/10/9 19:57:21

workbuddy-to-dsh 迁移教程:Node.js 环境搭建与 dsh 归档导入实操

1. 从零上手 workbuddy-to-dsh&#xff1a;这个工具到底解决什么问题第一次看到workbuddy-to-dsh这个名字&#xff0c;很多人会以为是某个小众的 npm 包&#xff0c;或者某个只在 GitHub 上挂了半年就没人维护的实验性项目。实际上&#xff0c;它解决的是一个非常具体、非常痛的…

作者头像 李华
网站建设 2026/10/9 19:56:58

课程论文扎堆写不完?学生党必备高效写作神器

末季到了&#xff0c;每个大学生都经历过这样的噩梦&#xff1a;五六门课同时交论文&#xff0c;每篇要求两三千字&#xff0c;还有两个小组报告要准备&#xff0c;图书馆座无虚席&#xff0c;宿舍里灯火通明。课程论文虽然不像毕业论文那样要求严格&#xff0c;但数量多、时间…

作者头像 李华
网站建设 2026/10/9 19:36:57

水下图像增强实战:从颜色校正到下游任务验证的完整方案

简介&#xff1a;这份资源是面向深度学习与计算机视觉方向的毕业设计、课程设计及人工智能学习者打造的水下图像增强系统完整实现包&#xff0c;针对水下图像因光线衰减、散射导致的低对比度、低亮度与色彩失真问题&#xff0c;提供从数据到模型再到应用入口的一站式方案。压缩…

作者头像 李华
网站建设 2026/10/9 19:34:58

一点接入多云:用统一接入网关根治业务系统对接失控

先聊一个我最近一直在琢磨的问题&#xff1a;你手上同时有七八个业务系统要对接外部平台&#xff0c;每个平台的接口风格完全不一样&#xff0c;有SOAP的、有REST的、有回调主动推送的&#xff0c;还有那种只给你一个FTP让你定时丢文件的。研发团队才几个人&#xff0c;每天被各…

作者头像 李华
网站建设 2026/10/9 19:34:32

C#反编译工具实战指南:从IL解析到可信代码还原

简介&#xff1a;本资源为开源免费的C#/.NET反编译工具ILSpy独立安装包&#xff0c;面向.NET开发者、逆向学习者及软件安全分析初学者&#xff0c;用于快速查看、分析和理解第三方.NET程序集&#xff08;如DLL、EXE&#xff09;的源码逻辑与结构。ILSpy由iCSharpCode团队开发&a…

作者头像 李华