
简介这是一份基于Java Swing与MySQL的学生选课管理系统完整项目面向计算机专业正在准备课程设计或期末大作业的学生也适合需要Java桌面应用实战的初学者。项目源于作者大三期末大作业经导师指导和认可评审分99分代码完整、可直接运行能够帮助读者快速理解Swing图形界面与MySQL数据操作的整合方式并掌握Java桌面项目从数据库设计到界面编码的完整流程。资源包共50个文件、约8.6MB主要包含16个Java源文件、1个SQL数据库脚本、6个XML工程配置、MySQL驱动jar包以及README文档说明另有20张界面截图和2张数据库E-R图、系统结构图覆盖登录、选课、成绩管理、学生信息管理、修改密码等模块方便对照界面和代码进行学习。目前已有140人学习下载既可直接作为课程设计或期末大作业的参考也可以从中吸收SwingMySQL项目的分层结构、数据库表设计与异常处理思路是Java初学者完成实战项目的实用性材料。1. 为什么拿去做课设的SwingMySQL选课系统值得重写一遍从网盘或分享链接解压出 System-master 文件夹里面是 JavaSwingMysql 学生选课管理系统带着 mysql-connector-j-8.3.0.jar、十几张界面截图和 README第一反应多半是“代码这么全跑起来就能交差”。但真正打开后ClassNotFoundException、连接超时、中文乱码轮着来因为这类源码把环境依赖和数据库初始化藏在细节里。这套系统用 Swing 做桌面端、MySQL 做存储覆盖登录注册、学生信息管理、课程管理、选课、成绩录入与查询是期末大作业和课程设计里典型的 CRUD 项目。对新手它是能看懂的课设源码对工作几年的开发者也值得拆开看 Swing 事件分发和 JDBC 事务怎么协作。接下来拆表结构、登录会话、选课并发三个关键点最后给出可落地的排错清单。2. 从数据库E-R图到建表SQL选课系统的数据模型怎么落2.1 先理解为什么是五张表而不是一张大表压缩包里的 数据库E-R图.jpg 和 系统结构图.jpg 不是装饰它们是期末大作业答辩时的第一张 PPT。选课管理系统的核心实体是学生、课程和成绩但实际开发时还要把登录账号和选课关系拆出来。如果全塞进一张学生表字段会出现大量 NULL课程倍数的数据也会让后期统计无从下手。这个项目没有用 MyBatis数据访问层是原生 JDBC表结构直接决定 DAO 层要写多少个方法所以建表之前务必先把 E-R 图对齐。常见做法是拆成五张表user 管账号和角色student 管学生基本信息course 管课程与容量select_course 管选课关系score 管成绩。选课和成绩分开是为了让“学生选了课但老师没录入”的情况也有一条选课记录成绩表里只放已出分的行。这样统计选课人数时不会把 NULL 成绩算进去也符合第三范式。唯一需要额外处理的是“一门课成绩只能有一条记录”这个约束在业务代码里检查也可以用唯一索引兜底后面会讲到。2.2 核心表结构和 E-R 关系说明表名关键字段职责userid, username, password, role登录账号role 区分 student/ adminstudentstudent_no, student_name, gender, major, class_name学生档案coursecourse_no, course_name, credit, teacher, capacity, selected课程信息与选课容量select_courseid, student_no, course_no, select_time学生选课关系scoreid, student_no, course_no, score, semester成绩记录student_no 和 course_no 用业务编号而不是自增主键因为界面查询太快开发时不用 JOIN 也能辨识。course 表里的 capacity 和 selected 是专门为选课准备的capacity 表示课程容量selected 表示当前已选人数。这个设计会带来并发问题第四章用事务解决。2.3 建表 SQL 脚本的关键写法下面是能在 MySQL 8.0 直接执行的建表脚本重点看字符集、引擎和唯一索引CREATE DATABASE IF NOT EXISTS stu_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE stu_system; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL, password VARCHAR(128) NOT NULL, role VARCHAR(16) NOT NULL DEFAULT student, UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student ( student_no VARCHAR(32) PRIMARY KEY, student_name VARCHAR(64) NOT NULL, gender VARCHAR(8), major VARCHAR(64), class_name VARCHAR(64) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;user 表密码字段留到 128 位是为了给哈希后的密文留空间而不是直接存明文。所有表统一 InnoDB事务和行级锁都依赖这个引擎。唯一键 uk_username 确保用户名不重复业务层即使并发注册数据库也会拦下重复账号。下面的语句把选课表和成绩表的关联键建出来并加唯一约束CREATE TABLE course ( course_no VARCHAR(32) PRIMARY KEY, course_name VARCHAR(128) NOT NULL, credit DECIMAL(3,1), teacher VARCHAR(32), capacity INT DEFAULT 50, selected INT DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE select_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(32), course_no VARCHAR(32), select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_stu_course (student_no, course_no), CONSTRAINT fk_sc_student FOREIGN KEY (student_no) REFERENCES student(student_no) ON DELETE CASCADE, CONSTRAINT fk_sc_course FOREIGN KEY (course_no) REFERENCES course(course_no) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(32), course_no VARCHAR(32), score DECIMAL(5,2), semester VARCHAR(32), UNIQUE KEY uk_stu_course_semester (student_no, course_no, semester) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明DECIMAL(5,2) 用于成绩最大 999.99足够课程设计DATETIME DEFAULT CURRENT_TIMESTAMP 让插入时自动带时间少写一行代码唯一索引 uk_stu_course 在数据库层面阻止重复选课uk_stu_course_semester 让同一学期只能录一次成绩。外键是双刃剑导入测试数据时顺序不对会报 1452 错误很多课设源码因此不敢加外键我的建议是保留外键然后用SET FOREIGN_KEY_CHECKS0临时关闭。提示导入 SQL 文件时如果遇到语法错误优先检查文件编码Windows 下用记事本另存为 UTF-8 再执行。3. 登录注册与会话保持Swing界面背后的JDBC连接逻辑3.1 登录按钮的监听器里最容易出现的问题打开登录界面源码最常见写法是在 JButton 的 ActionListener 里直接 new Connection、拼字符串 SQL再 ResultSet 判断。代码能跑但有两个隐患一是 SQL 注入用户输入 or 11时 WHERE 条件恒真直接绕过密码二是连接未关闭连续登录几次后 MySQL 报 Too many connections。这个问题也常出现在 java 面试题里。这个项目既然给了 mysql-connector-j-8.3.0.jar说明驱动是 8.x连接 URL 至少写成下面这样Class.forName(com.mysql.cj.jdbc.Driver); Connection conn DriverManager.getConnection( jdbc:mysql://localhost:3306/stu_system?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf8, root, 123456); String sql SELECT id, role FROM user WHERE username ? AND password ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { // 登录成功写会话 } } }class 名是 com.mysql.cj.jdbc.Driver旧版的 com.mysql.jdbc.Driver 在 8.0 驱动里已经过时。allowPublicKeyRetrievaltrue 解决 MySQL 8 默认 caching_sha2_password 报 Public Key Retrieval is not allowed 的问题serverTimezone 指定时区避免日期字段差 8 小时characterEncodingutf8 负责中文不乱码。PreparedStatement 用 ? 占位把输入当作参数而不是 SQL 片段这是对 SQL 注入的第一层防御。3.2 密码不能明文比对但别在课设里引入太重的东西源码里的 user 表如果直接存明文答辩时大概率会被问“密码安全性怎么设计”。最简单有效的做法是加盐的 SHA-256 或 MD5虽然不够现代但作为课设表达“我知道密码要哈希”就够了。我一般在项目里加一个 PasswordUtilsprivate static String toHash(String raw, byte[] salt) { try { MessageDigest md MessageDigest.getInstance(SHA-256); md.update(salt); byte[] digest md.digest(raw.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }盐值不要用用户名注册时生成一个随机串存库时把盐和哈希拼接比如随机盐$哈希。登录时先按 username 取盐再重新计算比对。这里的 String.format(%02x) 是 Java 8 兼容写法如果项目跑在旧 JDK 上也能通过。另一个容易忽略的点是 Swing 登录框用 JPasswordField 时应该用new String(passwordField.getPassword())或者直接对 char[] 做哈希不要用 getText() 返回的 String后者的敏感信息会留在堆里等待 GCSwing 的 char[] 可以被手动清空。3.3 登录状态放哪里用 UserContext 代替窗口传参Swing 应用切换界面是 setVisible(true) 而不是浏览器跳转如果每个窗口构造器都传 User 对象代码会很散。常见做法是定义一个静态的 UserContextpublic class UserContext { private static User current; public static void login(User user) { current user; } public static User getUser() { return current; } public static void logout() { current null; } }登录成功后在事件线程里 UserContext.login(user)主界面读取即可。注意 Swing 的 UI 操作必须在 Event Dispatch Thread 上执行不要在 JDBC 回调里直接操作组件否则界面可能出现半渲染状态。可以简单用 SwingUtilities.invokeLater(Runnable) 包一层。这里没有用线程池或缓存课设阶段的复杂度刚刚好也方便答辩时讲清楚数据流。3.4 连接管理器从 DriverManager 到最小连接池登录代码每调用一次都 new Connection严格来说不是好习惯。如果后续有选课、查询等大量操作几十个并发请求就会压垮默认数据库连接数。对这个项目你可以选择引入 HikariCP但 Swing 原生 JDBC 的约束下我推荐做最小的连接管理方案优点缺点每次操作新建连接实现简单代码直观频繁建连开销大连接数不可控静态单例持有单个连接适合单窗口单用户长时间空闲会被 MySQL 断开最小连接池复用连接避免重复握手引入额外依赖脚本变大课程设计场景静态单例是最稳的配合 executeQuery 前后判断连接是否关闭。实际生产中还是交给连接池这个度要看你的验收标准是什么。4. 选课扣名额与成绩录入事务边界和唯一约束怎么落地4.1 先查再插入的常见写法在并发场景下会破功大多数学生选课系统的业务代码长这样先查 course 表 selected capacity再执行 INSERT最后 UPDATE selected 1。在单用户演示时完全正常但课设答辩现场如果有两个窗口同时操作或者老师在日志里看到重复选课记录就会被问到“并发怎么办”。问题在于 check 和 insert 是两个独立请求中间存在时间差两个事务都读到名额为 1然后都插入成功最终 selected 变成 2。解决思路不是把代码写长而是把数据权限交给事务和锁。4.2 用 SELECT FOR UPDATE 锁住课程行把选课操作包在一个事务里先锁住 course 行再判断剩余名额。下面这段是 JDBC 写法连接来自上一章说的连接管理器conn.setAutoCommit(false); try { PreparedStatement lockPs conn.prepareStatement( SELECT capacity, selected FROM course WHERE course_no ? FOR UPDATE); lockPs.setString(1, courseNo); ResultSet rs lockPs.executeQuery(); if (!rs.next()) { throw new RuntimeException(课程不存在); } int capacity rs.getInt(capacity); int selected rs.getInt(selected); if (selected capacity) { throw new RuntimeException(课程已满员); } PreparedStatement insertPs conn.prepareStatement( INSERT INTO select_course(student_no, course_no, select_time) VALUES (?, ?, NOW())); insertPs.setString(1, studentNo); insertPs.setString(2, courseNo); insertPs.executeUpdate(); PreparedStatement updatePs conn.prepareStatement( UPDATE course SET selected selected 1 WHERE course_no ?); updatePs.setString(1, courseNo); updatePs.executeUpdate(); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); }SELECT ... FOR UPDATE 是 InnoDB 的行级排他锁从这条语句开始其他事务对同一课程的读取和更新都要等待当前事务结束。这样新增选课记录和更新名额就成了一个不可分割的整体。需要注意事务里所有 SELECT 都要带 FOR UPDATE 才有效如果先普通 SELECT 再做 UPDATE等于没锁。JDBC 默认隔离级别是 REPEATABLE_READ选课场景下 READ_COMMITTED 更激进但这里为了课设好解释保持默认即可。4.3 唯一索引兜底遇到重复选课直接报 1062事务锁有一个薄弱点如果代码只锁 course 行而另一个事务直接绕过业务层插入 select_course还是会出现重复。因此建表时的 UNIQUE KEY uk_stu_course(student_no, course_no) 是最后一道防线。当数据库收到违反唯一约束的 INSERT 时会返回错误码 1062Java 端以 SQLIntegrityConstraintViolationException 抛出。MySQL 错误码常见触发场景处理建议1045用户名密码错检查 db.propertiesroot 是否授权1146Table doesnt exist先导入脚本再启动程序1062重复选课/注册业务层捕获 DuplicateKey 后给出提示1452外键关联不到主表确认 student、course 先有数据1366中文内容无法插入表和库连接字符集改为 utf8mb4演示时为了展示这个兜底可以把唯一约束写在建表脚本里然后打开两个选课窗口连续点选。正常情况是先到的事务成功第二个事务在 commit 前抛出异常并回滚。4.4 成绩录入的原子更新成绩录入相对简单但要注意按学期判断重复。如果业务允许同一学期重录就做 UPDATE 而不是 INSERT如果每学期一条记录就用 INSERT ... ON DUPLICATE KEY UPDATE。下面这段适合“补录分数”的场景String sql INSERT INTO score(student_no, course_no, score, semester) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE score VALUES(score); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentNo); ps.setString(2, courseNo); ps.setBigDecimal(3, score); ps.setString(4, semester); ps.executeUpdate(); }VALUES() 函数在 MySQL 8.0.20 已标记废弃改写为AS NEW score别名也可以。对学生管理系统来说这个写法比先 delete 再 insert 安全得多不会因为中间状态丢失历史记录。成绩表有了唯一索引后即便两个录入员同时对同一门课录分也只会保留一条。5. 十分钟跑通项目mysql-connector-j-8.3.0 连接参数与改造技巧5.1 依赖加载和数据库导入在 IDEA 里打开 StuSystem.iml 后先看 lib 目录下的 mysql-connector-j-8.3.0.jar 有没有被识别成依赖。File - Project Structure - Libraries 里添加 jar然后确认src被标记为 Sourcesresources被标记为 Resources。数据库先建库再导数据命令行执行mysql -u root -p stu_system.sql如果文件过大或编码不一致可以加--default-character-setutf8mb4。之后修改 DBUtil 里的连接串核心参数如下jdbc:mysql://localhost:3306/stu_system?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf85.2 常见报错逐条排除ClassNotFoundException驱动 jar 没进 classpath刷新项目或手动添加依赖。Public Key Retrieval is not allowed连接 URL 少了 allowPublicKeyRetrievaltrue。Unknown database没执行建库脚本。Bad connectionMySQL 服务没启动或端口被占用先试telnet 127.0.0.1 3306。如果本地 MySQL 是 5.7而项目自带的是 8.3.0 驱动一般也能连但建议驱动主版本和数据库主版本不要差太大否则可能出现鉴权插件不兼容。最省事的验证方式是在代码里单独加一个 TestConnection 入口执行SELECT 1mysql -u root -p -e SELECT 1 stu_system能返回 1 就说明连接串、账号权限和库全部正常之后再去启动 Swing 界面。5.3 改造成你自己的课设时优先改这三个点第一个点是注册时生成随机盐并保存答辩时可以现场演示“连续注册两个相同用户第二个被数据库唯一索引拦下”。第二个点是选课事务里加入SELECT ... FOR UPDATE并在日志里打印耗时让老师看到你有意识处理并发。第三个点是修改密码功能项目截图里已经有修改密码界面建议把它从“输入旧密码直接改”改成“先校验旧密码再更新哈希后的新密码”这个流程在 java 面试八股文里也是常考点。资源文件尽量用getResource(/images/login_bg.jpg)这种方式加载而不是绝对路径这样打成 jar 后图片不会丢。打包时用mvn package或手动jar cfe StuSystem.jar 启动类 -C out .然后把lib/mysql-connector-j-8.3.0.jar复制到同一目录用java -Djava.ext.dirslib -jar StuSystem.jar运行或者直接在 IDEA 里运行包含 main 方法的入口类。整个流程跑通后你手里留下的就是一份真正能演示、能讲清边界条件的 Java Swing 课程设计。本文还有配套的精品资源点击获取