简介一套面向高校计算机专业学生的数据库课程设计资料包围绕教务管理系统采用 MySQL 与 Java 实现覆盖需求分析、数据库设计、系统实现及测试等完整流程适合正在学习数据库原理、Java 编程并需要完成课程设计的学生参考使用。压缩包共 28 个文件大小 4.45MB含 9 个 Java 源码文件、9 个编译后的 class 文件、1 份 SQL 建库脚本、2 个 JAR 依赖包以及 properties 配置文件、工程配置和 txt 使用说明等源码、编译输出与说明文档分区清楚便于对照学习与二次开发。这套资料目前已有 2649 人浏览学习说明方案在教学场景中较具参考价值。通过阅读其中设计内容并查看源码可以掌握 MySQL 表结构设计、Java 连接数据库的常用写法理解教务场景中课程、学生、教师等核心数据关系也可以基于现有框架自行扩展功能结合使用说明快速熟悉项目结构与开发思路为真实项目开发积累实践基础。1. 教务管理系统课程设计从选课超员开始理解 MySQL 与 Java 的分工拿到数据库课程设计题目的时候我们组定的是教务管理系统技术栈就是 MySQL 和 Java。一开始觉得无非是建几张学生表、课程表再写个增删改查交差真正动手才发现选课并发超容量、成绩被后提交的人覆盖、删除课程被外键挡住这些问题全都发生在数据库设计边界上。这篇方案会按需求分析、总体架构、数据库设计、Java 实现、避坑排查、验收答辩的顺序展开把每个模块的取舍讲清楚。适合正在做数据库课设、想拿 MySQL 与 Java 串出一套完整系统的读者照着改就能用。2. 需求分析与总体架构设计三类角色、六大模块与三层结构这一章先回答两个问题系统要做什么系统怎么做出来。很多课设失败不是因为代码写不完而是需求从一开始就没定准最后做出来的功能和题目要求对不上。2.1 角色梳理与权限边界教务管理系统通常有三类角色权限边界必须分清否则后面登录模块没法写。角色核心权限典型操作学生查询本人信息登录、浏览可选课程、选课、退课、查看成绩教师维护本人授课数据查看授课列表、录入成绩、修改草稿状态成绩、发布成绩管理员基础数据维护维护学生/教师/课程档案、分配开课任务、重置密码权限判断在 Java 侧做层叫 service方法开头先校验当前登录角色和资源归属。比如教师修改成绩时必须校验该教学班是不是这个老师名下不能只依赖前端按钮隐藏。2.2 功能模块划分与业务规则项目划分为六个功能模块登录认证、基础信息管理、课程与开课管理、选课退课、成绩管理、统计查询。每个模块对应一组用例写文档时建议给用例编号比如 UC-LOGIN-01 表示登录功能第一条用例这样需求文档可以直接对应到后面的测试用例。业务规则要单独列出来这部分是数据库约束和 Java 校验的依据。常见规则包括同一学生对同一教学班不能重复选课选课人数不能超过教学班容量退课要释放容量成绩取值范围是 0 到 100成绩只有从草稿状态变为发布状态后学生才可见只有授课教师能录入和修改成绩。规则建议编号管理BR-01 到 BR-06答辩时评委会问“你有哪些业务约束”直接念编号和含义即可。2.3 技术选型与三层架构技术组合选 Java Swing JDBC MySQL。选 Swing 而不是 Web 是因为课程设计重点在数据库C/S 程序部署和演示都简单评委机器上没有浏览器依赖问题。Java 侧不需要引入 MyBatis直接用 JDBC 写数据访问层能把 SQL 的锅和 Java 的锅分开答“为什么不用框架”时也更有底气。三层结构划分如下界面展示层负责登录框、选课表格、成绩录入面板业务逻辑层集中处理选课事务、成绩状态流转、权限判断数据访问层只负责执行 SQL 并包装结果。连接全部走一个 DBUtil 工具类不让每个界面直接创建连接。工程结构参考后面 Java 实现章节先记住三句话界面不写 SQL事务不进 DAO连接不被多个线程共用。3. 数据库设计六张核心表、三层约束与一组常用脚本数据库设计是整个课设的得分大头。设计稿里要能看到实体关系、字段约束、索引和事务场景而不仅仅是几张建表语句。3.1 核心表结构与实体关系这套方案用六张核心表学生表 student、教师表 teacher、课程表 course、教学班表 teaching、选课成绩表 sc、管理员表 admin。重点理解为什么需要 teaching 表。有的课程设计会把课程和老师直接绑在课程表里这样同一门课不同老师上、不同学期开课就没办法表达。拆出 teaching 表后course 只存课程基础信息teaching 存“某学期某老师在某教室开这门课”sc 表再指向 teaching。这样选课容量放在教学班层面改容量不会影响课程主数据。学生表关键字段包括 id、student_no、name、gender、birth_date、enroll_year、phone、password_hash、status。教师表类似包含 teacher_no、title、department、password_hash。课程表字段包括 course_no、course_name、credit、course_type、status。教学班表字段包括 course_id、teacher_id、semester、capacity、selected_count、classroom、class_time、status。选课成绩表绑定 student_id 和 teaching_idgrade 允许为空表示未出分status 区分已选、已录、已发布三个阶段。3.2 建表 SQL 与关键约束初始化数据库时统一使用 InnoDB 引擎和 utf8mb4 字符集。下面给出三张重点表的建表语句完整脚本能在配套资源里直接导入。CREATE DATABASE IF NOT EXISTS edu_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE edu_db; CREATE TABLE teaching ( id INT AUTO_INCREMENT PRIMARY KEY, course_id INT NOT NULL, teacher_id INT NOT NULL, semester VARCHAR(20) NOT NULL, capacity INT NOT NULL DEFAULT 40, selected_count INT NOT NULL DEFAULT 0, classroom VARCHAR(50), class_time VARCHAR(100), status TINYINT DEFAULT 1, KEY idx_teaching_course (course_id), KEY idx_teaching_teacher (teacher_id), CONSTRAINT fk_teaching_course FOREIGN KEY (course_id) REFERENCES course (id), CONSTRAINT fk_teaching_teacher FOREIGN KEY (teacher_id) REFERENCES teacher (id) ) ENGINEInnoDB; CREATE TABLE sc ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, teaching_id INT NOT NULL, grade DECIMAL(5,2) COMMENT NULL代表未出分, status VARCHAR(20) NOT NULL DEFAULT SELECTED COMMENT SELECTED/GRADED/PUBLISHED, UNIQUE KEY uk_sc_student_teaching (student_id, teaching_id), KEY idx_sc_teaching (teaching_id), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student (id), CONSTRAINT fk_sc_teaching FOREIGN KEY (teaching_id) REFERENCES teaching (id), CONSTRAINT chk_grade CHECK (grade 0 AND grade 100) ) ENGINEInnoDB;这段 SQL 已经体现三个设计点。第一sc 表的唯一索引 uk_sc_student_teaching 从数据库层面保证一个学生对一个教学班只能有一条选课记录这是对抗并发重复选课的兜底。第二教学班的 selected_count 是冗余字段用它做容量判断比每次 count(*) 快代价是必须放在事务里和选课、退课一起更新。第三CHECK 约束在 MySQL 8.0 里会生效5.7 版本会忽略所以 Java 层仍然要校验成绩范围。3.3 事务边界与视图脚本数据库里至少要设计三个事务场景学生选课、退课、成绩发布。选课事务同时做两件事向 sc 表插入选课记录把 teaching 表的 selected_count 加一。退课事务相反删除 sc 记录把 selected_count 减一。成绩发布事务只更新 sc 表的状态不碰成绩原字段。事务边界写在 Java 的 service 层因为选课还要先读 capacity不是简单的一条 INSERT。视图可以简化统计查询建议加一个学生成绩视图把学生、教学班、课程、教师信息拼在一起方便后面各种联合查询复用。CREATE VIEW v_student_score AS SELECT s.student_no, s.name AS student_name, c.course_no, c.course_name, te.semester, t.name AS teacher_name, sc.grade, sc.status FROM sc JOIN student s ON sc.student_id s.id JOIN teaching te ON sc.teaching_id te.id JOIN course c ON te.course_id c.id JOIN teacher t ON te.teacher_id t.id;某个学生要查自己的选课和成绩只需要对 v_student_score 做一次条件查询不用每次重写四个 JOIN。视图不提升性能但能让 Java 侧代码干净很多答辩时说出“我用视图封装了复杂关联查询”是一个明确的加分动作。3.4 初始化数据脚本演示最怕空表所以 init.sql 要带测试数据。管理员、学生、老师、课程、教学班、选课记录全部准备齐全。密码不要明文存放初始化时用 MySQL 自带的 SHA2 函数直接生成摘要。INSERT INTO student (student_no, name, gender, birth_date, enroll_year, phone, password_hash, status) VALUES (S20240001, A同学, M, 2004-03-12, 2024, 13800000001, SHA2(S20240001123456, 256), 1); INSERT INTO teacher (teacher_no, name, title, department, phone, password_hash, status) VALUES (T2024001, B老师, 讲师, 计算机系, 13900000001, SHA2(T2024001123456, 256), 1);这里把“账号 初始密码 123456”拼起来做 SHA-256初始密码可以统一登录后由管理员安排修改。Java 侧登录时用同样算法拼一次再比对两边就能对上初始化脚本也可以重复执行不会出现明文密码被人直接看到的问题。4. Java端实现JDBC连接、事务选课与成绩状态机数据库设计完成后Java 端负责把规则落地。这一章会给出工程结构、连接工具类、选课事务代码、成绩录入和登录验证都是可以直接抄进工程里改的版本。4.1 工程目录与数据库连接层工程结构建议保持简单但分层清晰。我自己习惯这样的目录edu-system/ ├── config/ │ └── db.properties ├── util/ │ └── DBUtil.java ├── dao/ │ ├── TeachingDao.java │ └── ScoreDao.java ├── service/ │ ├── UserService.java │ ├── CourseService.java │ └── ScoreService.java ├── ui/ │ ├── LoginFrame.java │ ├── StudentMainFrame.java │ └── TeacherMainFrame.java └── resource/ └── init.sqlconfig 放数据库连接配置util 放工具类dao 只执行 SQLservice 写业务规则ui 只负责事件转发。DBUtil 的核心代码非常固定public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/edu_db ?useSSLfalsecharacterEncodingutf8serverTimezoneGMT%2B8; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }URL 里的三个参数各有作用。characterEncodingutf8 解决中文乱码serverTimezoneGMT%2B8 解决 MySQL JDBC 驱动对时区的误判useSSLfalse 避免本地开发时出现证书相关告警。关闭连接时用 try-with-resources 或者 finally防止界面操作频繁导致连接数耗尽。4.2 选课事务锁、容量检查与唯一索引兜底选课是最容易翻车的地方代码必须把事务边界画对。下面是一个完整的选课方法核心逻辑是先用 SELECT FOR UPDATE 锁住教学班行再做容量检查和插入public boolean selectCourse(int studentId, int teachingId) { Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); conn.setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED); PreparedStatement lockPs conn.prepareStatement( SELECT selected_count, capacity FROM teaching WHERE id ? FOR UPDATE); lockPs.setInt(1, teachingId); ResultSet rs lockPs.executeQuery(); if (!rs.next()) { conn.rollback(); return false; } int selectedCount rs.getInt(1); int capacity rs.getInt(2); if (selectedCount capacity) { conn.rollback(); return false; } PreparedStatement insertPs conn.prepareStatement( INSERT INTO sc(student_id, teaching_id, grade, status) VALUES (?, ?, NULL, SELECTED)); insertPs.setInt(1, studentId); insertPs.setInt(2, teachingId); insertPs.executeUpdate(); PreparedStatement updatePs conn.prepareStatement( UPDATE teaching SET selected_count selected_count 1 WHERE id ? AND selected_count capacity); updatePs.setInt(1, teachingId); if (updatePs.executeUpdate() 0) { conn.rollback(); return false; } conn.commit(); return true; } catch (Exception e) { try { conn.rollback(); } catch (SQLException ignore) { } return false; } finally { conn.close(); } }这段代码解决三个问题。SELECT FOR UPDATE 让两个事务同时选课时变成串行执行第二个事务会等第一个事务提交后才读到最新人数。UPDATE 语句再次带 selected_count capacity 条件是防止主键锁范围之外的意外。唯一索引 uk_sc_student_teaching 负责最后一道防线万一两个请求都插入了同一条选课记录数据库会抛重复键异常事务回滚。4.3 成绩录入状态机与归属校验成绩模块容易出现两个问题发布后还能被改别的老师能改非自己授课班的成绩。解决方法是把状态写进 UPDATE 条件里String sql UPDATE sc SET grade ?, status GRADED WHERE id ? AND status IN (SELECTED, GRADED) AND teaching_id IN ( SELECT id FROM teaching WHERE teacher_id ? );这条 SQL 的含义很直观只有当前状态是已选或已录草稿时才允许更新发布状态 PUBLISHED 被排除在外同时要求教学班必须属于当前登录教师。executeUpdate 返回 0 时统一提示“成绩不可修改或没有权限”不用继续往下执行。批量录入成绩时把这条 SQL 放到 PreparedStatement 里循环 addBatch每五百条执行一次 executeBatch演示几十个学生的成绩录入不会卡顿。4.4 登录认证哈希存储而不是明文登录逻辑可以复用同一个哈希工具方法账号当盐使用避免两个不同用户同样密码产生相同摘要public static String sha256(String userNo, String password) throws Exception { MessageDigest md MessageDigest.getInstance(SHA-256); byte[] bytes md.digest( (userNo password).getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); }登录时把页面输入的账号密码拼起来哈希再去对应角色表查 password_hash匹配成功则创建会话对象。这样做比存明文强很多也能和初始化脚本里的 SHA2 函数结果对齐。线上系统会推荐 BCrypt 等慢哈希课程设计用 SHA-256 加盐足够解释清楚。5. 常见问题排查中文乱码、并发超选与成绩覆盖的五个翻车点这一章的内容全部来自实际调试过程。课设遇到的绝大多数报错都集中在下面五个位置提前知道怎么排查能省出大量时间。5.1 中文乱码数据库表和连接配置不一致现象是插入中文后数据库里显示问号或者查询条件带中文查不到数据。原因通常是建库字符集和 JDBC 连接字符集不一致比如表用的 latin1连接用的 utf8两边翻译不到一起去。解决方法是建库时统一写成DEFAULT CHARACTER SET utf8mb4连接 URL 加characterEncodingutf8。注意 MySQL 的 utf8 其实是 utf8mb3 的别名如果要做演示数据的 emoji要使用 utf8mb4否则会报字符集过长错误。5.2 数据库连接失败驱动版本和时区提示现象是启动程序就报 ClassNotFoundException或Communications link failure或Unknown database。ClassNotFoundException 说明驱动 jar 没放进 classpathURL 没加时区参数MySQL 8 驱动会报 serverTimezone 错误Unknown database 说明数据库还没执行建库脚本。解决思路是先确认使用 mysql-connector-java 对应版本的驱动再把连接参数补全到像 4.1 节那样完整。我第一次做课设时因为没放驱动 jar 折腾了一晚上最后发现只是构建路径问题这种低级错误最亏。5.3 并发选课超容量先查后插不锁行现象是模拟一百个学生同时选一个容量四十的班最终选课人数超过四十。原因是两个事务同时读到剩余名额大于零判断通过后一起插入。解决方法是采用第 4 章的 SELECT FOR UPDATE 方案把容量判断和插入放进同一个事务同时对 teaching 行加锁。想在演示时证明这一点可以开两个线程同时执行选课方法观察最终 sc 记录数不超过 capacity这时候能在答辩中顺手讲清楚行锁和表锁的区别。5.4 成绩被覆盖丢失更新问题现象是两个老师同时录入同一学生成绩后提交的人把先提交的人的成绩覆盖掉。原因是默认读已提交隔离级别下事务没有锁住成绩行后写者直接覆盖。解决思路分两种如果希望简单就在更新语句里加状态条件只有草稿状态允许修改发布后禁止再改如果希望更通用可以给 sc 表加 version 字段用乐观锁方式实现更新时带版本号条件影响行数为零说明有并发冲突。课设采用前者已经足够答辩时提到乐观锁方案会是很漂亮的加分项。5.5 删除课程外键失败父子表顺序控制现象是删除 course 时数据库报Cannot delete or update a parent row。原因很简单sc 通过教学班引用了 course直接删父表必然被外键挡住。解决方法是先删除 sc 中的选课记录再删除 teaching 中的教学班记录最后删除 course整个过程包在事务里。我一般建议把这种多级删除写成一个管理员的“强制删除”方法而不是直接给界面一个裸的 DELETE 按钮防止误操作把成绩数据一次性清空。生产系统用逻辑删除更稳课设能演示清楚物理外键和删除顺序足够了。6. 验收与答辩一键初始化、并发测试与高频追问应对课设交的不只是代码还有可复现的演示过程。我的验收习惯是把数据库删掉重新执行一遍 init.sql再跑功能测试确保评委看到的就是一个干净环境。验收项通过标准初始化脚本重复执行不报错测试数据完整登录认证三类角色都能登录错误密码被拒绝选课退课超容量被拦截重复选课被拦截退课后空余名额恢复成绩流程教师录入草稿、发布后学生可见发布后不可修改并发一致性多线程抢选不超容量成绩更新不覆盖演示顺序上我一般先登录学生账号选一门课再打开两个窗口同时抢同一门课展示容量不变然后切换教师账号录入成绩并发布最后用学生账号刷新看到成绩。答辩时先讲业务规则再讲表结构和两个关键事务最后讲异常设计。评委常追问为什么不用 MyBatis、为什么事务要放 service 层、为什么不用存储过程做选课按自己的设计意图回答就能站得住。以前我总把验收拖到最后一天结果演示前一晚发现初始化脚本在另一台机器上跑不通。从那以后每次课设我都会先在全新的空数据库里强制走一遍初始化再跑一次并发选课测试确认无误才提交。希望帮到你。本文还有配套的精品资源点击获取