Java学生成绩管理系统源码解析:从环境搭建到JDBC实战 简介这是一套面向高校计算机相关专业学生与Java初学者的学生成绩管理系统完整实践资料以Java语言结合GUI界面与数据库实现可作为期末课程设计、Java综合实验或自学练手项目使用。压缩包共80个文件约5.74MB包含28个java源文件、42个class编译文件、2个jar依赖包以及docx实验报告、xls成绩数据表和界面图片等配套素材源码与文档齐备导入后即可运行调试。系统功能覆盖登录验证、学生与教师注册、成绩的添加修改查询删除、文件导入成绩、学生账号管理及密码修改等模块结构完整、逻辑清晰。目前已有9124人学习下载适合需要参考完整项目架构、理解数据库连接与GUI交互实现、并借助实验报告梳理设计思路的读者也可作为课程作业的对照范本。1. 从一份能跑起来的 Java 成绩管理源码说起课程设计季一到后台问得最多的就是「有没有能直接跑、带数据库、还能交实验报告的 Java 学生成绩管理系统」。我拆的这份资源正好对上一套 Java 实现的学生成绩管理系统带 GUI 界面、带数据库脚本、附实验报告文档。它不是那种只有增删改查空壳的 Demo而是把学生信息、课程、成绩录入、查询统计、报表导出串成一条完整业务线适合三类人赶课程设计的学生、想拿一个完整 CRUD 项目练手的初级开发者、需要给学生演示「桌面端 数据库」分层结构的讲师。下面我按「它是什么 → 怎么跑起来 → 表怎么设计 → 坑在哪 → 怎么改造成自己的」这条线拆能抄的代码和参数我都给到位。2. 环境搭建与首次运行JDK、数据库、GUI 三件套怎么配这套系统的技术栈很朴素Java SE 做主体Swing 做 GUIJDBC 直连数据库没有 Spring 那套重框架。好处是依赖少、启动快、答辩时讲得清代价是所有连接管理、事务、异常都得自己写。先把运行环境对齐不然第一次跑必翻车。2.1 版本与依赖的选型理由JDK 建议 8 或 11别一上来就 17。原因很实际Swing 在 8/11 上最稳老项目里常见的sun.*内部类调用、javax.swing的默认外观在 17 上会有警告甚至行为差异而 JDBC 驱动版本又和数据库绑定。数据库二选一MySQL 5.7/8.0 或 SQLite。课程设计场景我一般推荐 MySQL因为实验报告里写「客户端-服务器架构」更像回事答辩也好讲如果只是本地演示、不想装服务SQLite 单文件更省事改一下驱动和 URL 即可。组件推荐版本说明JDK8 或 11Swing 兼容性最好数据库MySQL 8.0 / SQLite 3二选一驱动不同JDBC 驱动mysql-connector-java 8.x / sqlite-jdbc与数据库版本匹配构建纯 javac 或 Maven源码包一般给的是普通工程依赖只需要一个 JDBC 驱动 jar。用 Maven 的话在pom.xml里加dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency不用 Maven 就把 jar 丢进lib/目录编译和运行时都加进 classpath。这一步看着简单但驱动版本和数据库大版本不匹配是后面「连不上库」的头号原因先对齐再往下走。2.2 建库建表与初始化数据源码包里通常带一个.sql脚本先执行它。核心表一般四张学生表、课程表、成绩表、用户表登录用。下面是我按这类系统常见结构整理的最小可用建表语句字段名可按你拿到的脚本微调CREATE DATABASE IF NOT EXISTS score_mgr DEFAULT CHARSET utf8mb4; USE score_mgr; CREATE TABLE student ( sno VARCHAR(20) PRIMARY KEY, -- 学号 sname VARCHAR(50) NOT NULL, -- 姓名 gender CHAR(2), -- 性别 major VARCHAR(50), -- 专业 class_no VARCHAR(30) -- 班级 ); CREATE TABLE course ( cno VARCHAR(20) PRIMARY KEY, -- 课程号 cname VARCHAR(50) NOT NULL, -- 课程名 credit DECIMAL(3,1) -- 学分 ); CREATE TABLE score ( id INT AUTO_INCREMENT PRIMARY KEY, sno VARCHAR(20), cno VARCHAR(20), score DECIMAL(5,2), term VARCHAR(20), -- 学期 CONSTRAINT fk_sno FOREIGN KEY (sno) REFERENCES student(sno), CONSTRAINT fk_cno FOREIGN KEY (cno) REFERENCES course(cno), UNIQUE KEY uk_sno_cno_term (sno, cno, term) ); CREATE TABLE sys_user ( username VARCHAR(30) PRIMARY KEY, password VARCHAR(64) NOT NULL, -- 存哈希别存明文 role VARCHAR(10) -- admin / teacher );逻辑说明score表用(sno, cno, term)做唯一键防止同一学生同一课程同一学期重复录入这是成绩系统最容易出的脏数据外键保证成绩不会挂到不存在的学生或课程上。参数上score用DECIMAL(5,2)而不是FLOAT避免 59.999 显示成 60 这种玄学问题password留 64 位是给 SHA-256 十六进制串用的。2.3 连接配置与启动顺序数据库连接参数一般集中在一个db.properties或常量类里找到它改成你自己的jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/score_mgr?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的密码三个参数值得单独说serverTimezone不写MySQL 8 驱动会报时区异常characterEncodingutf8不写中文姓名和课程名可能变问号useSSLfalse是本地演示省掉证书配置生产别这么干。启动顺序固定为先起数据库服务 → 执行 SQL 脚本 → 确认db.properties指向正确库 → 编译运行主类。主类通常是带main的登录窗口或主窗体运行命令javac -encoding UTF-8 -cp lib/* -d out src/**/*.java java -cp out:lib/* com.example.MainWindows 下 classpath 分隔符是;不是:这是新手第一个翻车点。编译报「找不到符号」多半是没把驱动或某个包加进-cp运行报No suitable driver则是驱动类名写错或 jar 没进 classpath。3. 成绩录入与查询的核心链路JDBC 怎么写才不埋雷GUI 只是壳真正决定这套系统能不能用的是 JDBC 那几段代码。成绩录入、按条件查询、统计平均分本质都是「拼 SQL → 设参数 → 执行 → 映射结果」。写得好不好差别在连接管理和 SQL 注入防护上。3.1 用 PreparedStatement 堵住注入与类型错成绩查询界面一般有「学号、课程、学期」几个输入框很多人图省事直接字符串拼 SQL结果输入 OR 11就能把全表拉出来。正确写法是占位符public ListScoreVO queryScore(String sno, String cno, String term) throws SQLException { StringBuilder sql new StringBuilder( SELECT s.sno, s.sname, c.cname, sc.score, sc.term FROM score sc JOIN student s ON sc.sno s.sno JOIN course c ON sc.cno c.cno WHERE 11 ); ListObject params new ArrayList(); if (sno ! null !sno.isEmpty()) { sql.append(AND sc.sno ? ); params.add(sno); } if (cno ! null !cno.isEmpty()) { sql.append(AND sc.cno ? ); params.add(cno); } if (term ! null !term.isEmpty()) { sql.append(AND sc.term ? ); params.add(term); } ListScoreVO list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); // 参数从 1 开始 } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { ScoreVO vo new ScoreVO(); vo.setSno(rs.getString(sno)); vo.setSname(rs.getString(sname)); vo.setCname(rs.getString(cname)); vo.setScore(rs.getBigDecimal(score)); vo.setTerm(rs.getString(term)); list.add(vo); } } } return list; }逻辑说明WHERE 11是为了后面统一用AND追加条件省去判断「是不是第一个条件」的麻烦params列表和占位符顺序严格对应setObject的下标从 1 开始这是 JDBC 的硬规矩写成 0 会直接抛异常。try-with-resources保证连接、语句、结果集按逆序关闭漏关连接是桌面程序跑久了「卡死」的常见原因。参数上getBigDecimal读成绩比getDouble更稳避免浮点误差在界面上显示成 88.99999。3.2 连接管理与事务别让一条坏 SQL 拖垮整个界面Swing 是单线程模型所有数据库操作都在事件线程里同步执行一旦某条 SQL 慢或者连接泄漏界面直接假死。常见做法是把连接获取收敛到一个工具类并给录入操作加事务public class DBUtil { private static final Properties P new Properties(); static { try (InputStream in DBUtil.class.getResourceAsStream(/db.properties)) { P.load(in); Class.forName(P.getProperty(jdbc.driver)); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection( P.getProperty(jdbc.url), P.getProperty(jdbc.username), P.getProperty(jdbc.password)); } }批量导入成绩时一个学生一条记录中途失败不能只写一半try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交 try (PreparedStatement ps conn.prepareStatement( INSERT INTO score(sno,cno,score,term) VALUES(?,?,?,?))) { for (ScoreRow row : rows) { ps.setString(1, row.getSno()); ps.setString(2, row.getCno()); ps.setBigDecimal(3, row.getScore()); ps.setString(4, row.getTerm()); ps.addBatch(); } ps.executeBatch(); conn.commit(); // 全部成功才提交 } catch (SQLException e) { conn.rollback(); // 任一失败整体回滚 throw e; } }参数说明setAutoCommit(false)是事务的前提addBatch/executeBatch把 N 条插入合并成一次网络往返几百条成绩导入从「转圈十几秒」降到「秒级」rollback放在 catch 里保证要么全进要么全不进。这里有个边界MySQL 的executeBatch遇到唯一键冲突会抛BatchUpdateException但前面已成功的批次在部分驱动下可能已生效所以回滚必须显式写不能指望自动。3.3 统计查询与结果映射平均分、及格率、排名这类统计交给 SQL 做比在 Java 里循环快得多SELECT c.cname, COUNT(*) AS total, ROUND(AVG(sc.score), 2) AS avg_score, SUM(CASE WHEN sc.score 60 THEN 1 ELSE 0 END) / COUNT(*) AS pass_rate FROM score sc JOIN course c ON sc.cno c.cno WHERE sc.term ? GROUP BY c.cno, c.cname ORDER BY avg_score DESC;CASE WHEN算及格人数是标准写法比在 Java 里再查一遍省一次往返ROUND(...,2)控制小数位避免界面显示一长串。映射到 Java 时用rs.getDouble(avg_score)即可注意列别名要和getXxx的字符串完全一致大小写敏感在部分数据库上会咬人。4. 表结构与业务规则的避坑清单五个真实会翻车的点这套系统跑通不难难在数据不出错、界面不假死、答辩不被问倒。下面五条是我拆这类项目时反复见到的坑按「现象 → 原因 → 解决」记。4.1 中文变问号或乱码现象学生姓名、课程名在界面和数据库里显示成???。原因建库时没指定utf8mb4或 JDBC URL 缺characterEncodingutf8或javac编译时没加-encoding UTF-8。解决三处一起改——建库语句带DEFAULT CHARSET utf8mb4URL 加编码参数编译命令加-encoding UTF-8。只改一处往往还是乱因为链路任何一环掉链子都白搭。4.2 登录成功但主界面空白现象输入账号密码能进主窗体一片灰。原因Swing 组件在非事件调度线程EDT里创建或者setVisible(true)之前没pack()。解决主窗体启动包一层SwingUtilities.invokeLater(() - new MainFrame().setVisible(true))布局用BorderLayout/GridBagLayout而不是绝对坐标窗口大小交给pack()算。绝对坐标在换分辨率后必错位这是血泪经验。4.3 删除学生时报外键约束失败现象删学生提示Cannot delete or update a parent row。原因score表还有该学号的成绩记录外键挡住了。解决两种策略二选一——业务上先删成绩再删学生软删除更稳加status字段标记或者建外键时加ON DELETE CASCADE。课程设计里我一般选软删除因为答辩时「删除即物理消失」容易被追问数据可追溯性。4.4 成绩录入重复不报错现象同一学生同一课程录了两次统计平均分被拉低。原因score表没建唯一键或者建了但代码里用INSERT没捕获冲突。解决加UNIQUE(sno,cno,term)代码里捕获SQLIntegrityConstraintViolationException后提示「该成绩已存在请修改」。别用「先查再插」并发下照样重复唯一键才是最后一道闸。4.5 打包后连不上数据库现象IDE 里跑得好好的打成 jar 双击就报驱动找不到。原因驱动 jar 没一起打进去或db.properties用绝对路径读不到。解决用 Maven 的maven-shade-plugin打 fat jar配置文件放src/main/resources用getResourceAsStream读命令行跑时确认-cp包含驱动。这一条在交作业前一定要在「干净目录」里验一遍别只在 IDE 里自测。5. 从能跑到能改把这份源码变成你自己的东西拿到源码只是起点真正拉开差距的是你能不能按自己的题目改。我给一个具体的改造路径顺便说怎么验证改对了。第一步换业务字段。比如题目要求加「平时成绩、期末成绩、总评」三列那就改score表加两列把原来的单列score拆成regular_score、final_score总评用生成列或查询时算ROUND(regular_score*0.3 final_score*0.7, 2)。改表后同步改ScoreVO、录入界面、查询 SQL 三处漏一处就出现「界面有、库里没有」的错位。第二步加一个统计报表导出。用Apache POI把统计结果写成 Excel核心就几行try (XSSFWorkbook wb new XSSFWorkbook()) { Sheet sheet wb.createSheet(成绩统计); Row header sheet.createRow(0); String[] cols {课程, 人数, 平均分, 及格率}; for (int i 0; i cols.length; i) header.createCell(i).setCellValue(cols[i]); int r 1; for (StatVO vo : stats) { Row row sheet.createRow(r); row.createCell(0).setCellValue(vo.getCname()); row.createCell(1).setCellValue(vo.getTotal()); row.createCell(2).setCellValue(vo.getAvgScore().doubleValue()); row.createCell(3).setCellValue(vo.getPassRate().doubleValue()); } try (FileOutputStream out new FileOutputStream(成绩统计.xlsx)) { wb.write(out); } }参数上注意setCellValue(double)和setCellValue(String)是重载传错类型编译能过但导出内容会怪及格率存的是 0~1 的小数要在单元格样式里设成百分比格式才好看。第三步验证。改完别只看界面跑三条自检 SQLSELECT COUNT(*) FROM score对总数、SELECT sno,cno,term,COUNT(*) FROM score GROUP BY 1,2,3 HAVING COUNT(*)1应返回空查重复、随机抽一个学生手算总评和系统显示比对。这三条过了基本能交差。我自己的习惯是每次改完表结构先把「增删改查 统计 导出」六条主路径各点一遍再跑上面三条 SQL最后在干净目录打一次包双击验证。这套流程救过我两次——一次是忘了同步 VO 字段导致导出空列一次是打包漏了配置文件。从那以后我每次交付前都强制走一遍这个清单省得答辩现场翻车。希望帮到你。本文还有配套的精品资源点击获取