Java Swing学生成绩管理系统实战:JDBC连接与JTable增删改查

Java Swing学生成绩管理系统实战:JDBC连接与JTable增删改查 简介这是一份基于 Java Swing 与 MySQL 实现的学生成绩管理系统源码包面向 Java 桌面应用初学者及需要课程设计、毕业设计参考的开发者。系统涵盖成绩信息管理、课程管理、学生信息管理、登录与密码修改等核心模块并通过 JDBC 完成对 MySQL 的增删改查能够帮助读者掌握界面布局、事件监听与数据库交互的整体开发流程。压缩包为 zip 格式共 145 个文件包括 27 个 java 源文件、30 个 form 界面文件、80 个 class 编译文件、4 个 jar 依赖包及 1 个 sql 数据库脚本资源整体约 746KB体积小巧但结构完整。已有 5365 人学习/下载适合用来快速搭建练习环境。通过阅读源码和运行调试读者可以获得完整的 Eclipse 工程、数据库建表与初始化脚本、Swing 界面设计范式并在此基础上扩展教师管理、班级管理等新模块加深对 Java 桌面应用与 MySQL 持久化存储的理解。1. 用 Swing 写成绩管理系统为什么还值得认真做一遍很多经历过实训项目的人看到「java swing 学生成绩管理系统」第一反应是「太老了」。在 Spring Boot Vue 占主导的今天Swing 确实不是企业级首选但它有一个被低估的价值用最少的框架依赖把 JDBC、事件监听、JTable 数据绑定、事务控制这些 Java 基础功完整走一遍。学生成绩管理系统的业务足够清楚——学生表、课程表、成绩表增删改查加统计——非常适合用来验证你对 Java SE 的掌握程度。这篇文章要解决的正是这样一件事在 Eclipse 下从建库到界面一步步把系统跑起来并告诉你成绩表为什么不能简单设计成一行一个学生加一个总分字段以及在用 MySQL 8.x 时 JDBC 驱动版本不匹配会踩到什么坑。适合正在做课程设计、准备 Java 面试基础题、或者想补桌面应用开发能力的读者。2. 建库建表前的实体设计成绩怎么算才是关键2.1 学生、课程、成绩三张表的 ER 关系学生成绩管理系统听起来简单但「成绩」这个业务对象很容易设计错。常见错误是直接在学生表里加chinese_score、math_score、english_score三个字段这样做当课程数量变动时就会面临改表结构的尴尬而且无法存储多次考试的成绩。正确做法是拆成三张核心表表名用途关键字段student学生档案stu_id, stu_name, stu_class, stu_phonecourse课程档案course_id, course_name, course_creditscore成绩事实表stu_id, course_id, score_value, exam_date学生和课程是多对多关系一个学生选多门课一门课被多个学生选。成绩表就是这两张表的关联实体每一行记录「某个学生在某门课上的某次考试成绩」。stu_id和course_id在成绩表里组成联合主键同时分别作为外键引用学生表和课程表。用 ER 图表示就是学生表中导出一个联系到成绩表成绩表再连接到课程表。2.2 MySQL 建库建表 DDL联合主键和外键约束在 MySQL 8.0 版本下我一般会在命令行和 MySQL Workbench 里分别确认一次执行结果。下面这段 DDL 可以直接复制进 MySQL Workbench 或 mysql 命令行工具CREATE DATABASE IF NOT EXISTS student_grade DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE student_grade; CREATE TABLE student ( stu_id VARCHAR(20) PRIMARY KEY, stu_name VARCHAR(50) NOT NULL, stu_class VARCHAR(50), stu_phone VARCHAR(20) ) ENGINEInnoDB; CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY, course_name VARCHAR(50) NOT NULL, course_credit DECIMAL(3,1) ) ENGINEInnoDB; CREATE TABLE score ( stu_id VARCHAR(20) NOT NULL, course_id VARCHAR(20) NOT NULL, score_value DECIMAL(5,2), exam_date DATE, PRIMARY KEY (stu_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON UPDATE CASCADE ) ENGINEInnoDB;建表时把utf8mb4写进库的默认字符集是避免中文乱码的第一步。ENGINEInnoDB是为了让外键约束和后续的事务操作生效。成绩表的联合主键防止了同一个学生同一门课被插入两条互相矛盾的记录。外键上的ON UPDATE CASCADE意味着如果学号因转专业发生变更成绩表会自动同步不需要手动更新。2.3 为什么不推荐把平均分存进成绩表成绩表里只存原始分平均分、总分、排名全部通过查询计算。原因有二第一存了总分就会产生「总分与明细不一致」的数据一致性问题第二当需要按学期筛选成绩时冗余字段的值维护会很麻烦。查询时用聚合函数实时算即可SELECT s.stu_id, s.stu_name, ROUND(AVG(sc.score_value), 2) AS avg_score, COUNT(sc.course_id) AS course_count FROM student s LEFT JOIN score sc ON s.stu_id sc.stu_id GROUP BY s.stu_id, s.stu_name ORDER BY avg_score DESC;这里用LEFT JOIN是因为有的学生可能还没有成绩记录内连接会把这类学生直接过滤掉。ROUND(..., 2)控制平均分的小数位数。业务系统里如果需要展示「张三的平均分是 88.50」这条 SQL 已经足够不必额外维护一张统计表。3. Eclipse 环境准备与 JDBC 连接驱动配好代码才稳3.1 mysql-connector-j 的 jar 包导入与常见启动异常在 Eclipse 里新建 Java 项目后第一件事不是写代码而是把 MySQL 官方驱动放进classpath。MySQL 8.x 对应的是mysql-connector-j早期版本叫mysql-connector-java。下载时注意选择平台无关的 jar 包然后在 Eclipse 项目上右键Build Path - Add External Archives选中 jar。这个环节最容易撞上的报错是满屏的ClassNotFoundException: com.mysql.cj.jdbc.Driver或No suitable driver found for jdbc:mysql://localhost:3306/student_grade。前者说明驱动 jar 没有真正加入构建路径后者通常是因为连接串写得有问题或者驱动类名写错。还有一种出现在项目运行期的异常是eclipse 找不到或无法加载主类原因是编译输出目录损坏或者配置了错误的 JRE清理一次项目再重新构建即可。3.2 封装 DBHelper 工具类注册驱动、获取连接、关闭资源连接数据库的代码如果散落在每一个业务方法里项目会变得很难维护。常见做法是先封装一个DBHelper类把驱动注册、连接获取、资源释放集中在一起。下面这套代码结构在课程设计和中小型桌面应用里都很通用package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBHelper { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/student_grade?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(ResultSet rs, PreparedStatement ps, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }Class.forName(DRIVER)在静态块里执行保证类加载时驱动只注册一次。连接串里的serverTimezoneAsia/Shanghai是 MySQL 8.x 必须配的参数不配会报CommunicationsException或时区错误。characterEncodingutf8配合数据库的utf8mb4是为了让中文在 JDBC 传输层不丢失。close方法里三个参数可以为 null调用DBHelper.close(rs, ps, conn)时不会因为某个参数为空而失败。3.3 第一次连库必踩的驱动版本参数对照老项目里常见的驱动类是com.mysql.jdbc.DriverMySQL 5.x 时代这么写没问题但 6.0 以后官方把驱动类迁移到了com.mysql.cj.jdbc.Driver。已经用旧驱动的项目在连接 MySQL 8 时会出现警告或直接拒绝连接。MySQL 版本JDBC 驱动类推荐的连接参数MySQL 5.xcom.mysql.jdbc.Driver无需 serverTimezoneMySQL 8.xcom.mysql.cj.jdbc.DriverserverTimezone 必填MySQL 8.x 新装mysql-connector-j 8.0.33同时建议 useSSLfalse连接成功后可以先用一个最简单的SELECT 1验证链路Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT 1); ResultSet rs ps.executeQuery(); System.out.println(rs.next() ? connect ok : connect fail); DBHelper.close(rs, ps, conn);如果这段在main方法里能打印connect ok说明环境问题已经全部排除。后续所有 Swing 界面代码都可以放心地基于这套连接工具开发。4. Swing 界面从登录到成绩录入JTable 绑定查询结果4.1 登录窗体与密码校验的常规写法学生成绩管理系统通常需要一个入口登录。Swing 里最直白的做法是JFrame加两个JLabel、两个JTextField/JPasswordField、一个JButton。点击登录按钮后用ActionListener触发查询从数据库里SELECT * FROM user WHERE username? AND password?。即使只有「管理员/admin」这一个固定用户也建议存在 MySQL 的用户表里而非硬编码在 Java 代码中。JButton loginBtn new JButton(登录); loginBtn.addActionListener(e - { String username userField.getText().trim(); String password new String(pwdField.getPassword()); String sql SELECT role FROM user WHERE username ? AND password ?; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { JOptionPane.showMessageDialog(frame, 登录成功角色 rs.getString(role)); } else { JOptionPane.showMessageDialog(frame, 用户名或密码错误, 错误, JOptionPane.ERROR_MESSAGE); } } } catch (Exception ex) { ex.printStackTrace(); } });userField.getText().trim()会去掉首尾空格防止用户误输入空格导致查询失败。JPasswordField.getPassword()返回的是字符数组而不是字符串直接用new String(...)转成字符串是教学项目里简单可接受的做法生产系统里还应当加密后再比对。 这里用try-with-resources自动关闭连接和 PreparedStatement。查询结果里如果只有一行说明账号密码匹配成功。4.2 JTable DefaultTableModel 动态刷新成绩数据主界面一般由一个JTable和一组操作按钮组成。JTable的数据模型推荐使用DefaultTableModel因为它允许运行时自由增删行。下面的方法封装了「从成绩表联查学生姓名和课程名并填充表格」的逻辑import javax.swing.table.DefaultTableModel; public void loadGradeData(DefaultTableModel model) { String sql SELECT sc.stu_id, s.stu_name, c.course_name, sc.score_value, sc.exam_date FROM score sc JOIN student s ON sc.stu_id s.stu_id JOIN course c ON sc.course_id c.course_id; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { model.setRowCount(0); while (rs.next()) { model.addRow(new Object[]{ rs.getString(stu_id), rs.getString(stu_name), rs.getString(course_name), rs.getBigDecimal(score_value), rs.getDate(exam_date) }); } } catch (Exception e) { e.printStackTrace(); } }model.setRowCount(0)的作用是先把旧数据清空再逐条添加新行这样点击「刷新」按钮时界面不会出现残留。JOIN让成绩表里的学生姓名和课程名称不再需要前端的二次查询一次 SQL 就能带出所有展示字段。表格初始化时该这样绑定String[] columns {学号, 姓名, 课程, 成绩, 考试日期}; DefaultTableModel model new DefaultTableModel(columns, 0); JTable table new JTable(model); JScrollPane scrollPane new JScrollPane(table);DefaultTableModel(columns, 0)表示有 5 列但初始行为 0后续由addRow动态填充。JTable必须放进JScrollPane否则表头不会显示列宽也会异常。4.3 按学号或姓名模糊查询PreparedStatement 写法与 % 拼接在搜索框里输入关键字点击查询按钮系统按学号或者姓名对学生成绩做模糊匹配。SQL 中模糊查询使用LIKE配合%通配符实现包含匹配。注意%是放在参数绑定之前拼进 SQL还是通过setString传进去——正确做法是拼在传入值两侧String keyword searchField.getText().trim(); String sql SELECT s.stu_id, s.stu_name, c.course_name, sc.score_value FROM score sc JOIN student s ON sc.stu_id s.stu_id JOIN course c ON sc.course_id c.course_id WHERE s.stu_name LIKE ? OR sc.stu_id LIKE ?; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); try (ResultSet rs ps.executeQuery()) { model.setRowCount(0); while (rs.next()) { model.addRow(new Object[]{ rs.getString(stu_id), rs.getString(stu_name), rs.getString(course_name), rs.getBigDecimal(score_value) }); } } } catch (Exception e) { e.printStackTrace(); }传入% 关键字 %后student_grade库里的LIKE ?会被替换成LIKE %张%再执行。 直接用字符串拼接 SQL 的写法很容易被注入攻击比如在文本框里输入 OR 11就能绕过条件而PreparedStatement的参数占位符机制从底层把值作为字面量处理不带任何解释执行。搜索框触发时可以在单击查询按钮的事件里调用以上逻辑。4.4 成绩录入与事务处理update 和 delete 的边界录入或修改成绩时界面一般弹出一个JDialog内含课程下拉框、学号文本框、成绩文本框和保存按钮。保存按钮背后的 SQL 分两种情况成绩记录存在则更新不存在则插入。如果只是课程设计可以直接先查再判断稳妥一点的做法是利用联合主键做INSERT ... ON DUPLICATE KEY UPDATEString sql INSERT INTO score (stu_id, course_id, score_value, exam_date) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE score_value VALUES(score_value), exam_date VALUES(exam_date); try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stuId); ps.setString(2, courseId); ps.setBigDecimal(3, new BigDecimal(scoreText)); ps.setDate(4, java.sql.Date.valueOf(examDate)); int rows ps.executeUpdate(); if (rows 0) { JOptionPane.showMessageDialog(frame, 保存成功); } } catch (SQLException e) { if (e.getErrorCode() 1452) { JOptionPane.showMessageDialog(frame, 学生或课程不存在请先核对基础档案); } }ON DUPLICATE KEY UPDATE在联合主键命中时会自动转成更新避免了「先查询再决定新增或修改」的两步逻辑。executeUpdate的返回值为 1 表示受影响行数为 1。错误码 1452 是外键约束失败代表录入时填写的学号在学生表中找不到这个提示对使用者比较友好。成绩录入完成后调用loadGradeData(model)刷新表格保证界面所见即数据库所有。5. 进阶打磨导出 CSV 和成绩统计的边界验证系统跑通基本功能后真正体现工程意识的是细节处理。先给成绩列表加一个「导出 CSV」功能用JFileChooser让用户选择保存路径把JTable当前的数据写入文本文件。这里要注意 CSV 对中文逗号的处理——如果字段本身包含逗号需要整体对齐加双引号学生姓名和课程名一般不含逗号但成绩字段可能出现79.50这样带点的小数直接写不会被拆分。核心代码如下File file chooser.getSelectedFile(); try (BufferedWriter writer new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file), StandardCharsets.UTF_8))) { for (int i 0; i table.getColumnCount(); i) { writer.write(table.getColumnName(i) ,); } writer.newLine(); for (int i 0; i table.getRowCount(); i) { StringBuilder sb new StringBuilder(); for (int j 0; j table.getColumnCount(); j) { Object val table.getValueAt(i, j); sb.append(val null ? : val.toString()).append(,); } writer.write(sb.toString()); writer.newLine(); } JOptionPane.showMessageDialog(frame, 导出成功); }用StandardCharsets.UTF_8而不是平台默认编码可以避免 Excel 打开 CSV 时出现「锟斤拷」式乱码。 另外值得把「优秀率」加进统计逻辑SELECT SUM(CASE WHEN score_value 90 THEN 1 ELSE 0 END)/COUNT(*) FROM score WHERE course_id ?用它判断一个班的单科成绩分布。验证系统是否可靠可以在录入界面连输 10 条成绩后去 MySQL Workbench 里核对数据行数或用SELECT COUNT(*) FROM score检查。最常用于自检的一条 SQL 是EXPLAIN SELECT ... WHERE stu_id2024001确认是否用到主键索引。像名单一样把缺考成绩设为NULL而不是 0对平均值计算的影响完全不同。最后在 Eclipse 里File - Export - Runnable JAR file选择包含驱动 jar 的启动配置生成可运行的 jar 包后即可在命令行直接执行但记得运行机器上也装了相同版本的 MySQL 并有对应数据库。本文还有配套的精品资源点击获取