简介:这是一套面向高校计算机相关专业学生与Java初学者的学生成绩管理系统完整项目,可作为课程设计、期末大作业或Java GUI与数据库综合练习的参考方案。系统基于Java语言开发,采用图形界面交互,覆盖登录验证、学生与教师注册、成绩的增删改查、文件导入成绩、学生账号管理及密码修改等核心功能,并配套数据库驱动与实验报告,便于直接运行与二次开发。压缩包共80个文件,约5.74MB,其中28个java源码与42个class文件构成主体程序,另含2个jar依赖、2个docx实验报告、xls成绩数据表及图片等资源,源码与文档齐备,导入后即可使用。目前已有9123人学习下载,适合需要快速获取可运行项目、理解成绩管理业务逻辑与数据库连接方式的读者参考借鉴。
1. 从一份“交作业级”源码说起:这套 Java 学生成绩管理系统到底能跑出什么
很多同学做课程设计时,最头疼的不是写不出增删改查,而是“功能凑齐了、报告写不出来、数据库还连不上”。这套 Java 学生成绩管理系统(含数据库以及实验报告)就是冲着这个场景来的:它把学生信息、课程成绩、班级统计、用户登录这几块做成了一个能直接运行的桌面程序,配套的 SQL 建表脚本和实验报告文档也一并打包。技术栈上走的是 Java Swing + JDBC + MySQL 这条经典路线,没有上 Spring 那套重框架,对刚学完 Java 基础、正在啃数据库连接的人来说,阅读和改造的门槛都低。它适合两类人:一类是要交课程设计、需要一份结构完整可演示的源码;另一类是想拿一个真实小项目练手 JDBC 和 GUI 事件处理的开发者。下面我按“能干什么 → 怎么跑 → 怎么改”的顺序,把这份资源拆开讲。
2. 技术选型与目录结构:为什么是 Swing + JDBC 而不是别的
2.1 这套组合的取舍逻辑
先说明一点,这个项目没有用 JavaFX,也没有用 Maven 做依赖管理,而是最朴素的 Swing + JDBC + 手动导入 mysql-connector 的 jar 包。这不是偷懒,而是课程设计场景下的合理选择:Swing 是 JDK 自带的,不需要额外配环境;JDBC 直连数据库,代码里每一步 SQL 都看得见,不像 MyBatis 那样把 SQL 藏进 XML 或注解里,对理解“Java 怎么跟数据库说话”这件事更直观。
常见做法是,把数据库连接参数写在一个单独的配置类里,而不是散落在各个 DAO 中。这个项目大概率也是这么处理的,因为一旦要换数据库地址或密码,只改一处比全局搜索要省事得多。下面是我一般会建议的目录组织方式,和这类项目的实际结构基本吻合:
StudentScoreSystem/ ├── src/ │ ├── com/scs/ │ │ ├── entity/ # 实体类:Student、Course、Score、User │ │ ├── dao/ # 数据访问层:StudentDao、ScoreDao 等 │ │ ├── util/ # 工具类:DBUtil 数据库连接 │ │ ├── view/ # 界面层:LoginFrame、MainFrame、各功能面板 │ │ └── Main.java # 程序入口 ├── lib/ │ └── mysql-connector-java-x.x.x.jar ├── sql/ │ └── init.sql # 建库建表 + 初始数据 └── doc/ └── 实验报告.docx这个分层不复杂,但边界是清楚的:entity 只放字段和 getter/setter,dao 只负责 SQL 执行,view 只负责界面渲染和事件绑定。新手最容易犯的错是把 SQL 直接写在按钮的点击事件里,导致界面和数据库逻辑缠在一起,后面想改一个字段名要翻好几个文件。这份源码如果按上面的结构组织,改造起来会舒服很多。
2.2 数据库表设计:三张核心表怎么关联
成绩管理系统的数据模型绕不开三个实体:学生、课程、成绩。成绩表是典型的中间表,用学生 ID 和课程 ID 做联合关联。建表脚本一般长这样:
-- 学生表 CREATE TABLE student ( id VARCHAR(20) PRIMARY KEY, -- 学号,用字符串避免前导零丢失 name VARCHAR(50) NOT NULL, gender VARCHAR(4), class_name VARCHAR(50), -- 班级 major VARCHAR(50) -- 专业 ); -- 课程表 CREATE TABLE course ( id VARCHAR(20) PRIMARY KEY, -- 课程编号 name VARCHAR(50) 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,1), -- 支持 100.0 这种一位小数 FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) );这里有几个参数值得注意。学号用 VARCHAR 而不是 INT,是因为很多学校的学号带前导零或者字母,用整型会丢格式。成绩用 DECIMAL(5,1) 而不是 FLOAT,是为了避免浮点误差导致 89.9 显示成 89.89999 这种尴尬。外键约束加上之后,删学生之前必须先处理他的成绩记录,否则数据库会直接拒绝,这其实是一种保护。
2.3 数据库连接工具类:一个容易被忽视的关键点
DBUtil 这个类看起来简单,但它是整个项目能不能跑起来的命门。典型实现如下:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/student_score?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭,注意顺序和空判断 } }serverTimezone=UTC这个参数在 MySQL 8 之后基本是必加的,不加会报时区错误。characterEncoding=utf8是为了中文姓名不乱码。驱动类名从 MySQL 8 开始是com.mysql.cj.jdbc.Driver,老版本是com.mysql.jdbc.Driver,用错了会直接抛 ClassNotFoundException。这些细节在实验报告里通常不会写,但实际跑的时候一个都不能少。
3. 从零跑起来:环境准备、建库、导包、启动的完整链路
3.1 环境清单与版本对应关系
在动手之前,先把版本对齐,否则后面报的错会让你怀疑人生。这套项目对版本不算挑剔,但有几个组合是经过验证比较稳的:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| JDK | 8 或 11 | JDK 8 兼容性最好,11 也能跑 |
| MySQL | 5.7 或 8.0 | 8.0 需要改驱动类名和 URL 参数 |
| mysql-connector | 8.0.x | 与 MySQL 8.0 配套 |
| IDE | Eclipse / IntelliJ IDEA | 两者均可,注意源码编码设为 UTF-8 |
如果你的 MySQL 是 5.7,驱动可以用 5.1.x,URL 里不需要serverTimezone。如果是 8.0,就必须用 8.0.x 的驱动并加上时区参数。这个对应关系搞错了,最常见的现象就是启动时直接抛Communications link failure或者Unknown database。
3.2 建库与初始化数据
拿到 sql 目录下的脚本后,不要急着在命令行里 source,先打开看一眼库名和表名是否和代码里一致。执行方式有两种,命令行如下:
# 登录 MySQL mysql -u root -p # 执行初始化脚本 source /path/to/init.sql;或者在 Navicat、DBeaver 这类工具里直接打开脚本运行。执行完之后用SHOW TABLES;确认三张表都在,再用SELECT * FROM student;看看初始数据有没有插进去。如果脚本里没有插入初始数据,登录界面可能因为没有账号而进不去,这时候需要手动往 user 表里插一条管理员记录。
提示:执行脚本前先确认脚本开头的
CREATE DATABASE语句里的库名,和 DBUtil 里 URL 的库名完全一致,大小写也要一致,Linux 下 MySQL 默认区分大小写。
3.3 导入项目与依赖 jar 包
在 IDE 里新建 Java 项目,把 src 下的包整体拷进去。然后关键一步:把 mysql-connector 的 jar 加到 Build Path 里。Eclipse 里是右键项目 → Build Path → Configure Build Path → Libraries → Add External JARs;IDEA 里是 File → Project Structure → Libraries → 加号 → Java。加完之后,jar 包会出现在 Referenced Libraries 下面,这时候 DBUtil 里的Class.forName才能找到驱动类。
很多人卡在这一步,现象是编译能过,一运行就报No suitable driver found。原因就是 jar 没进 classpath,或者进了但驱动类名写错了。检查方法很简单:在 DBUtil 的静态块里加一句System.out.println("driver loaded"),启动时看控制台有没有输出。
3.4 启动入口与登录流程
Main 类通常只做一件事:SwingUtilities.invokeLater(() -> new LoginFrame().setVisible(true));。用 invokeLater 是 Swing 的规矩,所有界面创建都要放在事件调度线程里,不然可能出现界面闪烁或者组件不显示。登录框一般校验 user 表里的用户名和密码,验证通过后 dispose 掉登录窗,打开主窗口。
主窗口里通常是左侧一个菜单树或者选项卡,右侧是内容面板。点击“学生管理”就加载学生列表的 JTable,点击“成绩录入”就切换到录入表单。这个切换逻辑如果用 CardLayout 实现,会比反复 new 窗口更流畅,也不会留下一堆隐藏窗口占内存。
4. 核心模块拆解:登录校验、学生 CRUD、成绩录入与统计的代码实现
4.1 登录模块:PreparedStatement 防注入的正确姿势
登录校验看起来简单,但用拼接 SQL 就是给自己埋雷。正确写法是用 PreparedStatement:
public boolean login(String username, String password) { String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery(); return rs.next(); // 有记录就说明账号密码匹配 } catch (SQLException e) { e.printStackTrace(); return false; } }?是占位符,setString按位置从 1 开始填。这样写的好处是,即使用户输入' OR '1'='1这种内容,也会被当成普通字符串处理,不会改变 SQL 语义。try-with-resources 是 JDK 7 之后的标准写法,连接和语句会自动关闭,不用手动写 finally。如果这里用了 Statement 加字符串拼接,那这个项目就只能当作业交,不能当例子学。
4.2 学生管理:表格渲染与增删改的联动
学生列表一般用 JTable 展示,数据来自SELECT * FROM student。核心是把 ResultSet 转成表格模型:
public void loadStudents(JTable table) { String[] columns = {"学号", "姓名", "性别", "班级", "专业"}; DefaultTableModel model = new DefaultTableModel(columns, 0); String sql = "SELECT id, name, gender, class_name, major FROM student"; try (Connection conn = DBUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { model.addRow(new Object[]{ rs.getString("id"), rs.getString("name"), rs.getString("gender"), rs.getString("class_name"), rs.getString("major") }); } } catch (SQLException e) { e.printStackTrace(); } table.setModel(model); }这里用 Statement 是因为没有用户输入参数,不存在注入风险。DefaultTableModel的第二个参数 0 表示初始行数为零,后面用addRow逐行追加。每次刷新都重新 setModel,而不是去清空旧 model,这样能避免表格里残留脏数据。删除操作拿到选中行的学号后,执行DELETE FROM student WHERE id = ?,如果该学生有成绩记录,外键会阻止删除,这时候要提示用户先删成绩或者做级联处理。
4.3 成绩录入与统计:一次插入多条与平均分计算
成绩录入通常有两种模式:单个录入和批量导入。单个录入就是选学生、选课程、填分数,然后 insert。批量导入一般是从表格里读多行,循环执行 insert,这时候用批处理会快很多:
public void batchInsert(List<Score> list) { String sql = "INSERT INTO score(student_id, course_id, score) VALUES(?,?,?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 关闭自动提交 for (Score s : list) { ps.setString(1, s.getStudentId()); ps.setString(2, s.getCourseId()); ps.setDouble(3, s.getScore()); ps.addBatch(); // 加入批次 } ps.executeBatch(); // 一次性提交 conn.commit(); } catch (SQLException e) { e.printStackTrace(); } }setAutoCommit(false)加上commit()是为了保证要么全成功要么全回滚,避免插了一半失败留下脏数据。统计功能一般是按课程算平均分、最高分、最低分,SQL 层面用AVG、MAX、MIN就能出结果,Java 这边只负责把结果填到界面标签上。如果要在 Java 里算,那就把某个课程的所有成绩查出来,用 Stream 的mapToDouble().average()处理,但数据量大时不如交给数据库。
5. 避坑与排查:跑不起来时先看这几条
5.1 中文乱码:数据库、连接、界面三处都要查
现象是学生姓名显示成问号或者方块。原因可能有三层:数据库建表时没指定 utf8,连接 URL 没加 characterEncoding,或者 Swing 组件的字体不支持中文。解决顺序是先确认SHOW CREATE TABLE student;里字符集是不是 utf8mb4,再检查 URL 参数,最后在代码里给 JLabel、JTable 设置支持中文的字体,比如new Font("微软雅黑", Font.PLAIN, 14)。
5.2 驱动加载失败:jar 包和类名双检查
现象是启动就报ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因通常是 jar 没加进 classpath,或者 MySQL 是 5.7 但用了 8.0 的驱动类名。解决办法是确认 Referenced Libraries 里有 connector jar,然后根据 MySQL 版本选对类名:5.7 用com.mysql.jdbc.Driver,8.0 用com.mysql.cj.jdbc.Driver。
5.3 时区报错:URL 参数补全
现象是连接时抛The server time zone value '...' is unrecognized。原因是 MySQL 8 的驱动要求明确指定时区。解决办法是在 URL 后面加上?serverTimezone=Asia/Shanghai或者?serverTimezone=UTC,同时保留useSSL=false避免 SSL 警告。
5.4 外键约束导致删除失败
现象是点删除学生时弹 SQLException。原因是 score 表里有该学生的成绩记录,外键阻止了删除。解决办法是先删成绩再删学生,或者在建表时把外键改成ON DELETE CASCADE,让数据库自动级联删除。两种方式各有取舍,级联删除方便但风险高,手动删更安全但代码多几行。
5.5 界面卡死:数据库操作别放事件线程
现象是点击查询按钮后界面无响应几秒钟。原因是 JDBC 查询是阻塞操作,直接跑在 Swing 的事件调度线程里。解决办法是把数据库操作放到SwingWorker的 doInBackground 里,查询完再在 done 方法里更新表格。数据量小的时候感觉不明显,一旦学生上千条就会很明显。
6. 二次开发怎么改:从单机版到带导出和权限的实用版
拿到这份源码之后,最值得做的改造不是加花哨界面,而是补两个真实场景里一定会用到的能力:数据导出和角色权限。下面说具体怎么动。
数据导出最实用的是导出 Excel。不用引入 POI 那么重的库,先用最简单的 CSV 实现:在成绩查询的 DAO 里加一个方法,把 ResultSet 逐行拼成逗号分隔的字符串,写进.csv文件。注意中文要用 UTF-8 BOM 开头,否则 Excel 打开会乱码。代码大概长这样:
public void exportCsv(String filePath) throws IOException { String sql = "SELECT s.id, s.name, c.name, sc.score " + "FROM score sc JOIN student s ON sc.student_id = s.id " + "JOIN course c ON sc.course_id = c.id"; try (Connection conn = DBUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8"))) { writer.write('\uFEFF'); // BOM,防 Excel 乱码 writer.write("学号,姓名,课程,成绩\n"); while (rs.next()) { writer.write(rs.getString(1) + "," + rs.getString(2) + "," + rs.getString(3) + "," + rs.getDouble(4) + "\n"); } } catch (SQLException e) { e.printStackTrace(); } }\uFEFF这个 BOM 字符是给 Excel 看的,不加的话中文列名会变成乱码。导出按钮绑定这个方法,用 JFileChooser 让用户选保存路径就行。
角色权限的改造稍微大一点,但思路清晰:在 user 表加一个 role 字段,登录成功后把角色存到一个全局的 Session 类里,主窗口根据角色决定哪些菜单可见。比如管理员能看到所有功能,教师只能录入和查询成绩,学生只能查自己的成绩。关键是在 DAO 层也要做校验,不能只靠界面隐藏按钮,否则改一下请求就能绕过。
验证改造是否成功,我一般会走一遍完整链路:用管理员登录 → 录入一条成绩 → 导出 CSV → 用 Excel 打开确认无乱码 → 换教师账号登录 → 确认看不到用户管理菜单。每一步都过了,才算改完。
从那以后我每次拿到这类课程设计源码,都强制自己先跑通登录和一条增删改查,再动任何一行代码。因为很多坑不在业务逻辑里,而在环境和配置上,先确认基线能跑,后面改出问题才知道是哪一步引入的。希望这份拆解能帮你少走点弯路。
本文还有配套的精品资源,点击获取