news 2026/10/1 6:59:39

Java+Swing+MySQL教务管理系统开发实战:从数据库设计到JTable联动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java+Swing+MySQL教务管理系统开发实战:从数据库设计到JTable联动

简介:Java+Swing+MySQL三件套组合实现的学校教务管理系统,面向Java初学者、课程设计与期末大作业场景。项目涵盖学生信息管理、课程安排、成绩录入等核心模块,难度适中,适合学习者通过完整项目掌握Swing界面开发与MySQL数据库交互的实战流程。压缩包共39个文件,以25个Java源码文件为主,配合XML界面布局、SQL建库脚本、PNG资源及MD说明文档,整体仅240KB,轻量易部署。已有219人学习使用,源码经本地编译验证可运行,评审得分98分,代码结构清晰、注释完整,可直接导入IDE运行。对于需要完成课程设计或快速上手Swing项目的读者而言,这是一份具备较强参考价值的完整范例。

1. 用 Java+Swing+Mysql 做学校教务管理系统:期末大作业的稳妥解

如果你正在找 Java 课程设计源码,大概率见过两种极端:一种是 Spring Boot + Vue 的“大厂风”,跑起来要装 Node、配 Maven 镜像、连 Redis,光环境就能折腾一下午;另一种是纯 JSP + Servlet 的老古董,界面丑到答辩时没底气。Java+Swing+Mysql 实现学校教务管理系统恰好落在中间——启动依赖只有 JDK 和 MySQL,界面用 JFrame 和 JTable 直接绘制,增删改查的数据流肉眼可见,断点打在事件监听器里就能讲清楚。这套资源是完整的 Maven 工程,包含 pom.xml、educational_admin.sql 以及 src/test/main 目录,本地编译过可运行,评审分能到 98,难度适中,适合正在做期末大作业或课程设计的在校学生,也适合想用 Swing 练 JDBC 事务和界面拆分的初学者。

2. 数据模型先行:从 educational_admin.sql 看懂五张表的教务关系

拿到压缩包先别急着点运行,第一件事是打开 educational_admin.sql。教务管理系统看起来功能多,拆到数据库层面就是“人、课、成绩”三件事。这个 SQL 文件把关系理顺了,后面所有 Swing 界面和 JDBC 代码都是围着它转。

2.1 表结构拆解:用户表、学生表、课程表、选课表与成绩表

我先讲最核心的表结构设计。一个合格的教务系统最少需要五张表,这套 SQL 里也是按这个思路拆的。

表名核心字段作用
sys_userid, username, password, role登录账号,区分管理员和普通用户
sys_studentid, student_no, name, gender, class_name学生基本信息
sys_courseid, course_no, course_name, credit, teacher课程基本信息
sys_student_courseid, student_id, course_id选课关系表
sys_scoreid, student_course_id, score成绩记录,挂在选课关系下

这个拆法的关键是“选课关系表”独立存在。你可能会问:为什么不把 score 直接存到 sys_student_course 里?答案是成绩并不是选课的唯一信息,一个学生选了一门课,可能还没考试,也可能补考两次,单独一张成绩表,后续查询和统计都更灵活。课程表不冗余存放学生姓名,学生表也不冗余存放课程名,全部通过 ID 关联,这就是最基本的数据库范式设计,答辩时老师问到“为什么这样拆”,你可以从数据冗余和更新异常两个点去答。

2.2 种子数据初始化:admin 账号、测试密码与演示数据的边界

SQL 文件后半段通常是初始化数据。教务系统登录入口是管理员账号,初始化脚本里一般会预置一个 admin 用户。见下面这段常用写法:

CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO sys_user (username, password, role) VALUES ('admin', MD5('123456'), 0);

逻辑说明:sys_user 表把 password 设为 VARCHAR(64),是因为 MD5 加密后的字符串长度固定是 32 位,64 位给后续升级留了余地。role 用 TINYINT 而不是字符串,是因为程序里判断角色用数字比 equals 字符串性能更好,也更简洁。创建时间直接给 DEFAULT CURRENT_TIMESTAMP,省得每插入一条都要手动 new Date()。

参数说明:这里密码用 MD5 是演示项目的常见做法,答辩时你能讲清楚 MD5 是不可逆哈希、数据库泄露后不能直接还原密码,就已经达标了。如果以后做企业级项目,换成 BCrypt,加盐成本更高。要注意 MySQL 8.0 里ENGINE=InnoDB DEFAULT CHARSET=utf8mb4这两句建议保留,utf8mb4 才能完整存储 emoji 和生僻字,你用 utf8 的话,学生姓名里带个“䶮”字就会报错或落库成乱码。

