作者:KerensaLiu | 2026年5月 | 数据库原理课程设计
前言
这篇文章将完整记录我从数据库设计、SQL脚本编写、Java后端开发、Swing UI设计到最终部署的全过程。如果你想了解如何把一个数据库课程设计从纸面变成可运行的应用,这篇文章应该能帮到你。
一、作业要求分析
先来看看作业到底要求了什么:
信息管理需求(6类数据)
- 课堂教学基本信息:教师、学生、课程、教学内容、授课地点、授课时间
- 过程管理记录:过程分类(测验/作业/实验/考试等),每类过程的记录
- 过程与教学内容的相关性:每个考核对应哪个知识点
- 过程与培养能力的相关性:每个考核培养了什么能力
- 学生表现情况:每个学生在每次考核中的得分、参与状态
- 过程评价体系:每类过程考核的评分标准
功能需求(6项基础 + n项拓展)
- 基础表的CRUD + 各种约束机制
- 查询某个学生在某次过程考核中的详细信息
- 统计每位学生每次考核的成绩,以及平均成绩
- 按知识点分类统计,找出成绩最优和最差的3人
- 查询未参与考核的学生
- 复杂查询 + 存储过程 + 触发器 + 断言
拓展要求
- 最小函数依赖集 + 范式分析 + 证明
- ER图 + 关系模式映射 + DDL + 10条测试数据
- 自定义拓展信息管理 + 补充函数依赖
- 所有统计创建为视图
- 用户管理 + 完整性约束
我的应对策略
面对这么多要求,我决定做一个完整的桌面应用,而不仅仅是SQL脚本合集。技术栈选择:
| 层 | 技术 | 理由 |
|---|---|---|
| 数据库 | MySQL 8.0 | 课程要求 |
| 后端 | Java 17 + JDBC | 稳定可靠,学校机房兼容 |
| 前端 | Java Swing + 莫兰迪配色 | 原生UI,无需额外安装 |
| 构建 | Maven | 依赖管理方便 |
| 工具 | IntelliJ IDEA + DBeaver | 开发+数据库管理 |
二、数据库设计
2.1 实体识别与关系设计
从需求中我抽象出13个实体/关系:
核心实体:Teacher(教师) —— 上课的人Student(学生) —— 被考核的人ClassInfo(班级) —— 学生所属班级Classroom(教室) —— 上课地点TeachingContent(教学内容)—— 知识点/章节ProcessCategory(过程分类)—— 考核类型ProcessRecord(过程记录)—— 具体的考核活动AbilityTarget(能力目标)—— 培养目标关联实体:Schedule(授课安排) —— 教师+教室+内容+时间StudentPerformance(学生表现)—— 学生+考核+成绩ProcessAbility(过程-能力)—— 考核→能力映射EvaluationCriteria(评价标准)—— 分类→评分标准Users(用户) —— 登录系统
2.2 最小函数依赖集
以几个核心表为例:
Teacher表:
F_min = { teacher_id → teacher_no, name, title, department, email, phone }
教师ID决定所有其他属性,不存在部分依赖和传递依赖。候选键:teacher_id, teacher_no。
StudentPerformance表:
F_min = { perf_id → student_id, record_id, score, status, comment, submit_time }
同时存在自然候选键 (student_id, record_id) → 所有属性。任选一个为主键即可。
ProcessRecord表:
F_min = { record_id → content_id, category_id, title, description, record_date, total_score }
record_id 决定一切,单属性候选键。
2.3 范式分析
所有13张表全部达到 BCNF(Boyce-Codd范式):
-
2NF(无部分依赖):所有表的主键都是单属性(除了ProcessAbility是双属性组合主键),天然满足2NF。对于复合主键表ProcessAbility,非主属性
expected_level完全函数依赖于整个主键(record_id, ability_id),不存在部分依赖。 -
3NF(无传递依赖):以Student表为例——
student_id → class_id → class_name看起来像传递依赖,但实际上class_name不在Student表中(它在ClassInfo表中),Student只存class_id作为外键。这恰恰是规范化的设计。 -
BCNF(所有决定因素都是候选键):我检查了每个表的每个函数依赖,确保决定因素都包含候选键。例如在StudentPerformance表中,
(student_id, record_id)是一个候选键,而perf_id是另一个。所有依赖的决定因素都是候选键。
2.4 ER图(关系描述)

