Java Swing + JDBC 学生成绩管理系统源码拆解与二次开发实战

发布时间:2026/10/15 14:55:52
Java Swing + JDBC 学生成绩管理系统源码拆解与二次开发实战 简介这是一套面向高校计算机相关专业学生与Java初学者的学生成绩管理系统完整项目可作为课程设计、期末大作业或Java GUI与数据库综合练习的参考方案。系统基于Java语言开发采用图形界面交互覆盖登录验证、学生与教师注册、成绩的增删改查、文件导入成绩、学生账号管理及密码修改等核心功能并配套数据库驱动与实验报告便于理解从界面到数据持久化的完整实现链路。压缩包共80个文件约5.74MB其中28个java源码与42个class文件构成主体程序另含2个jar驱动、2个docx实验报告、xls成绩数据及图片等辅助资源导入后即可运行调试。目前已有9123人学习下载适合需要快速获取可运行项目、对照源码梳理模块结构并完成实验报告撰写的读者参考借鉴。1. 从一份能跑的成绩单说起这个 Java 项目到底能干什么如果你手头正好有一份 Java 学生成绩管理系统的源码打开一看是 GUI 界面加数据库还附带实验报告那大概率是课程设计或实训周的产物。这类项目最尴尬的地方在于代码能跑但换个环境就报错数据库连不上界面点两下就卡死。我见过太多人拿到源码后卡在第一步——不知道从哪改起也不知道哪些模块是核心、哪些只是凑数的。这个项目本质上是一个基于 Java Swing 的桌面端成绩管理工具后端用 JDBC 直连数据库功能覆盖学生信息录入、成绩增删改查、按条件统计和报表导出。它适合两类人一是刚学完 Java 语法、想找一个完整项目练手的在校生二是需要快速交付一个课程设计或小工具、不想从零搭框架的开发者。下面我按实际拆项目的顺序把技术选型、目录结构、核心代码和改造点一次讲清楚。2. 技术选型与目录结构Swing JDBC 的经典组合2.1 为什么是 Swing 而不是 JavaFX这个项目选 Swing 做 GUI原因很直接Swing 是 JDK 自带的不需要额外引入依赖对于课程设计级别的项目来说少一个依赖就少一个环境报错的可能。JavaFX 虽然界面更现代但从 JDK 11 开始已经不再随 JDK 捆绑需要单独下载 SDK 并配置模块路径对新手来说门槛反而更高。Swing 的组件模型虽然老但胜在资料多、示例全遇到问题搜一下基本都能找到答案。项目里用到的核心组件包括JFrame做主窗口、JTable展示成绩列表、JTextField和JComboBox做输入控件布局上大概率用的是BorderLayout加GridBagLayout的组合。这种搭配在课程设计里非常常见优点是代码直白缺点是界面美观度一般想改造成现代风格需要花不少功夫。2.2 目录结构拆解一个典型的 Java 成绩管理系统目录结构大致如下src/ ├── main/ │ ├── java/ │ │ ├── com/example/score/ │ │ │ ├── Main.java // 程序入口 │ │ │ ├── ui/ // 界面层 │ │ │ │ ├── LoginFrame.java │ │ │ │ ├── MainFrame.java │ │ │ │ └── StudentPanel.java │ │ │ ├── dao/ // 数据访问层 │ │ │ │ ├── StudentDao.java │ │ │ │ └── ScoreDao.java │ │ │ ├── model/ // 实体类 │ │ │ │ ├── Student.java │ │ │ │ └── Score.java │ │ │ └── util/ // 工具类 │ │ │ └── DBUtil.java │ │ └── resources/ │ │ └── db.properties // 数据库配置 └── test/ └── java/ // 测试代码这个分层结构是典型的 MVC 变体ui包负责界面展示和事件响应dao包封装所有 SQL 操作model包存放与数据库表对应的实体类util包提供数据库连接工具。实际项目中有些代码会把 SQL 直接写在界面类里那样虽然写起来快但后期改需求时非常痛苦。如果拿到手的源码分层清晰说明作者有一定工程意识如果所有代码都堆在几个文件里改造时第一件事就是先把 DAO 层抽出来。2.3 数据库表设计成绩管理系统的数据库通常至少包含三张表学生表、课程表和成绩表。学生表存学号、姓名、班级等基本信息课程表存课程编号、课程名称、学分成绩表存学号、课程编号、分数通过外键关联前两张表。有些简化版会把课程信息直接写死在代码里只保留学生表和成绩表这样虽然少了一张表但后期加课程就得改代码不推荐。下面是一个常见的建表 SQL-- 学生表 CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, class_name VARCHAR(50), gender VARCHAR(4) ); -- 课程表 CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY, course_name VARCHAR(100) NOT NULL, credit DECIMAL(3,1) ); -- 成绩表 CREATE TABLE score ( id INT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20), course_id VARCHAR(20), score DECIMAL(5,2), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );建表时注意score字段用DECIMAL而不是FLOAT因为浮点数在比较和求和时会出现精度问题比如 59.999999 会被判定为不及格这种玄学问题排查起来很费时间。另外外键约束在批量导入数据时可能会拖慢速度如果数据量不大可以保留数据量上万时建议先禁用外键检查再导入。3. 核心模块拆解从数据库连接到界面刷新3.1 数据库连接工具类DBUtil是整个项目里最关键的类之一所有数据库操作都依赖它获取连接。常见做法是把 JDBC URL、用户名和密码写在db.properties文件里通过Properties类读取这样换环境时只需要改配置文件不用重新编译代码。public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); // 加载驱动MySQL 8 以上用 com.mysql.cj.jdbc.Driver Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { throw new RuntimeException(数据库配置加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭资源注意顺序ResultSet - Statement - Connection try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }这段代码有几个细节值得注意第一静态代码块里加载配置和驱动保证类加载时只执行一次第二close方法里每个资源单独 try-catch避免某个资源关闭失败导致后面的资源无法关闭第三MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver如果用的是 5.x 版本则是com.mysql.jdbc.Driver写错了会报ClassNotFoundException。db.properties文件的内容大致如下jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/score_db?useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456serverTimezone参数在 MySQL 8 里必须指定否则会报时区错误。useSSLfalse是为了避免控制台输出 SSL 警告本地开发可以关掉生产环境建议开启。3.2 学生信息的增删改查DAO 层的代码模式非常固定每个方法打开连接、执行 SQL、处理结果、关闭连接。以StudentDao为例查询所有学生的方法如下public ListStudent findAll() { ListStudent list new ArrayList(); String sql SELECT student_id, name, class_name, gender FROM student; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setStudentId(rs.getString(student_id)); s.setName(rs.getString(name)); s.setClassName(rs.getString(class_name)); s.setGender(rs.getString(gender)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; }这里用了 try-with-resources 语法连接、语句和结果集都会自动关闭比手动在 finally 里关更简洁。注意PreparedStatement和Statement的区别前者支持参数占位符?能防止 SQL 注入写插入和更新时一定要用。插入学生的代码public boolean insert(Student s) { String sql INSERT INTO student(student_id, name, class_name, gender) VALUES(?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, s.getStudentId()); ps.setString(2, s.getName()); ps.setString(3, s.getClassName()); ps.setString(4, s.getGender()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }参数下标从 1 开始不是 0这个坑新手经常踩。executeUpdate返回受影响的行数大于 0 表示操作成功。更新和删除的逻辑类似只是 SQL 语句不同。3.3 成绩统计与界面刷新成绩统计通常包括按课程求平均分、最高分、最低分以及按班级统计及格率。这些计算可以放在 SQL 里做也可以查出来在 Java 里算。SQL 做的好处是数据量大时效率高Java 做的好处是逻辑直观、方便调试。下面是一个按课程统计的 SQLSELECT c.course_name, AVG(s.score) AS avg_score, MAX(s.score) AS max_score, MIN(s.score) AS min_score, SUM(CASE WHEN s.score 60 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS pass_rate FROM score s JOIN course c ON s.course_id c.course_id GROUP BY c.course_name;界面层拿到统计数据后需要刷新JTable。常见做法是自定义一个TableModel把ListScore转成二维数组和列名数组然后调用table.setModel(new DefaultTableModel(data, columns))。每次增删改后重新查询并刷新模型界面就会更新。注意JTable的刷新必须在事件调度线程EDT里执行如果在子线程里直接操作组件会出现界面卡死或数据错乱。正确做法是用SwingUtilities.invokeLater包裹刷新逻辑。4. 避坑与常见问题那些让你加班到深夜的报错4.1 数据库连接失败Communications link failure现象是程序启动后点任何按钮都报Communications link failure控制台堆栈一大串。原因通常是 MySQL 服务没启动或者db.properties里的端口写错了。默认端口是 3306如果本机装了两个 MySQL 实例可能实际端口是 3307。解决方法是先用命令行mysql -u root -p确认能连上再检查配置文件里的 URL。另外 MySQL 8 的时区参数serverTimezone如果写成UTC插入的时间会比北京时间少 8 小时建议写成Asia/Shanghai。4.2 中文乱码数据库和连接都要设 UTF-8现象是界面上输入中文姓名存进数据库变成问号或者查出来显示乱码。原因有三个层面数据库表的字符集、连接 URL 的字符集、Java 文件的编码。建表时指定DEFAULT CHARSETutf8mb4连接 URL 加characterEncodingutf8IDE 里把文件编码设为 UTF-8。三个地方缺一不可。如果已经建了表可以用ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;修改。注意utf8在 MySQL 里最多存 3 字节存 emoji 会报错用utf8mb4更稳妥。4.3 界面卡死耗时操作别放在 EDT 里现象是点击“统计”按钮后界面无响应转圈直到统计完成。原因是统计逻辑直接在按钮的actionPerformed里执行而actionPerformed运行在 EDT 上EDT 被阻塞后界面就无法重绘。解决办法是把耗时操作放到SwingWorker的doInBackground里完成后在done方法里更新界面。如果数据量不大也可以先显示一个“加载中”的提示让用户知道程序没死。4.4 驱动版本不匹配No suitable driver found现象是DriverManager.getConnection报No suitable driver found for jdbc:mysql://...。原因通常是Class.forName加载的驱动类名和实际 jar 包版本不一致或者 jar 包根本没加到 classpath。检查pom.xml或lib目录里 mysql-connector 的版本8.x 用com.mysql.cj.jdbc.Driver5.x 用com.mysql.jdbc.Driver。如果用 Maven确认依赖 scope 不是provided或test。4.5 外键约束导致删除失败现象是删除学生时报Cannot delete or update a parent row: a foreign key constraint fails。原因是该学生在成绩表里还有记录外键约束阻止了删除。解决办法有两种一是先删成绩再删学生用事务包起来二是建表时把外键设为ON DELETE CASCADE删除学生时自动删除对应成绩。课程设计里推荐第一种因为逻辑更清晰也方便在实验报告里写事务处理。5. 二次开发与改造从能跑到好用5.1 把 JDBC 换成连接池项目里每次操作都新建连接频繁开关连接开销很大。改造的第一步是引入连接池常见选择是 HikariCP 或 Druid。以 HikariCP 为例在pom.xml加依赖后把DBUtil改成从HikariDataSource获取连接private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(url); config.setUsername(username); config.setPassword(password); config.setMaximumPoolSize(10); config.setMinimumIdle(2); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }改完后close方法里的conn.close()不再是真正关闭连接而是归还到池里。注意连接池的maximumPoolSize不要设太大本地开发 10 个足够设成 100 反而可能因为数据库最大连接数限制而报错。5.2 用 Maven 管理依赖和打包如果源码是 Eclipse 项目没有pom.xml建议先转成 Maven 项目。在项目根目录创建pom.xml加入 mysql-connector、HikariCP 和 JUnit 依赖然后mvn clean package打成可执行 jar。打包时注意在maven-jar-plugin里配置mainClass否则java -jar会报找不到主类。如果资源文件在src/main/resources下Maven 会自动打进 jar不用手动复制。5.3 加一层 Service 层现在 DAO 直接被 UI 调用业务逻辑散落在界面代码里。改造时可以加一个service包把“录入成绩时检查学号是否存在”“统计前先判断该课程有没有成绩”这类逻辑收进去。UI 只负责收集输入和展示结果Service 负责校验和组合 DAO 调用。这样以后换界面比如改成 Web时Service 和 DAO 可以复用只重写 UI 层。5.4 实验报告的写法建议如果这份资源附带实验报告改造后记得同步更新报告里的“系统设计”和“核心代码”章节。报告里不要贴大段完整代码挑关键方法贴配上流程图或表结构说明。测试部分至少写三个用例正常录入、重复学号录入、删除有成绩的学生。每个用例写清楚输入、预期输出和实际输出。我见过太多报告只写“系统运行正常”这种在答辩时很容易被追问到哑口无言。5.5 一个具体的改造技巧用 TableModel 替代二维数组很多源码里刷新表格时直接传二维数组给DefaultTableModel这样每次都要遍历 List 转数组代码啰嗦且容易下标越界。更好的做法是自定义一个StudentTableModel extends AbstractTableModel内部持有ListStudentgetValueAt直接从 List 取。这样增删改后只需要fireTableDataChanged()界面自动刷新不用重建模型。这个改动不大但能让代码整洁不少。public class StudentTableModel extends AbstractTableModel { private final String[] columns {学号, 姓名, 班级, 性别}; private ListStudent data new ArrayList(); public void setData(ListStudent data) { this.data data; fireTableDataChanged(); } Override public int getRowCount() { return data.size(); } Override public int getColumnCount() { return columns.length; } Override public String getColumnName(int col) { return columns[col]; } Override public Object getValueAt(int row, int col) { Student s data.get(row); switch (col) { case 0: return s.getStudentId(); case 1: return s.getName(); case 2: return s.getClassName(); case 3: return s.getGender(); default: return null; } } }用的时候在界面类里持有一个StudentTableModel实例查询后调用setData即可。这个模式在 Swing 项目里很通用换成课程表、图书列表也一样。从那以后我每次拿到一个带数据库的 Java 项目都先跑通连接、再抽 DAO、最后才动界面顺序反了就会在报错里反复横跳。希望这份拆解能帮你少走点弯路把这份源码真正跑起来、改下去。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询