3. Swing 界面层:JFrame 主窗体、JTable 刷新与 JDialog 传值

Swing 项目的核心难点不在语法,而在“界面组件怎么和数据联动”。很多新手把 JFrame 当成一个死画板,画完控件后就不知道怎么把数据库里的数据摆上去。这一章我从主窗体布局开始,讲透三个高频操作。

3.1 主窗体骨架:JMenuBar 菜单栏与 CardLayout 卡片切换

教务管理系统打开后,左边是菜单,右边是内容区。Swing 里实现“菜单点击切换页面”最顺手的方案是 JMenuBar 加 CardLayout 布局管理器。CardLayout 的工作原理是把多个面板叠在一起,每次只显示其中一张,切换时根据 key 找到对应面板。

private JPanel cardPanel; private CardLayout cardLayout; private void initMenu() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("教学管理"); JMenuItem itemCourse = new JMenuItem("课程管理"); itemCourse.addActionListener(e -> switchPage("course")); menu.add(itemCourse); JMenuItem itemScore = new JMenuItem("成绩录入"); itemScore.addActionListener(e -> switchPage("score")); menu.add(itemScore); menuBar.add(menu); setJMenuBar(menuBar); } private void switchPage(String key) { cardLayout.show(cardPanel, key); }

逻辑说明:每个 JMenuItem 绑定的 ActionListener 里只做一件事——调用 cardLayout.show()。switchPage() 接收的字符串是构造 cardPanel 时 add 面板用的标识名,必须和 add(coursePanel, "course") 里的 key 完全一致,否则运行时会报 IllegalArgumentException。把菜单初始化和页面切换拆成两个方法,是让你在后续扩展时只改 menu 部分,不用动 switchPage。

参数说明:JMenuBar 放的是顶层菜单,如果还想做工具栏按钮,可以再加 JToolBar;但这套课设项目用菜单栏就够了,别把界面做复杂,答辩演示时反而容易手忙脚乱。这里我一般会用 Lambda 表达式写 ActionListener,代码短,但老项目里习惯用匿名内部类的写法,考试时如果老师要求 Java 8 之前的语法,你需要知道两种写法等价。

3.2 JTable 与 TableModel:手动刷新翻车的高频现场

JTable 是 Swing 项目里最常用的表格组件。新手最容易犯的错是:查完数据库后直接调用 table.repaint(),以为界面会自己更新,结果表格纹丝不动。原因是 JTable 本身不存数据,它只是负责把 TableModel 里的数据画出来,数据变了,必须让 TableModel 知道。

DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); String sql = "SELECT course_id, course_name, credit, teacher FROM sys_course"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { model.addRow(new Object[]{ rs.getInt("course_id"), rs.getString("course_name"), rs.getInt("credit"), rs.getString("teacher") }); }

逻辑说明:setRowCount(0) 的作用是清空表格原有行,相当于重置数据源。然后逐条把 ResultSet 里的记录填进 model.addRow()。这里有个细节:addRow 接收的是 Object[],所以 rs.getInt() 返回的 int 会自动装箱成 Integer,Swing 单元格能正常显示。如果不清空直接 addRow,数据会无限累积,每次点查询都会在表格底部追加一批重复行,这是我见过翻车最多的点。

参数说明:JTable 默认的 TableModel 是 DefaultTableModel,用(DefaultTableModel) table.getModel()强转的前提是你创建表格时没有自定义模型。如果你用new JTable(data, columnNames)构造过,getModel() 返回的就是 DefaultTableModel,可以直接转。这套项目里课程管理、学生管理、成绩管理三个页面都是同样的套路,把查询方法和刷新表格封装成一个方法,每次增删改后调用它,这才是正解。

3.3 JDialog 模态窗口:新增与编辑如何把结果传回主界面

“新增课程”或“编辑学生信息”这类功能,Swing 里的标准做法是弹出一个 JDialog 子窗口。子窗口填完数据点保存,主窗口的表格要立刻刷新。这涉及两个问题:子窗口怎么关闭、主窗口怎么知道“操作成功了”。我一般用模态 JDialog 加一个布尔标志位解决。