关键关系说明:
- Teacher : Schedule = 1 : N(一个老师有多个时间段的课)
- Student : ProcessRecord = N : M(通过StudentPerformance关联)
- ProcessRecord : AbilityTarget = N : M(通过ProcessAbility关联)
- TeachingContent : ProcessRecord = 1 : N(一个知识点可能有多次考核)
2.5 完整约束设计
除了主键和外键,我还设计了多种用户自定义约束:
-- CHECK约束
CHECK (gender IN ('男', '女')) -- 性别枚举
CHECK (email LIKE '%@%.%') -- 邮箱格式
CHECK (score BETWEEN 0 AND 100) -- 成绩范围
CHECK (weight BETWEEN 0 AND 100) -- 权重范围
CHECK (capacity BETWEEN 1 AND 500) -- 教室容量
CHECK (start_time < end_time) -- 时间逻辑
CHECK (week_day BETWEEN 1 AND 7) -- 星期范围
CHECK (start_week > 0 AND end_week <= 20) -- 周次范围-- 断言(用触发器实现)
-- 过程记录日期必须在学期范围内
-- 过程分类权重总和不超过100%
-- 出勤成绩不超过10分
三、SQL脚本编写
整个数据库的SQL脚本分为7个文件,按顺序执行:
3.1 文件结构
database/
├── 01_database_design.sql ← 数据库创建 + 设计文档
├── 02_create_tables.sql ← DDL:13张表 + 约束 + 索引
├── 03_insert_data.sql ← DML:每表10+条测试数据
├── 04_create_views.sql ← 10个视图
├── 05_stored_procedures.sql ← 7个存储过程
├── 06_triggers.sql ← 9个触发器
└── 07_complex_queries.sql ← 13个复杂查询示例
3.2 10个视图
| 视图 | 用途 | 对应功能 |
|---|---|---|
| v_student_process_detail | 学生考核详情 | 功能(2) |
| v_student_assessment_stat | 分类成绩统计 | 功能(3) |
| v_student_avg_score | 学生平均成绩 | 功能(3) |
| v_content_assessment_stat | 知识点考核统计 | 功能(4) |
| v_content_best_worst | 最优最差排名 | 功能(4) |
| v_absent_students | 未参与学生 | 功能(5) |
| v_category_summary | 分类汇总 | 拓展 |
| v_student_ranking | 成绩排名 | 拓展 |
| v_process_ability | 过程-能力视图 | 拓展 |
| v_full_schedule | 完整课表 | 拓展 |
3.3 7个存储过程
sp_get_student_all_performance(IN student_id) -- 学生全部考核
sp_get_student_process_score(IN student_id, record_id) -- 单次成绩
sp_get_student_summary(IN student_id) -- 综合统计
sp_get_content_best_worst(IN content_id) -- 知识点排名
sp_get_absent_students(IN record_id) -- 缺席查询
sp_upsert_performance(...) -- 成绩录入
sp_get_score_distribution(IN category_id) -- 分数分布
3.4 9个触发器
| 触发器 | 触发时机 | 功能 |
|---|---|---|
| trg_before_insert_performance | BEFORE INSERT | 成绩≤考核总分 |
| trg_before_update_performance | BEFORE UPDATE | 成绩≤考核总分 |
| trg_after_insert_process | AFTER INSERT | 自动为学生创建缺席记录 |
| trg_after_insert_student | AFTER INSERT | 自动创建已有考核的缺席记录 |
| trg_before_delete_student | BEFORE DELETE | 级联删除表现记录 |
| trg_before_insert_category | BEFORE INSERT | 权重总和≤100% |
| trg_before_update_category | BEFORE UPDATE | 权重总和≤100% |
| trg_check_record_date | BEFORE INSERT | 日期在学期范围内 |
| trg_check_attendance_score | BEFORE INSERT | 出勤分≤10 |
四、Java应用架构
4.1 项目结构
src/main/java/com/classflow/
├── Main.java # 入口:登录窗口
├── config/
│ └── DatabaseConfig.java # 数据库连接配置
├── model/ # 实体类 13个
│ ├── Teacher.java
│ ├── Student.java
│ ├── ClassInfo.java
│ ├── Classroom.java
│ ├── TeachingContent.java
│ ├── Schedule.java
│ ├── ProcessCategory.java
│ ├── ProcessRecord.java
│ ├── StudentPerformance.java
│ ├── AbilityTarget.java
│ ├── ProcessAbility.java
│ ├── EvaluationCriteria.java
│ └── User.java
├── dao/ # 数据访问层 11个
│ ├── BaseDAO.java # 通用CRUD基类
│ ├── TeacherDAO.java ~ UserDAO.java
├── ui/
│ ├── MainFrame.java # 主窗口 + 顶部栏
│ ├── theme/
│ │ └── MorandiTheme.java # 莫兰迪配色系统
│ └── panels/
│ ├── BasicInfoPanel.java # 基础信息管理(6子标签)
│ ├── ProcessPanel.java # 过程考核管理(4子标签)
│ ├── QueryPanel.java # 查询与统计(10项查询)
│ └── UserPanel.java # 用户管理
└── util/└── DBUtil.java # JDBC工具类
4.2 架构设计思路
三层架构(简化版):
UI Layer (Swing) → DAO Layer (JDBC) → MySQL↓ ↓Panels BaseDAO基类↓ ↓事件处理 ResultSet映射↓ ↓表格展示 Model对象
为什么不直接用Spring?
课程设计要求的是数据库原理,不是企业级开发。用纯JDBC + Swing可以:
- 清晰展示SQL语句的执行过程
- 没有框架黑盒,每行代码都在掌控之中
- IDEA Community版就能跑,不依赖任何付费工具
4.3 BaseDAO设计
所有DAO继承这个基类,核心方法只有4个:
// 查询多行 → List<T>
protected List<T> query(String sql, Object... params)// 查询单行 → T or null
protected T querySingle(String sql, Object... params)// 更新/删除 → 受影响行数
protected int executeUpdate(String sql, Object... params)// 插入 → 自增ID
protected int executeInsert(String sql, Object... params)
子类只需实现 mapRow(ResultSet rs) 方法,完成ResultSet到Model的映射。这样避免了每个DAO都重复写JDBC的样板代码。
五、莫兰迪配色UI设计
5.1 配色理念
莫兰迪色系以意大利画家乔治·莫兰迪命名,特点是低饱和度、柔和、安静。特别适合需要长时间操作的管理系统——不刺眼,减少视觉疲劳。
5.2 色板
主背景 #F5F0EB 暖米白
面板背景 #E8E0D5 暖浅驼
工具栏 #DDD5C8 浅暖灰
按钮 #C5D0C0 莫兰迪绿
主文字 #5D5348 深棕灰
次要文字 #8B7D6B 中棕灰
边框 #D1CBC0 暖灰
强调色 #D4C5C7 灰粉
辅助强调 #B5C4B1 灰绿
表头 #CCC4B6 浅米棕
5.3 代码实现
public static void styleButton(JButton btn) {btn.setBackground(BUTTON_BG); // #C5D0C0btn.setForeground(BUTTON_TEXT); // #4A423A 深棕文字btn.setFont(FONT_BUTTON); // 微软雅黑 Bold 13btn.setBorder(new EmptyBorder(8, 18, 8, 18));btn.setFocusPainted(false);btn.setCursor(new Cursor(Cursor.HAND_CURSOR));// 鼠标悬停变深btn.addMouseListener(new MouseAdapter() {public void mouseEntered(MouseEvent e) { btn.setBackground(BUTTON_HOVER); }public void mouseExited(MouseEvent e) { btn.setBackground(BUTTON_BG); }});
}
5.4 表格交替行配色
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {@Overridepublic Component getTableCellRendererComponent(...) {if (!isSelected) {c.setBackground(row % 2 == 0 ? Color.WHITE : TABLE_ALT_ROW);}return c;}
});
六、功能实现详解
6.1 登录系统
直接对比用户名和密码(课程项目简化处理):
SELECT u.*, CASE WHEN u.role='teacher' THEN t.name WHEN u.role='student' THEN s.name END AS related_name
FROM Users u
LEFT JOIN Teacher t ON ...
LEFT JOIN Student s ON ...
WHERE u.username=? AND u.password_hash=? AND u.is_active=1
三种角色:admin(全部权限)、teacher(教师)、student(学生)。
6.2 基础信息管理(Tab 1)
6个子标签,每个都是完整的CRUD:
教师管理 | 学生管理 | 班级管理 | 教室管理 | 教学内容 | 授课安排
核心实现——用 Map<String, TabInfo> 确保每个标签页独立管理自己的表格:
private record TabInfo(JTable table, DefaultTableModel model) {}
private Map<String, TabInfo> tabMap = new LinkedHashMap<>();private void loadTableData(String type) {TabInfo ti = tabMap.get(type); // 获取当前标签的表格DefaultTableModel tm = ti.model();tm.setRowCount(0);// 根据type加载不同数据...
}
踩坑记录:一开始我把 table 和 tableModel 作为类字段,每个 createEntityPanel() 都会覆盖它们。结果点"教师管理"时,数据写到了最后一个标签(授课安排)的表格里,看起来像没数据。调试了很久才发现这个问题。
6.3 过程考核管理(Tab 2)
4个子标签:
过程分类 | 过程记录 | 学生表现 | 评价标准
过程分类管理考核类型和权重,权重总和不超过100%(触发器保障)。
学生表现的亮点是使用 ON DUPLICATE KEY UPDATE 实现成绩的插入或更新:
INSERT INTO StudentPerformance (student_id, record_id, score, status, comment)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE score = VALUES(score),status = VALUES(status),comment = VALUES(comment),submit_time = CURRENT_TIMESTAMP
(student_id, record_id) 上有UNIQUE约束,所以同一学生对同一考核只能有一条记录。
6.4 查询与统计(Tab 3)
10项查询功能,左侧按钮列表,右侧表格+详细区域:
| 查询 | SQL要点 |
|---|---|
| 学生考核详细信息 | 多表JOIN:Student → Performance → Record → Content |
| 学生过程考核统计 | 视图 v_student_avg_score,聚合统计 |
| 学生平均成绩排名 | RANK() OVER (ORDER BY overall_avg DESC) |
| 知识点考核统计(降序) | GROUP BY content_id + student,ORDER BY AVG(score) DESC |
| 未参与考核学生 | CROSS JOIN + LEFT JOIN,WHERE sp.status IS NULL |
| 过程分类成绩分布 | CASE WHEN 分段统计 |
| 班级成绩对比 | AVG + MAX + MIN + STDDEV 聚合 |
| 学生能力达成度 | ProcessAbility → AbilityTarget 关联查询 |
| 授课计划查看 | 视图 v_full_schedule |
| 成绩预警 | HAVING AVG(score) < 60 |
为什么用直接SQL而不是视图?
部分查询(如未参与考核)需要动态参数(process_id可选),且DAO的ResultSet映射对视图列不兼容。所以我选择在UI层直接写原生SQL——对于一个课程项目来说,这比过度抽象更实用。
七、开发过程中踩过的坑
7.1 DBeaver的DELIMITER问题
最初存储过程和触发器用了 DELIMITER $$ 语法,但在DBeaver中执行时报语法错误。原因是DBeaver对DELIMITER的处理与mysql命令行不同。
解决:去掉 DELIMITER,每个 CREATE PROCEDURE/TRIGGER ... BEGIN ... END; 作为独立语句执行。
7.2 DROP IF EXISTS的"不存在"中断
DROP TRIGGER IF EXISTS xxx 在触发器不存在时产生一个MySQL Note,DBeaver默认配置下会当成错误中断执行。
解决:首次执行时直接去掉 DROP IF EXISTS,因为触发器本来就不存在。
7.3 标签页表格共享Bug
前面提到过——多个子标签页的 tableModel 被反复覆盖,导致数据写入错误表格。
解决:用 Map<String, TabInfo> 为每个标签页独立存储其表格引用。
7.4 按钮白色文字看不清
最初的按钮配色是莫兰迪绿底 + 白字(#FFFFFF),在浅色背景下对比度不够。
解决:改为浅绿底(#C5D0C0)+ 深棕字(#4A423A),对比度大幅提升。
7.5 Maven依赖下载失败
国内访问Maven Central经常超时,导致MySQL Connector和FlatLaf无法下载。
解决:配置阿里云Maven镜像(~/.m2/settings.xml),清理缓存的 .lastUpdated 文件后重新下载。
八、部署与运行指南
8.1 环境准备
- MySQL 8.0+
- JDK 17+
- IntelliJ IDEA Community
- DBeaver
8.2 数据库初始化
在DBeaver中按顺序执行 database/ 目录下的SQL文件:
01 → 02 → 03 → 04 → 05 → 06
- 01-04:全选 + Ctrl+Enter 即可
- 05-06:右键 → Execute SQL Script(或逐段选择执行)
8.3 配置密码
修改两个文件中的密码为你的MySQL root密码:
DatabaseConfig.java第6行application.properties第4行
8.4 运行
IDEA打开项目 → Maven自动下载依赖 → 运行 Main.java → 登录。
预设账号:
| 角色 | 用户名 | 密码 |
|---|---|---|
| 管理员 | admin | admin |
| 教师 | zhangming | 123456 |
| 学生 | chenxm | 123456 |
九、测试数据一览
为了让所有统计功能都有意义的数据,我插入了12名学生 × 12次过程考核 = 144条表现记录:
成绩分布设计:
- 优秀(90+):陈小明、吴丽华、陈雨桐
- 良好(80-89):林小红、刘思雨、赵雪梅、何晓芳
- 中等(60-79):黄志强、周建国、郑鹏飞
- 不及格(<60):杨浩宇、马天宇
缺席/请假记录:故意在部分学生中插入了 absent 和 excused 状态,确保"未参与考核学生"查询有结果显示。
十、项目总结与收获
10.1 技术收获
-
数据库设计:从需求分析 → 实体识别 → 函数依赖 → 范式分析 → ER图 → DDL,完整走了一遍数据库设计流程。BCNF的理论在实践中的体现变得非常清晰。
-
约束的重要性:CHECK、UNIQUE、外键、触发器这些约束不是纸上谈兵——没有它们,数据质量根本无法保证。
ON DUPLICATE KEY UPDATE这种MySQL特性在成绩录入场景中非常实用。 -
视图的价值:把复杂查询封装成视图,不仅简化了SQL,还让统计逻辑和业务逻辑解耦。
-
Swing开发的细节:Swing虽然古老,但理解了它的线程模型(EDT)、布局管理器、事件分发后,依然能写出体验不错的桌面应用。
10.2 项目统计
数据库表:13张
视图:10个
存储过程:7个
触发器:9个
Java类:35个
SQL总行数:~500行
Java总行数:~2800行
测试数据:144条学生表现记录
Git提交:9次
10.3 改进方向
如果这个项目继续迭代,我会考虑:
- 连接池:目前每次查询都创建新连接,用HikariCP可以大幅提升性能
- 密码加密:目前明文存储和比较,生产环境必须用BCrypt
- 分页查询:数据量大时需要分页加载
- 图表可视化:用JFreeChart给统计结果加上图表
- Spring Boot迁移:如果作为真实项目,后端应该迁移到Spring Boot + MyBatis
10.4 一点感悟
这个项目让我体会到:数据库原理不是背概念,而是在设计每一张表、每一条约束、每一个查询时,自然地运用范式理论、关系代数、事务特性。当一个SQL查询写出80行、跨了6张表还能正确运行时,你会感觉到数据库设计的魅力。
如果你也在做类似的课程设计,希望这篇文章能给你一些参考。
项目仓库:github.com/KerensaLiu709/ClassFlow
技术栈:Java 17 + Swing + MySQL 8.0 + Maven
