资讯动态

Java+Swing+MySQL学生选课及成绩管理系统:从零到答辩的完整实践

发布时间:2026/10/8 14:31:57 来源:尧图企业网站定制
简介这份基于Java Swing与MySQL的学生选课及成绩管理系统课程设计面向计算机相关专业学生及入门开发者适合用作毕业设计、课程设计或工程实训的参考项目。资源包共53个文件压缩后约8.63MB包含16个Java源文件、1个SQL数据库脚本、6个XML配置及说明文档并配有20张界面截图与E-R图、系统结构图便于查看运行效果与数据库设计。项目提供管理员、教师、学生三类预置账号登录测试方便数据库连接配置集中在JdbcConfig类中附有环境搭建说明与README可快速部署。涉及登录注册、学生信息管理、课程管理、选课、成绩查询与修改密码等模块流程完整。已有219人学习/下载适合需要完整选课、成绩管理业务流程参考或希望掌握Swing界面开发与MySQL数据操作的学习者。1. 基于 Java Swing MySQL 的学生选课及成绩管理系统课程设计为什么绕不开这个组合临近课程设计答辩经常有人问“用 Java 写管理系统界面选 Swing 还是 JavaFX”。我的答案很直接如果你没有前端基础、想在两周内把一个学生选课及成绩管理系统从零跑到演示Java Swing MySQL 是性价比最高的组合。Swing 负责窗口、表格、按钮这些交互JDBC 负责和 MySQL 通信数据库设计决定业务能不能自圆其说。这个组合能把你平时零散学的 JDBC、事件监听、事务、表关系建模全部串成一条线。适合谁正在做课程设计或毕业设计的本科生以及想补桌面应用开发经验的 Java 工程师。别小看这个“老”技术栈面试考基础时PreparedStatement 防注入、事务的 ACID、外键约束都能在这个系统里找到活例子。更关键的是这套系统的业务足够清晰学生登录、浏览课程、选课退课、老师录成绩、学生查成绩。每一步拆下去都是面试官最爱问的经典场景。我会按实际交付课程设计的顺序走先定环境与项目骨架再设计数据库然后写 DAO 和 Swing 界面最后是踩坑记录和答辩前的进阶准备。如果你正卡在“老师要求用 Swing 但网上的例子都是控制台程序”这个阶段这篇笔记能直接帮你把整条链路跑通。2. 环境与骨架先行JDK、MySQL 5.7/8.0 选型与 Maven 目录结构2.1 JDK 与 MySQL 版本怎么选JDK 8 还是 JDK 17MySQL 5.7 还是 8.0网上 Java 环境配置教程很多但课程设计现场最容易翻车的不是装不上而是装完连不上。先定版本基调JDK 8 仍然是最稳的选择Swing 在 JDK 8 里开箱即用老师的机器大概率也是 JDK 8。JDK 11 和 JDK 17 都是 LTS能跑 Swing但要注意 JDK 9 之后模块化Swing 被放进了java.desktop模块。如果你用 IDEA 直接跑普通 classpath 项目一般感觉不到差别一旦项目里写了module-info.java就要在模块描述里加requires java.desktop;否则编译直接报“模块找不到 java.desktop”。我的建议是课程设计老老实实用 JDK 8少一个变量就少一个坑。配好之后用命令行验证不要只看 IDE 里的版本java -version javac -version echo $JAVA_HOMEjava -version输出的是运行环境javac -version输出的是编译器。如果两个版本不一致说明 PATH 里混入了多个 JDK。我见过最典型的翻车现场IDEA 里配的 JDK 17命令行里java -version却是 1.8结果数据库驱动报了UnsupportedClassVersionError。还有一点JDK 9 之后不要再手动配 CLASSPATH 环境变量那是老古董做法会干扰 Maven 和 IDE 的依赖管理。MySQL 版本的选择更关键因为它直接决定你用什么驱动和什么连接串。我一般按这个规则来本机 MySQL 版本驱动类推荐驱动坐标5.7.x比如 5.7.44com.mysql.jdbc.Drivermysql:mysql-connector-java:5.1.498.0.xcom.mysql.cj.jdbc.Drivercom.mysql:mysql-connector-j:8.0.x8.0.x 连 5.7 库com.mysql.cj.jdbc.Driver8.0.x 驱动向后兼容只配一套的话我建议直接用 8.0.x 驱动它能同时连 5.7 和 8.0。但要特别注意MySQL 8.0 默认认证插件是caching_sha2_password如果你拿着 5.1.49 的老驱动去连 8.0第一次握手就会报Unable to load authentication plugin。反过来如果你按网上 MySQL 安装教程装好了 5.7.44又下载了 8.0 的驱动连接串里必须显式写serverTimezoneAsia/Shanghai不然驱动会把数据库时区映射成 UTC时间相关字段会差 8 小时。注意MySQL 8.0 的连接 URL 至少要带useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai这三个参数少一个都可能在一开始就把你拦在门外。这个我在第 5 章还会展开说。2.2 用 Maven 还是 IDE 直接建项目依赖管理与推荐的目录骨架很多课程设计教程让你直接 IDEA 新建空项目然后手动把mysql-connector-java.jar拖进lib目录。这个做法能跑但换一台电脑答辩时经常会遇到“ClassNotFoundException: com.mysql.cj.jdbc.Driver”因为 jar 没被正确导入。我习惯用 Maven 管理依赖pom 文件一提交任何机器上都能还原环境。properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency /dependencies这段配置里maven.compiler.source和target锁死编译级别为 JDK 8避免在 JDK 17 机器上编译出 class 版本过高的产物。UTF-8编码必须显式声明否则 Windows 上默认 GBK中文字符串字面量会被编译成乱码。MySQL 驱动的坐标我写的是新版com.mysql:mysql-connector-j如果你的 Maven 仓库拉不到也能用老的mysql:mysql-connector-java:8.0.33两者内容一致只是官方迁移了命名空间。Maven 下载慢就配阿里云镜像在settings.xml的 mirror 节点加一行urlhttps://maven.aliyun.com/repository/public/url课程设计的依赖只有几 MB配好镜像一分钟内肯定拉完。项目目录骨架我固定用下面这套按包名就能看出职责边界包名职责model实体类Student、Course、SC字段对应数据库表dao数据访问层只写 JDBC 和 SQL不写界面逻辑service业务层选课事务、成绩校验、登录验证viewSwing 界面登录窗口、主窗口、成绩表格util工具类DBUtil 连接管理、常量定义有人会问课程设计直接 JDBC DAO 就够了为什么不用 MyBatis我的看法是MyBatis 生成建表 SQL、动态 SQL 这些能力确实实用但 Swing 项目的核心是让你把 JDBC 底层的PreparedStatement、ResultSet、事务提交讲清楚。用框架会把最该展示的基础能力遮住答辩老师追问一句“你的 DAO 每个方法底层怎么管理连接的”你会很被动。纯 JDBC 写一次后面再学 MyBatis 就是顺水推舟的事。3. 数据库设计先于代码四张表的关系模型与初始化 SQL3.1 关系模型的核心为什么成绩字段放在选课关联表里学生和课程是多对多关系一个学生可以选多门课一门课可以被多个学生选。这种关系在关系型数据库里的标准建模方式是拆三张表学生表、课程表、选课关联表我通常命名sc。这个模型本身不复杂真正让很多人栽跟头的是“成绩字段放哪”。我的原则是成绩是“某学生对某门课某一次选课行为的结果”它既不属于学生实体也不属于课程实体而是属于学生和课程之间的关系。所以score字段放在sc表里student表里不出现成绩course表里也不出现成绩。这样设计以后你要查“张三的数据库成绩”只需要SELECT score FROM sc WHERE student_id ? AND course_id ?要算某门课的平均分AVG(score)直接作用在sc表上。我第一次做这个课题时图省事在student表里加了course_name和score两个字段结果一个人选了三门课就得拆三行记录统计和更新全是灾难最后推倒重来。在sc表上还要加一个联合唯一索引UNIQUE KEY uk_sc (student_id, course_id)这是数据库层面防止重复选课的兜底。没有这个约束就要靠 Java 代码先查一次“是否已选”并发场景下两个请求同时查到“未选”然后都执行插入就会产生重复选课记录。加了唯一索引后第二次插入会被SQLIntegrityConstraintViolationException拦下程序捕获这个异常提示“你已经选过这门课”逻辑就干净了。字段类型和数据完整性再抠三个细节学号用VARCHAR(20)不用INT因为学号可能出现前导零比如001转成数字会丢零成绩用DECIMAL(5,2)不用FLOATFLOAT是近似值算平均分时会出现 88.499999 这种尴尬值DECIMAL是精确小数密码字段留VARCHAR(64)这是给 BCrypt 或者 SHA-256 哈希之后的结果留的长度如果只存明文64 位足够存盐值加哈希值。3.2 初始化 SQL 与测试数据从建库到可演示的完整脚本初始化脚本要能在新的 MySQL 环境里一键执行所以我习惯把建库、建表、插入测试数据全写在一个.sql文件里用 Navicat 或命令行source执行。注意脚本开头带DROP DATABASE IF EXISTS这是给“翻车了重来”留的后悔药重新执行一遍就能恢复出厂状态。DROP DATABASE IF EXISTS student_course; CREATE DATABASE student_course DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_course; CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, password VARCHAR(64) NOT NULL COMMENT 密码, major VARCHAR(50) COMMENT 专业 ) ENGINEInnoDB; CREATE TABLE course ( id INT AUTO_INCREMENT PRIMARY KEY, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程编号, name VARCHAR(50) NOT NULL COMMENT 课程名, credit DECIMAL(3,1) NOT NULL COMMENT 学分, teacher VARCHAR(50) COMMENT 授课教师, capacity INT NOT NULL DEFAULT 30 COMMENT 容量 ) ENGINEInnoDB; CREATE TABLE sc ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) NULL COMMENT 成绩未出分为NULL, UNIQUE KEY uk_sc (student_id, course_id), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB; INSERT INTO student (student_no, name, password, major) VALUES (2024001, 张三, 123456, 计算机科学与技术), (2024002, 李四, 123456, 软件工程); INSERT INTO course (course_no, name, credit, teacher, capacity) VALUES (C001, 数据结构, 4.0, 王老师, 60), (C002, 数据库原理, 3.5, 赵老师, 50), (C003, 操作系统, 3.5, 孙老师, 40); INSERT INTO sc (student_id, course_id, score) VALUES (1, 1, 88.5), (1, 2, NULL), (2, 2, 91.0), (2, 3, 77.0);字符集用utf8mb4而不是utf8utf8mb4才是完整的 Unicode 字符集能存表情符号更重要的是它兼容所有中文生僻字和特殊符号。COLLATE utf8mb4_unicode_ci表示排序规则不区分大小写做学号模糊搜索时行为更符合直觉。表注释和字段注释我建议写全。MySQL 8.0 之后information_schema能直接查注释答辩时老师问“你为什么用 DECIMAL”你打开SHOW CREATE TABLE sc能看到注释里写着的“成绩未出分为 NULL”这种细节很加分。测试数据的编排也有讲究。两条学生记录、三条课程记录、四条选课记录覆盖了“已有成绩、未出分、多个学生选同一门课”三种状态。演示的时候正好能展示张三查成绩有两行一行显示 88.5一行显示“未出分”李四查成绩有两行全部有分数。如果只插“都有分”的数据未出分状态的界面逻辑就无法演示答辩时老师让你现场加个学生选个课可能当场暴露空指针。4. 从 JDBC 到 Swing 界面三层结构如何落地成可运行代码4.1 连接工具类与 DAO 层PreparedStatement 和防注入的标准写法先写连接工具类这是整个系统的黑匣子入口。我习惯把数据库连接参数放到db.properties文件里而不是硬编码在 Java 代码中。这样做的好处是换一台电脑答辩时只需要改这个文件里的密码不用重新编译。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_course?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai jdbc.userroot jdbc.password123456工具类用静态代码块加载驱动并提供统一的连接获取入口package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ResourceBundle; public class DBUtil { private static String url; private static String user; private static String password; static { try { ResourceBundle bundle ResourceBundle.getBundle(db); url bundle.getString(jdbc.url); user bundle.getString(jdbc.user); password bundle.getString(jdbc.password); Class.forName(bundle.getString(jdbc.driver)); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(找不到MySQL驱动检查pom依赖); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }ResourceBundle.getBundle(db)会从 classpath 根目录读取db.properties文件注意文件不要带.properties后缀。Class.forName注册驱动的写法在 JDBC 4.0 以后可以省略因为驱动包里有META-INF/services自动注册机制但保留Class.forName能让代码更明确也方便你在捕获ClassNotFoundException时抛出“检查依赖”的友好提示。DAO 层以登录方法为例感受PreparedStatement和Statement的差别public boolean login(String studentNo, String password) { String sql SELECT COUNT(*) FROM student WHERE student_no ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentNo); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { rs.next(); return rs.getInt(1) 0; } } catch (SQLException e) { e.printStackTrace(); return false; } }PreparedStatement的?占位符会把传入值当作纯数据而不是 SQL 片段这是防注入的基础。如果写成SELECT COUNT(*) FROM student WHERE student_no studentNo 那么输入 OR 11就能绕过密码校验。代码里我用的是 try-with-resources 写法Connection、PreparedStatement、ResultSet在 try 块结束时会自动关闭避免连接泄漏。课程设计的 Java 版本只要在 JDK 7 以上都支持这个语法。注意ps.setString的参数从 1 开始计数setInt、setBigDecimal同理字段顺序和 SQL 中?顺序必须一一对应。4.2 Swing 界面与事件监听从登录窗口到成绩表格的完整链路Swing 界面在整个系统里的定位是“薄显示层”只负责把用户操作转发给 service/dao不直接写 SQL。这是 Swing 项目最容易忽略的原则——很多人图省事在ActionListener里直接写DriverManager.getConnection最后界面和数据库逻辑完全纠缠在一起改个字段名要翻整个类。登录窗口的核心代码JFrame frame new JFrame(学生选课及成绩管理系统); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 320); frame.setLayout(new GridBagLayout()); JTextField noField new JTextField(16); JPasswordField pwdField new JPasswordField(16); JButton loginBtn new JButton(登录); loginBtn.addActionListener(e - { String no noField.getText().trim(); String pwd new String(pwdField.getPassword()); if (studentDao.login(no, pwd)) { JOptionPane.showMessageDialog(frame, 登录成功); new MainFrame(no); frame.dispose(); } else { JOptionPane.showMessageDialog(frame, 学号或密码错误); } });这里有两个细节。第一JPasswordField.getPassword()返回的是char[]而不是String这是出于安全考虑——char[]用完后可以手动清空而String会留在常量池里。我一般会new String(pwdField.getPassword())转成字符串然后立刻把char[]引用置空。第二事件监听器是典型的ActionListener匿名内部类写法Lambda 表达式简化后代码更短。注意 Swing 事件回调都在事件分发线程EDT上执行所以loginBtn.addActionListener里的代码不能做耗时操作否则窗口会卡死。课程设计的登录查询毫秒级返回感受不明显但成绩报表查询或导出 PDF 这种秒级操作必须用SwingWorker。成绩展示用JTable DefaultTableModel组合这是 Swing 中最容易做数据刷新的方案String[] columns {课程编号, 课程名称, 学分, 成绩, 授课教师}; DefaultTableModel model new DefaultTableModel(columns, 0); JTable table new JTable(model); new SwingWorkerListCourseScore, Void() { Override protected ListCourseScore doInBackground() { return scDao.findScoresByStudent(no); } Override protected void done() { try { ListCourseScore list get(); model.setRowCount(0); for (CourseScore cs : list) { model.addRow(new Object[]{ cs.getCourseNo(), cs.getCourseName(), cs.getCredit(), cs.getScore() null ? 未出分 : cs.getScore().toPlainString(), cs.getTeacher() }); } } catch (Exception ex) { JOptionPane.showMessageDialog(table, 成绩加载失败); } } }.execute();DefaultTableModel的setRowCount(0)是先清空表格再逐行addRow这是刷新 JTable 的标准操作。注意get()方法必须放在done()里调用因为它会重新抛出doInBackground里的异常。成绩字段是BigDecimal直接用toString()可能会显示科学计数法用toPlainString()保证显示为88.50这样的普通小数。SwingWorker的两个泛型参数第一个是后台返回结果类型第二个是增量发布类型这里不用增量所以填Void。5. 这些坑让课程设计翻车5 个常见问题与排查路径5.1 MySQL 8.0 连接报错Public Key Retrieval is not allowed现象程序第一次执行DriverManager.getConnection抛SQLNonTransientConnectionException: Public Key Retrieval is not allowed。原因MySQL 8.0 默认认证插件改成caching_sha2_password客户端首次连接需要从服务器获取公钥做 RSA 加密密码。驱动出于安全考虑默认不自动获取公钥需要连接参数显式授权。解决连接 URL 加上allowPublicKeyRetrievaltrue同时useSSLfalse。因为allowPublicKeyRetrieval要求关闭 SSL两者缺一不可。如果你的 MySQL 是 5.7不会遇到这个问题但加了这两个参数也不会报错统一加上更省心。5.2 控制台和界面中文全部变成问号现象插入的中文数据显示为???或者 SQL 执行报Incorrect string value: \xE5\xBC\xA0...。原因建库时没指定字符集默认用了latin1或连接 URL 没带characterEncodingutf8或 IDE 的编译编码是 GBK 而数据库是 UTF-8。解决三层统一。建库 SQL 用DEFAULT CHARACTER SET utf8mb4连接 URL 加useUnicodetruecharacterEncodingutf8IDEA 里Settings - File Encodings把 Global Encoding、Project Encoding、Properties Files 全设成 UTF-8。命令行执行 SQL 时Windows 下先用chcp 65001切到 UTF-8 代码页再source脚本否则命令行输入的中文本身就是乱码。5.3 用了 Statement 拼接 SQL单引号直接导致登录绕过现象在学号输入框输入1 OR 11密码随便填竟然登录成功了。原因代码用了Statement字符串拼接SELECT ... WHERE student_no no 被拼成了student_no 1 OR 11恒真条件直接绕过验证。解决全部换成PreparedStatement占位符。注意模糊查询也要用占位符但LIKE的匹配符得在传参时拼好例如ps.setString(1, % keyword %)不能写成LIKE %?%。这个错误在课程设计代码审查里几乎是必查项原因不止是安全问题——拼接 SQL 遇到中文和特殊字符时少转义一个单引号就会整个查询失败。5.4 改完成绩再查界面显示的还是旧值现象在数据库里UPDATE sc SET score 95 WHERE ...回到程序重新查询成绩还是原来的。原因连接没有提交事务。Java 代码里手动开启了事务但忘了conn.commit()或者 DAO 每次都拿同一个连接且连接处于setAutoCommit(false)状态。还有一种常见原因程序用了内存缓存查询走了旧缓存。解决先检查代码里有没有setAutoCommit(false)有的话在事务块末尾必须commit()异常路径上要rollback()。其次确认每次查询都调用DBUtil.getConnection()拿到新连接。课程设计阶段不推荐做缓存等答辩完再优化不迟。5.5 Swing 窗口一点按钮就转圈界面被 JDBC 查询阻塞现象点击“查询成绩”按钮后窗口卡住鼠标变成漏斗过几秒才恢复数据量大时直接假死。原因数据库查询被直接写在ActionListener里阻塞了事件分发线程EDT。EDT 负责所有界面重绘和事件处理被阻塞后按钮点击、窗口拖动全部失效。解决用SwingWorker把耗时查询挪到后台线程。代码模板就是 4.2 节成绩表格那段核心是doInBackground()里做 JDBCdone()里更新 JTable。注意不要在doInBackground()里碰任何 Swing 组件Swing 组件不是线程安全的跨线程修改界面会随机出现“GUI 组件被并发修改”的异常。6. 从能跑到能答辩事务处理、连接池与演示技巧的进阶准备课程设计“能跑”和“能答辩”之间隔着两层代码经不经得起追问演示能不能覆盖边界场景。先说事务。选课操作至少要执行“查余量”和“插入选课记录”两步这两步之间如果程序崩溃就会留下“余量扣了但记录没插入”的脏数据。所以选课必须包在事务里Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); try { // 第一步用 SELECT ... FOR UPDATE 锁住课程行查余量 // SELECT capacity FROM course WHERE id ? FOR UPDATE // 第二步验证余量大于当前选课人数 // INSERT INTO sc (student_id, course_id) VALUES (?, ?) conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }SELECT ... FOR UPDATE是行级锁两个学生同时选同一门课时后一个会被阻塞到前一个提交。这一串代码写下来答辩时老师问“并发情况下怎么防止超选”你直接把这五行讲清楚比任何口头解释都有说服力。连接池这块我建议课程设计阶段不引入 HikariCP但要在脑子里知道为什么。纯DriverManager.getConnection每次新建物理连接MySQL 默认最多 151 个连接你反复开关窗口连 100 次就会报Too many connections。代码里只要确保 try-with-resources 及时关闭连接课程设计的并发量远够不到上限。如果答辩想加分可以准备一句话生产环境会用 HikariCP 替代手动连接管理因为连接复用能省掉 TCP 握手和认证的开销。不要为了显摆框架而在课程设计里强行替换那会引入新的配置复杂度和展示风险。演示技巧是我最想强调的。答辩前准备三种数据正常数据、边界数据、异常操作。正常数据让老师看到成绩列表和未出分的“未出分”提示边界数据是一张容量为 1 的课程现场演示两个学生抢同一门课第二个学生看到“课程已满”异常操作是重复选课点击选课后程序弹出“你已经选过这门课”。很多课程设计翻车不是代码有问题而是演示时老师随便点了一个你没准备的场景界面抛异常堆栈。最后说一个我的血泪教训第一次做这个课题时我把成绩字段放在学生表里为了算一门课的平均分写了一大堆自关联 SQL答辩时被老师问“你为什么不拆选课表”当场卡住。后来老老实实按关系模型拆表后面所有统计都变成了一行AVG(score)。你从一开始就按本文的模型走不会走这个弯路。如果做过程中遇到连接问题先查SHOW VARIABLES LIKE character_set%再查连接 URL这两个位置解决 80% 的乱码和连接失败。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