AddCourseDialog dialog = new AddCourseDialog(this); dialog.setModal(true); dialog.setVisible(true); if (dialog.isSucceeded()) { refreshCourseTable(); }

逻辑说明:setModal(true) 让子窗口成为模态窗口,代码执行到 setVisible(true) 时会阻塞,直到子窗口关闭才继续往下走。isSucceeded() 是子窗口里的一个 getter,保存按钮点击后、dispose() 之前把它设为 true。主窗口拿到 true 就刷新表格,拿到 false 就不做任何操作。这个模式比用回调监听器更容易让答辩老师理解,也规避了 Swing 线程问题——刷新操作始终在事件分发线程里执行,不需要额外处理并发。

参数说明:AddCourseDialog 构造时传入 this 是把它挂到主窗口上,这样最小化主窗口时对话框会一起最小化,比较符合桌面应用习惯。子窗口里保存按钮的监听器要做两件事:先执行 JDBC 插入,成功后把 succeeded 置为 true,再 dispose()。顺序别颠倒,否则会出现结果是 false 但数据已经写进数据库的诡异问题。

4. JDBC 细节:连接串参数、PreparedStatement 与事务提交边界

Swing 界面做得再漂亮,和 MySQL 打交道这一层出了问题是看不见摸不着的。这一章聚焦 JDBC,三个高频问题:连接串参数配不对、SQL 拼接出漏洞、事务没提交导致数据丢失。教学项目中这三个点也是评分差异所在。

4.1 MySQL 连接串的三件套:useSSL、serverTimezone、characterEncoding

我见过太多课设代码把连接串写成一行裸的jdbc:mysql://localhost:3306/xxx,在 MySQL 5.7 上跑得通,换到 MySQL 8.0 就报时区异常,换到带 SSL 的服务器上又报证书错误。这段连接串参数值得你抄进笔记:

String url = "jdbc:mysql://localhost:3306/educational_admin" + "?useSSL=false" + "&serverTimezone=Asia/Shanghai" + "&characterEncoding=utf8"; String user = "root"; String password = "123456"; Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, user, password);

逻辑说明:useSSL=false 是关闭 SSL 加密通道,本地开发 MySQL 默认没配证书,不关会报 SSL 相关警告甚至连接失败。serverTimezone=Asia/Shanghai 是给 MySQL 8.0 用的,8.0 对时区敏感,不指定会报 “The server time zone value 'CST'” 的运行时异常。characterEncoding=utf8 是让 JDBC 客户端和 MySQL 服务端用统一的字符集传输中文,少了它,数据库中即使存的是 UTF-8,查询出来的中文也可能是问号。

参数说明:MySQL 5.7 和 8.0 的驱动类名不同,8.0 用的是com.mysql.cj.jdbc.Driver,老项目里写的com.mysql.jdbc.Driver在 8.0 驱动下已经标记过期,部分版本会直接报 ClassNotFoundException。所以 pom.xml 里如果引的是 mysql-connector-java 8.x,Class.forName 必须写带 cj 的那个类。

4.2 PreparedStatement 防拼接:不只是防注入

登录功能最常见的写法是把用户名密码直接拼进 SQL 字符串,然后 Statement 执行。这在课设源码里很常见,但属于明确的扣分点。用 PreparedStatement 改一下,代码量没有增加,安全性完全不一样。

String sql = "SELECT * FROM sys_user WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery(); if (rs.next() && rs.getInt("role") == 0) { // 管理员登录成功 } else { // 密码错误或无权限 }

逻辑说明:PreparedStatement 先把 SQL 模板发给 MySQL 预编译,然后用 setString 把参数值单独传进去。这样即使 username 里含有单引号或OR 1=1,也会被当作纯字符串字面量,而不是 SQL 代码。除了防注入,预编译还能让数据库缓存执行计划,同一条 SQL 连续执行多次时性能比 Statement 高,这在导入上千条学生成绩时能直接感受到差别。

参数说明:setString 的下标从 1 开始,不是 0,写错会报 index out of range。用户名和密码的取值来自 Swing 里 JTextField.getText() 和 JPasswordField.getPassword(),注意 JPasswordField 拿到的是 char[] 而不是 String,这是刻意设计——字符串是不可变的,会留在内存里,char[] 用完可以手动清空。

4.3 事务边界:批量改课表时忘记 commit 的后果

