ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java+MySQL学生成绩管理系统:从表结构到JDBC封装与Swing实现

Java+MySQL学生成绩管理系统:从表结构到JDBC封装与Swing实现 简介基于MySQL与Java实现的学生成绩管理系统课程设计项目面向需要完成数据库或Java课程设计的在校生也适合想练习JDBC与Swing/FX开发的初学者。整个压缩包共96个文件大小238KB以Java源文件38个.java和编译后的.class文件50个为主另含SQL建库脚本与项目配置文件可导入开发环境后直接查看与运行。项目围绕成绩管理核心业务展开覆盖学生信息维护、课程信息管理、成绩录入与查询统计以及按角色区分的用户权限控制SQL脚本提供了studentdb数据库的表结构便于快速初始化。系统通过JDBC完成业务层与MySQL的数据交互并以表格、排序、筛选等方式呈现成绩数据还包含异常处理和基础测试意识能帮助读者理解完整Java课程设计的模块划分与编码组织方式。该资源已帮助4302位学习者适合作为课程设计参考或JavaMySQL整合练习的入门项目。1. 学生成绩管理系统拆解Java 和 Mysql 是怎么把成绩管起来的学生成绩管理系统在课程设计里太常见但真到了答辩前最容易暴露问题的地方并不是界面丑而是成绩表设计得前后矛盾、JDBC 连接参数配不明白、Swing 里查出来的结果集不知道往哪放。这个项目把学生信息、课程信息、成绩维护和登录权限都收进了一个 Eclipse 工程压缩包解开后能看到StudentScoreManagerSystem-master/bin和srcsrc下面按Main、Users、Dialog、DataBase分包至少不是一个类写到底的玩具。适合正在做 Java 课程设计、需要在一个完整例子上改功能的人也适合想补后端 Java 基础、把数据库表和业务代码串起来的前端同学。后面几章我按拆这个包的实际路线讲先管好 Mysql 表结构再封装 JDBC然后落到 Swing 界面最后补两个答辩前值得做的增强动作。2. studentdb.sql 拆解先把 Mysql 表结构和联合索引定对拿到StudentScoreManagerSystem-master压缩包后不要一上来就开 Eclipse 跑Main先看数据库脚本。工程里那个studentdb.sql通常是完整的建库建表脚本决定后面所有 Java 代码能不能稳定查询。课程设计版的学生成绩管理系统表不需要特别多但四类实体必须分开学生、课程、成绩、登录用户。最常见的设计错误是把所有字段塞进一张大表或者成绩表里只用单一自增主键却没有约束“同一个学生同一门课只能有一条成绩”。Mysql 的 InnoDB 引擎支持外键和事务既然是给课程设计用就该把这层关系立住。2.1 四张核心表学生、课程、成绩、用户先看表的规划。下面这张表可以作为你改造原项目时的基准和studentdb.sql的思路基本一致。表名主要字段作用studentstudent_no, name, class_name, gender, phone学生基本信息coursecourse_id, course_name, teacher, credit课程基本信息scorestudent_no, course_id, score, exam_time成绩明细一行表示一个学生的一门课usersusername, password, role, student_no登录账号与角色权限对应 SQL 脚本可以这么写CREATE DATABASE IF NOT EXISTS studentdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE studentdb; CREATE TABLE student ( id INT NOT NULL AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, name VARCHAR(50) NOT NULL, class_name VARCHAR(50), gender CHAR(1), phone VARCHAR(20), PRIMARY KEY (id), UNIQUE KEY uk_student_no (student_no) ) ENGINEInnoDB COMMENT学生信息表; CREATE TABLE course ( id INT NOT NULL AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL, course_name VARCHAR(100) NOT NULL, teacher VARCHAR(50), credit DECIMAL(3,1), PRIMARY KEY (id), UNIQUE KEY uk_course_no (course_no) ) ENGINEInnoDB COMMENT课程信息表; CREATE TABLE score ( id INT NOT NULL AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2), exam_time DATE, PRIMARY KEY (id), UNIQUE KEY uk_stu_course (student_no, course_id), KEY idx_course_id (course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_no) REFERENCES student (student_no) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course (id) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINEInnoDB COMMENT成绩表; CREATE TABLE users ( user_id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, role VARCHAR(10) NOT NULL DEFAULT student, student_no VARCHAR(20), PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT用户表;导入时如果脚本自带CREATE DATABASE直接用两个命令mysql -u root -p -e CREATE DATABASE IF NOT EXISTS studentdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -u root -p studentdb studentdb.sql不想用命令行就打开 MySQL Workbench把studentdb.sql整个拖进查询编辑器执行。关于脚本本身有几点要说明score用DECIMAL(5,2)而不是FLOAT因为浮点类型在百分制下容易出现89.9999这类精度问题student_no用VARCHAR(20)而不是数字方便以后出现带前缀的学号每张表都显式指定ENGINEInnoDB这样外键和事务才有效。2.2 联合唯一索引与外键成绩表真正的门槛上面脚本里最关键的一行是UNIQUE KEY uk_stu_course (student_no, course_id)。这个联合唯一索引就是 Mysql 创建索引的一个标准用法它保证同一学号加同一课程只能出现一次。实际使用中成绩重复录入是最常见操作失误尤其是教师手动录成绩时点两次保存。有了这层约束Java 代码里不需要先查一遍再决定是否插入直接执行插入捕获SQLIntegrityConstraintViolationException就能知道这条成绩已经存在。课程设计里这点足够但换成生产环境还会考虑逻辑删除这里不展开。外键的作用是防止成绩表里出现不存在的学生或课程。删除某个学生时如果他用ON DELETE CASCADE关联的成绩会一并清掉。这个设计在课程设计里很实用因为演示数据经常要反复清理。如果你希望保留历史成绩就把外键策略改成ON DELETE RESTRICT这时删除已有成绩记录的学生会被数据库拒绝Java 代码里也要给出对应提示。两种方式没有绝对对错关键是要知道自己选的是哪一种。2.3 查询和索引对不上时Mysql 的 explain 会告诉你答案学生成绩管理系统的查询基本是围绕score表展开的典型的联表查询长这样EXPLAIN SELECT s.student_no, s.name, c.course_name, sc.score FROM score sc JOIN student s ON sc.student_no s.student_no JOIN course c ON sc.id c.id WHERE s.student_no 20250101;执行EXPLAIN后重点看type列。eq_ref和ref说明关联走了索引如果出现ALL说明有一张表被全表扫描了。这个项目数据量不大全表扫描也感觉不到慢但答辩时老师只要问一句“这个查询走了哪个索引”就能看出你是不是真的理解 Mysql 的执行计划。关联字段上建了外键后Mysql 会自动为外键列创建索引score.student_no和score.course_id不需要再手动建。如果将来加了一个“按考试时间查成绩”的功能再给score.exam_time建一个普通索引即可。3. Java 连接 MysqlJDBC 驱动、URL 参数和 DataBase 类封装工程里DataBase这个包名很直白一般就是把 JDBC 操作收拢在一个类里。Java 连 Mysql 最绕不开的问题有三个驱动类名写错、URL 参数缺失导致中文乱码或时区报错、每次查询都新建连接不关闭。先解决这三个再谈业务代码。3.1 getConnection() 的写法url 参数比驱动类名更容易踩坑项目里常见的DataBase类是这么写的package StudentManageSystem.DataBase; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DataBase { private static final String URL jdbc:mysql://localhost:3306/studentdb?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghairewriteBatchedStatementstrue; private static final String USER root; private static final String PASSWORD 你自己的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }驱动类名要和你放的 jar 版本对应。课程设计常用两种Mysql Connector/J 5.x 用com.mysql.jdbc.Driver8.x 用com.mysql.cj.jdbc.Driver。如果你把 8.x 的 jar 放进 Eclipse 但还在写旧类名运行时会报ClassNotFoundException。Eclipse 里添加 jar 的常见做法是右键项目Build Path - Add External Archives选中下载好的mysql-connector-java-x.x.x.jar然后在.classpath文件里能看到对应记录。URL 里的参数逐个说明这也是 Mysql 连接问题的高发区参数作用建议值useUnicode是否使用 UnicodetruecharacterEncoding字符串编码解决中文乱码utf8useSSL本地开发是否加密连接falseserverTimezone指定数据库服务器时区Asia/ShanghairewriteBatchedStatements开启后批处理性能更好trueserverTimezone不写Mysql 8.0 和 JDBC 驱动之间经常报时区错误。characterEncodingutf8不写Swing 界面上输入的中文姓名存进数据库后可能是乱码。这几个参数不是越多越好但上面这些在课程设计环境里非常稳妥。3.2 用 PreparedStatement 而不是 Statement 拼接字符串访问数据库时我最推荐的做法是所有的 SQL 都走PreparedStatement。原因不只是防注入而是代码可读性和类型转换都更干净。下面是一个按关键字查询成绩列表的典型方法package StudentManageSystem.DataBase; import java.sql.*; import java.util.ArrayList; import java.util.List; public class ScoreDao { private DataBase db new DataBase(); public ListScoreVO listByKeyword(String keyword) { String sql SELECT s.student_no, s.name, c.course_name, sc.score, sc.exam_time FROM score sc JOIN student s ON sc.student_no s.student_no JOIN course c ON sc.course_id c.id WHERE s.student_no LIKE ? OR s.name LIKE ? ORDER BY sc.exam_time DESC; ListScoreVO list new ArrayList(); String kw keyword null ? : keyword.trim(); try (Connection conn db.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % kw %); ps.setString(2, % kw %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { ScoreVO vo new ScoreVO(); vo.setStudentNo(rs.getString(student_no)); vo.setStudentName(rs.getString(name)); vo.setCourseName(rs.getString(course_name)); vo.setScore(rs.getBigDecimal(score)); vo.setExamTime(rs.getDate(exam_time)); list.add(vo); } } } catch (SQLException e) { e.printStackTrace(); } return list; } public boolean updateScore(String studentNo, int courseId, double score) { String sql UPDATE score SET score ? WHERE student_no ? AND course_id ?; try (Connection conn db.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setDouble(1, score); ps.setString(2, studentNo); ps.setInt(3, courseId); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } }updateScore里的 SQL 就是 Mysql 的UPDATE语法配合占位符。成绩修改一般按“学号 课程”定位一条记录所以WHERE条件只写这两个字段。这里没有直接用字符串拼接避免了学生姓名里出现单引号导致 SQL 语法错误。JDBC 中setDouble(1, score)是把成绩参数绑定到第一个问号executeUpdate()返回的是影响行数0表示没有找到对应记录前台可以提示“请先录入成绩”。3.3 连接管理别在循环里反复 new Connection一个常见问题是把getConnection()写进while循环里查 100 个学生就开 100 个连接。虽然课程设计数据量小不一定崩但DriverManager建立连接要做 TCP 握手、认证开销明显比业务查询大。正确做法是每个方法在try-with-resources中获取连接方法结束自动关闭。如果并发高一些再换成 HikariCP 这类连接池但那是加分项课程设计阶段用上面的方式已经够了。Mysql 连不上的报错信息值得整理成一张排查表答辩时能说清楚比背概念有用报错信息常见原因处理方向Communications link failureMysql 服务没启动、端口被防火墙拦检查服务状态和 3306 端口ClassNotFoundException驱动 jar 没加入 Build Path重新添加 mysql-connector-javaUnknown databaseURL 中的库名不存在先执行 studentdb.sqlAccess denied for user用户名、密码、远程权限不对核对账号密码或授权The server time zone value is unrecognized驱动 8.x 缺少 serverTimezone调整 URL 参数4. Swing 界面与 JTable成绩查询、录入和角色按钮联动Java 桌面端的课程设计用 Swing 比 JavaFX 更省事不依赖额外的运行时。这个工程里Main一般放程序入口和登录窗口Users放用户相关界面Dialog放成绩、学生、课程的添加修改弹窗DataBase负责数据库连接。结构上就是典型的界面层、业务层、数据访问层分离只是没有把 Service 再单独拆出来。4.1 从 ResultSet 到 JTable表格数据刷新的标准动作成绩管理界面核心是一个JTable。很多初学者把数据从ResultSet里取出来后不知道怎么显示最后又去拼字符串。正确做法是准备一个DefaultTableModel把列头和数据行都交给它。下面这段代码是刷新成绩表格的常用写法private void loadData(String keyword) { String sql SELECT s.student_no, s.name, c.course_name, sc.score, sc.exam_time FROM score sc JOIN student s ON sc.student_no s.student_no JOIN course c ON sc.course_id c.id WHERE s.student_no LIKE ? OR s.name LIKE ? ORDER BY sc.exam_time DESC; try (Connection conn new DataBase().getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String kw keyword null ? : keyword.trim(); ps.setString(1, % kw %); ps.setString(2, % kw %); try (ResultSet rs ps.executeQuery()) { DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); while (rs.next()) { model.addRow(new Object[]{ rs.getString(student_no), rs.getString(name), rs.getString(course_name), rs.getBigDecimal(score), rs.getDate(exam_time) }); } } } catch (SQLException e) { JOptionPane.showMessageDialog(this, 成绩加载失败 e.getMessage()); } }创建表格时先指定列名比如new DefaultTableModel(null, new String[]{学号, 姓名, 课程, 成绩, 考试时间})后续addRow的数据顺序就按这个列定义来。setRowCount(0)是刷新而不是重复追加否则每次点击查询表格都会越积越长。查询条件用LIKE %?%空关键字会匹配所有数据这样搜索框和初始化界面可以共用同一个方法。4.2 成绩录入弹窗与保存事件成绩录入一般放在Dialog包里比如ScoreDialog。主窗口点击“编辑成绩”时先拿到选中行再把学号和课程 id 传进弹窗。事件监听代码大致是saveButton.addActionListener(e - { int row table.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选择一条成绩); return; } String studentNo table.getValueAt(row, 0).toString(); String courseName table.getValueAt(row, 2).toString(); new ScoreDialog(this, studentNo, courseName).setVisible(true); loadData(searchField.getText()); });getValueAt(row, 0)拿到的是当前行第 0 列的值也就是学号。ScoreDialog里保存按钮做的事是两步第一步校验输入的成绩范围第二步调用ScoreDao.updateScore。弹窗建议设置成setModal(true)否则主窗口关不掉后面再拿新数据刷新表格时容易出现时序问题。4.3 按角色控制按钮学生只能看教师只能改自己的课权限管理是这个系统里比较加分的模块。登录时从users表读出role在 Swing 界面里对按钮做setEnabled(false)比在数据库层做复杂控制更直观if (student.equals(currentUser.getRole())) { addButton.setEnabled(false); deleteButton.setEnabled(false); updateButton.setEnabled(false); } else if (teacher.equals(currentUser.getRole())) { deleteButton.setEnabled(false); }getRole()在用户登录后从users表查出存进一个LoginUser对象。按钮禁用后学生只能看成绩表教师能录入和修改但没有删除权限管理员保留全部权限。这样的实现虽然简单却是权限管理最基础的做法先在前端控住交互入口再在后端方法里校验身份不要只做前端按钮隐藏否则别人直接调用某个方法就能绕过界面。4.4 成绩非法输入前端校验和后端约束一个都不能少成绩输入校验不能只靠数据库。数据库里DECIMAL(5,2)能挡住超长数字但101这样的成绩也能写进去除非你手动加CHECK (score 0 AND score 100)。MySQL 8.0 会启用CHECK约束5.7 时代只是解析不执行。为了保险Java 界面侧也要拦截if (scoreValue 0 || scoreValue 100) { JOptionPane.showMessageDialog(this, 成绩必须在 0 到 100 之间); return; }这两层加在一起才能防止脏数据进入统计结果。成绩统计里如果出现一个 1000 分后面的平均分、及格率全都会被带偏。课程设计阶段把这一条写进ScoreDialog既能提升代码完成度也是面试八股文里常问的“前端校验和后端校验区别”的现成案例。5. 答辩前再补两个动作成绩 CSV 批量导入和 Mysql 自动备份课程设计项目到能用的程度不难但要是想在同组作品里有点区分度可以考虑加两个小功能成绩批量导入和数据库自动备份。这两个功能都不需要改界面大框架代码量也不大但能实实在在解决教师手工录入成绩太慢的问题。5.1 用 JDBC 批量插入处理 CSV 成绩单如果教师手里有一份从 Excel 导出的 CSV 文件里面是学号、课程编号、成绩、考试时间逐条插入太慢。用PreparedStatement.addBatch()可以一次性提交一批记录配合 URL 里已经配置的rewriteBatchedStatementstrue执行效率会明显高于单条插入。public void batchImport(String csvPath) { String sql INSERT INTO score(student_no, course_id, score, exam_time) VALUES (?,?,?,?); Connection conn null; try { conn db.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement(sql); BufferedReader reader new BufferedReader( new InputStreamReader(new FileInputStream(csvPath), StandardCharsets.UTF_8))) { String line; int count 0; while ((line reader.readLine()) ! null) { String[] f line.split(,); ps.setString(1, f[0].trim()); ps.setInt(2, Integer.parseInt(f[1].trim())); ps.setDouble(3, Double.parseDouble(f[2].trim())); ps.setDate(4, java.sql.Date.valueOf(f[3].trim())); ps.addBatch(); if (count % 500 0) { ps.executeBatch(); } } ps.executeBatch(); conn.commit(); } } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } throw new RuntimeException(e); } finally { if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }这段代码里setAutoCommit(false)让所有插入先不落盘executeBatch()每 500 条刷一次最后统一commit()。如果在中间某一行出现学号不存在或课程 id 为空的情况conn.rollback()会把这一整批回滚不会出现“导入一半成功一半失败”的尴尬状态。注意 CSV 里的日期格式要符合java.sql.Date.valueOf默认的yyyy-MM-dd。5.2 用 mysqldump 做每日备份项目答辩前先保住数据另一个值得加的是备份脚本。数据库文件不像普通文件可以随手复制正在运行中的 Mysql 表如果直接被拷贝容易出现数据不一致。用mysqldump导出是最稳妥的简单方案mysqldump -uroot -p123456 --single-transaction --default-character-setutf8mb4 studentdb D:\backup\studentdb_20250601.sql--single-transaction参数对 InnoDB 表有效备份过程中不会长时间锁表适合项目里正在写入成绩时执行。Windows 下可以把这个命令写进一个backup.bat然后用任务计划程序每天凌晨执行Linux 环境则把同一条命令放进 crontab。课程设计答辩前手动跑一次把备份文件导出来交给老师检查数据库结构也比只给一个studentdb.sql看起来完整得多。本文还有配套的精品资源点击获取
返回列表