批量录入成绩或者批量调整课表,如果每一条都是自动提交,中途某条失败,前面成功的部分就留在数据库里了,数据和演示都对不上。事务的正确姿势是在连接层面关掉自动提交,执行完后再统一 commit。

conn.setAutoCommit(false); try { for (ScoreItem item : scoreList) { PreparedStatement ps = conn.prepareStatement( "UPDATE sys_score SET score = ? WHERE id = ?"); ps.setInt(1, item.getScore()); ps.setInt(2, item.getId()); ps.executeUpdate(); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); }

逻辑说明:setAutoCommit(false) 是事务的开关。循环执行完所有 UPDATE 后调用 commit(),数据才真正落库。如果循环到第 10 条时数据库连接断开或者 SQL 写错,catch 块中的 rollback() 会把前面 9 条执行过的操作全部撤销,数据库回到开始前的状态。这个机制保证“要么全部成功,要么全部失败”。finally 里的 setAutoCommit(true) 是必须的,避免归还连接池时把非自动提交状态带到下一个使用者。

参数说明:事务范围要控制在这个循环内,别把用户登录、窗口打开等无关操作也包进来,否则连接占用时间过长,多窗口并发操作时会出现锁等待超时。这套教务系统的成绩录入场景,一次事务只处理一个班级的成绩列表,是合理的边界。

5. 避坑排查:ClassNotFoundException、中文乱码、启动秒退与表格不刷新

这一章把课设过程中最容易踩的五个坑列出来,每条都按“现象 → 原因 → 解决”写。我先说结论:这些坑大部分不是代码逻辑问题,而是环境配置和组件使用方式问题,因此打包源码时,环境说明和代码文件一样重要。

5.1 现象:启动主类后秒退,控制台报 ClassNotFoundException

双击运行一个带界面的 jar 包,窗口一闪就消失,控制台报java.lang.NoClassDefFoundError: com/mysql/cj/jdbc/Driver。第一次遇到时我也懵了很久,后来才发现是 MySQL 驱动 jar 没有打进 classpath。原因:你在 IDEA 里运行没问题,是因为 IDEA 把你项目里的 lib 目录加入了编译路径;打成 jar 或者直接命令行运行时,驱动包没有被包含进去。解决:在 pom.xml 里显式声明 mysql-connector-java 依赖,用 Maven 的 assembly 插件把依赖打进去;如果项目没走 Maven,把驱动 jar 放在项目根目录的 lib/ 文件夹里,然后在 IDEA 的 Project Structure 里选中该 jar,点击 “Add as Library”。最直接验证是否成功的方式是在代码里临时打印驱动版本号,看到输出再继续。

5.2 现象:界面上中文正常,但 MySQL 里存的是问号乱码

表里中文全部变成??,或者查询出来变成乱码。原因:这是典型的字符集链路不一致。MySQL 客户端、数据库表、JDBC 连接串、Java 源文件四个环节只要有一个不是 UTF-8,中文就会出问题。解决:按顺序排查四个点。MySQL 建库时用CREATE DATABASE ... CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;连接串加 characterEncoding=utf8;IDEA 的 Settings 里把 File Encoding 改为 UTF-8;最重要的一步是在 MySQL 命令行里执行SHOW VARIABLES LIKE 'character_set%',确认 character_set_client 和 character_set_results 都是 utf8mb4。最容易被忽略的是 Java 源文件本身的编码,IDEA 在中文 Windows 上默认可能用 GBK,编译后字符串直接乱。你可以在还没运行前先看一眼代码里中文是否正常显示,不正常就立即在右下角切换编码。

5.3 现象:连接 MySQL 8 报时区错误或者 SSL 连接错误

控制台出现The server time zone value 'CST' is unrecognized或者SSL connection error: protocol error。原因:MySQL 8.0 默认时区是系统时区,驱动要求明确指定;SSL 握手因为没有证书而失败。解决:在 JDBC URL 上补齐两个参数,serverTimezone=Asia/Shanghai和useSSL=false。注意参数之间用&符号连接,在 XML 或 properties 文件里写时,&要转义成&,这个细节能卡你半小时。

5.4 现象:JTable 点了新增按钮但表格不更新,甚至调用 repaint 无效

新增了一条课程数据,数据库里确认有这条记录了,但界面上表格没有任何变化。原因:JTable 的数据源是 TableModel,repaint 只触发界面重绘,不会重新查询数据库。你调table.repaint()只是让屏幕刷新,数据模型没有变,表格自然不变。解决:把“清空模型 + 重新查询 + 填充模型”封装成一个方法refreshData(),在任何数据变更操作执行后调用它,而不是调 repaint。方法内部用上一章讲过的 setRowCount(0) + addRow 循环。如果你在新增、修改、删除三个模块都走了这个方法,就不会出现界面和数据库对不上的情况。

5.5 现象:登录时用同一套用户名密码,有人能登录有人不能

管理员账号能登录,学生账号登录后没有操作权限,或者直接提示密码错误。原因:sys_user 表里的 role 字段区分了不同角色,但登录的 SQL 只校验了用户名和密码,没有校验 role;另一种可能是初始化数据里有两行相同 username 但不同 role 的记录,导致查询结果集指向错误的那行。解决:登录 SQL 增加AND role = ?条件;初始化 SQL 里把 admin 账号的 username 设成唯一索引可防止重复账号。登录代码里查出记录后,先判断rs.next()是否存在,再读取 role 转发到对应主界面,顺序不能反,否则空指针在角色判断时等着你。

6. 答辩交付:数据一致性验证与单机 JAR 打包降级方案

把源码跑通只是第一步,课设最终要交付的是一个“换台电脑也能运行”的成果。这里分享两个我自己的习惯:验证清单和打包方案。

先讲打包。你辛辛苦苦写完 Swing 程序,答辩时不能用 IDEA 现场跑,因为老师机器上未必装了 JDK 或者 MySQL 驱动。最省事的方案是把项目打成可执行 JAR,然后把驱动 jar 也放进同一个目录。用一个启动脚本直接拉起来:

@echo off chcp 65001 java -Dfile.encoding=UTF-8 -jar educational-admin.jar pause

这段内容是 Windows 下的 bat 启动脚本。chcp 65001把控制台代码页切成 UTF-8,避免中文路径打印乱码;-Dfile.encoding=UTF-8是给 JVM 的启动参数,保证 Swing 界面按 UTF-8 读取资源;pause防止工程错误时窗口秒退。如果你在 macOS 或 Linux 上演示,把 bat 换成 sh 脚本,内容就是java -Dfile.encoding=UTF-8 -jar educational-admin.jar,加上执行权限。打包时用 IDEA 的 Build Artifact 功能,主类选择教务系统的入口 JFrame,注意 Manifest 文件里 Main-Class 路径不要带包名前缀错误。

打包完成后,我建议你强制走一遍交付前的自查:第一,用干净的 MySQL 库重新执行一遍 educational_admin.sql,从零初始化你的数据;第二,清空本机 Maven 缓存,用mvn clean package重新构建一次,确认依赖解析没问题;第三,换一台没有装 IDEA 的机器,只装 JRE 和 MySQL,跑一遍登录、添加课程、录入成绩、退出四个核心流程。如果这四个流程在两台机器上都顺畅,你的课设已经超过了大多数只会在 IDEA 里 F5 的同学。

在答辩演示前,我还习惯把 JTable 的字体调大一行:

table.setRowHeight(28); table.setFont(new Font("微软雅黑", Font.PLAIN, 14));

这个小细节能让投影仪下的表格数据清晰可见,避免老师只看到蚂蚁一样的字。从那以后,我每次提交课设都强制自己走一遍“干净环境启动 + 全流程操作 + 字体可读性检查”三道步骤,这套思路也让几个学弟在答辩时少踩了不少没必要的坑。希望帮到你。

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

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

线上问题复盘:不知不觉拖垮服务的文件句柄泄露

线上服务有一类问题特别隐蔽,不会直接宕机,不会瞬间报错,只会随着运行时间慢慢恶化。刚开始一切正常,跑几天或者一两周后,接口开始随机超时、请求失败、服务日志刷不出来,最后彻底拒绝新连接。 很多人遇到这…

作者头像 李华
网站建设 2026/10/1 6:58:29

Java实现 DWG 转 PDF 服务

手上有个需求,要把 CAD 图纸(DWG)转成 PDF 做归档,要求走接口调用、Docker 部署,端口还得能配。刚拿到需求的时候觉得这活儿不难,真做下来发现坑比想象中多,而且基本都出在 DWG 这个格式本身上。…

作者头像 李华
网站建设 2026/10/1 6:58:11

DEV 实战:ComboBoxEdit 与 barEditItem 配置 TaoToken 的 settings.json 骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华