ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java Swing + MySQL 学生选课系统:JDBC 增删改查与避坑实践

2026/10/8 12:57:50 拓冰建站 浏览量
Java Swing + MySQL 学生选课系统:JDBC 增删改查与避坑实践 简介这份基于Java Swing与MySQL的学生选课管理系统是一套面向Java初、中级学习者及课程设计人群的实战资源适用于完成学生信息、课程编排、选课记录管理及数据库课程设计等场景。系统覆盖系统管理、数据管理、数据查询三大模块系统管理包含密码设置、用户登录退出数据管理分为学生管理、课程管理和选课管理均提供增加、删除、修改功能数据查询支持对学生、课程、选课信息的按需检索整体结构清晰便于二次开发与功能扩展。资源包共3个文件整体大小仅3.34MB内含可直接导入IDEA的Java源码工程、MySQL数据库脚本以及配套课程设计实训报告docx可帮助读者在Eclipse或IDEA中结合JDK8与MySQL5.7以上环境快速运行项目。已有268人学习/下载适合作为课程设计提交素材、实训参考或自学SwingMySQL整合开发的完整示例。1. 学生选课管理系统Java Swing MySQL 课设的三件套每年课设季「学生选课管理系统」在 Java 课程设计里出现的频率几乎稳居前三。用 Java Swing 搭桌面界面、MySQL 存数据、JDBC 串访问是一个相当经典的组合功能固定覆盖登录、三大数据管理和查询难度不高但每个环节都能卡住一批人。这套资源把完整源码、studb.sql 数据库脚本和课程设计报告打包在一起跑通它等于把 Java 面向对象、JDBC、MySQL 建表、Swing 事件监听完整走了一遍。适合刚学完 Java 基础准备交课设的在校生也适合想系统看一遍 CRUD 完整流程的入门开发者。2. 功能与数据拆解三大模块的流转和 studb.sql 的表结构课设系统的功能往往不复杂但边界要清晰。这套学生选课管理系统把功能切成了三块系统管理、数据管理、数据查询。先说清楚这三块分别干什么再落到数据库设计上后面写代码的时候才不会东一榔头西一棒子。2.1 系统管理登录、密码设置与退出的状态流转系统管理模块主要管用户信息包含密码设置、用户登录、退出三个功能。登录是所有功能入口操作人员在登录窗口输入用户名和密码后台去 user 表校验匹配成功后进入主界面密码设置支持修改当前登录用户的密码退出就是关闭主界面、回到登录页或者直接结束进程。从状态流转来看这个模块的核心是「未登录 → 已登录 → 退出」三条路径。未登录状态下任何业务操作都不该触发所以主界面的业务面板要么在登录之后才初始化要么在加载时依赖当前登录用户的信息。实际课设里主界面通常在构造函数里接收一个 userId 参数这样所有跟用户相关的操作都能追溯来源。密码设置这里有个常见的处理方式因为课设不涉及加密要求很多实现直接UPDATE user SET password ? WHERE id ?。实操时至少应该做一次「旧密码校验」否则任何一个拿到登录态的人都能随便改密码。这个点在实训报告里可以作为「需求完善说明」写出来答辩时能加分。// UserDAO.java - 修改密码先校验旧密码再更新 public boolean changePassword(int userId, String oldPwd, String newPwd) { String checkSql SELECT id FROM user WHERE id ? AND password ?; String updateSql UPDATE user SET password ? WHERE id ?; try (Connection conn DBUtil.getConnection()) { // 第一步校验旧密码 PreparedStatement ps conn.prepareStatement(checkSql); ps.setInt(1, userId); ps.setString(2, oldPwd); ResultSet rs ps.executeQuery(); if (!rs.next()) { return false; // 旧密码不对直接返回 } // 第二步更新密码 ps conn.prepareStatement(updateSql); ps.setString(1, newPwd); ps.setInt(2, userId); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }这段代码用两步完成改密先查再改。注意校验和更新用的是同一个 Connection而不是各开一个连接这样能避免连接资源浪费。setInt 和 setString 的索引顺序对应 SQL 里问号的顺序这个一旦错位轻则参数类型异常重则改错用户的密码。2.2 数据管理学生、课程、选课的增删改查边界数据管理是工作量最大的模块拆成三块学生管理、课程管理、选课管理。学生管理负责维护学生基础信息功能是添加、删除、修改学生。添加的表单字段是学号、姓名、性别、年龄、院系学号是自然主键重复添加会触发主键冲突修改时按学号定位改姓名、性别、年龄、院系中的部分或全部字段删除时要小心——如果这个学生已经选了课直接删 student 表记录会被外键约束拦下来。课程管理维护课程信息字段是课程号、课程名、学分、教师。学分一般用小数教师用字符串。这一块的增删改和学生管理高度对称完全可以复用同一套 DAO 模板只改表名和字段映射。我见过一些课设代码把学生和课程的 DAO 合并成一个通用 BaseDAO虽然抽象了重复逻辑但答辩时被问到细节反而容易卡壳所以建议保持学生、课程、选课各一个 DAO 类的平铺结构直观、好解释。选课管理是业务最复杂的一块一次选课记录关联一个学生和一门课程插入前要确认学生存在、课程存在、且这个学生没有选过这门课。重复选课在界面层可以弹提示在数据库层要靠联合唯一索引兜底。选课的删除操作一般是退选只删 sc 表里的一条记录不影响 student 和 course 表这个边界要跟学生删除区分开。查询模块的边界同样要清楚查询学生信息是单表查询在 student 表上按学号或姓名过滤查询课程信息是课程表上的过滤查询选课信息则是跨表联查因为要同时展示学号、姓名、课程号、课程名、教师单查任何一张表都凑不齐这些列。三表联查的 SQL 是整个系统里最值钱的一条语句。2.3 数据库表结构与初始化数据设计studb.sql 是整套系统的地基包含建库语句、建表语句和初始化数据三部分。典型的表结构如下表名关键字段约束说明userid, username, passwordid 自增主键studentsno, sname, ssex, sage, sdeptsno 主键coursecno, cname, credit, teachercno 主键scid, sno, cnosno cno 联合唯一user 表只有自增 id 和用户名密码字段student 和 course 用业务编号做主键sc 表是选课关系表通过 sno、cno 两个外键关联另外两张表。联合唯一索引非常关键——没有它同一个学生选同一门课在数据库层面能插入两条界面上的判断逻辑一旦有遗漏数据就脏了。-- studb.sql 核心建表语句 CREATE DATABASE IF NOT EXISTS student_course_db DEFAULT CHARACTER SET utf8; USE student_course_db; CREATE TABLE student ( sno VARCHAR(20) PRIMARY KEY, sname VARCHAR(50) NOT NULL, ssex VARCHAR(10), sage INT, sdept VARCHAR(50) ); CREATE TABLE course ( cno VARCHAR(20) PRIMARY KEY, cname VARCHAR(50) NOT NULL, credit DECIMAL(3,1), teacher VARCHAR(50) ); CREATE TABLE sc ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL, cno VARCHAR(20) NOT NULL, UNIQUE KEY uk_sc_sno_cno (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno) );建库语句的DEFAULT CHARACTER SET utf8是中文数据不出乱码的前提。sc 表里UNIQUE KEY uk_sc_sno_cno (sno, cno)是防重复选课的数据库层保障两个 FOREIGN KEY 保证选课记录必须引用真实存在的学生和课程。初始化数据一般会预置一个管理员账号以及十来条学生、课程记录方便拿到工程就能直接测试增删改查。我拿到脚本后的第一件事是先看初始化数据的 id 范围。因为后面写增删改代码时手动构造的测试数据如果跟已有数据撞了一执行就主键冲突容易被误判成代码 bug。另一个要确认的是库名如果 JDBC URL 里写的库名和 script 里 CREATE DATABASE 的不一致连库直接报 unknown database这种问题排查起来最浪费时间。3. 部署与运行JDK 8、MySQL 5.7、IDEA 的配置顺序课设项目最怕的不是写代码而是环境起不来。这个系统的运行环境是 JDK 8、MySQL 5.7 及以上、Eclipse 或 IDEA。实际部署中我建议统一用 IntelliJ IDEA MySQL 5.7 的组合JDK 8 配 IDEA 的兼容性最稳。下面按顺序走一遍。3.1 环境清单与文件说明整套资源解压后是四个部分源码工程、数据库脚本 studb.sql、课程设计报告 docx、以及打包好的压缩包。源码工程可以直接用 IDEA 打开但打开之前先确认本机环境跟它对齐。组件版本要求作用JDK1.8编译运行 Java 代码MySQL5.7 及以上存储业务数据IDEA/Eclipse任意较新版本开发与运行mysql-connector-java.jar5.x / 8.xJDBC 驱动JDK 8 安装时要注意 PATH 和 JAVA_HOME 两个环境变量。MySQL 建议直接装 5.7 系列这是 5.7 版本的稳定性已经验证得很充分。装的时候 root 密码务必记住后面 JDBC URL 里要写。驱动 jar 是容易被忽略的点。IDEA 的正确做法是把 mysql-connector-java.jar 放进工程的 lib 目录然后右键 Add as Library。加载失败最常见的报错是ClassNotFoundException: com.mysql.jdbc.Driver这个会在避坑章节具体讲。3.2 导入 studb.sql 到 MySQL数据库脚本导入方式有两种命令行和图形工具。命令行最通用图形工具最直观。这里给命令行的方式因为在实训报告里写步骤时命令行更容易说清楚。mysql -uroot -p Enter password: ****** SOURCE D:/student-course/studb.sql;第一条命令用 root 登录 MySQL第二条命令执行数据库脚本。SOURCE 后面的路径改成你本机实际存放 studb.sql 的路径Windows 下路径分隔符用斜杠或双反斜杠都可以。执行完成后在 MySQL 命令行里验证一下SHOW DATABASES; USE student_course_db; SHOW TABLES;看到 student、course、sc、user 四张表就说明导入成功。如果 SHOW DATABASES 里没有这个库多半是脚本里建库语句没执行成功或者当前用户没有建库权限。前者打开脚本文件直接看第一行就能确认后者在课设机器上不常见。3.3 IDEA 导入工程与 JDBC 依赖配置用 IDEA 导入工程时选择 Open 定位到源码目录IDEA 会自动识别项目结构。如果打开后报错说没有 JDK进入 File → Project Structure → SDK把 Project SDK 指到本机 JDK 8 安装目录。JDBC 连接信息一般写在 DBUtil 工具类里结构是这样public class DBUtil { // 驱动、URL、账号、密码四个常量集中管理 private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/student_course_db?useSSLfalsecharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的数据库密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(驱动加载失败检查 mysql-connector-java.jar); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码有四个关键点。第一驱动类名要跟 jar 版本匹配MySQL 5.7 写 com.mysql.jdbc.Driver 更稳MySQL 8 可以写 com.mysql.cj.jdbc.Driver。第二URL 里的 useSSLfalse 用来避免 SSL 握手警告。第三characterEncodingutf8 解决中文乱码。第四PASSWORD 常量是唯一需要修改的地方改成你自己的 MySQL root 密码。配置好后直接找到带 main 方法的入口类通常是 LoginFrame 或者 MainFrame右键 Run。看到登录窗口弹出来部署就算成功。4. 代码实现登录校验、增删改查与选课核心逻辑Swing 课设的代码量不大但结构上值得捋一遍。通常按界面类、DAO 类、实体类三层组织实体类对应数据库表DAO 类封装 JDBC 操作界面类负责 Swing 组件和事件监听。下面挑三个核心功能拆开讲。4.1 登录模块从 Swing 界面到 JDBC 校验登录窗口是最典型的 Swing 布局两个 JLabel 提示、两个 JTextField、一个 JButton。点击登录按钮后ActionListener 里调用 DAO 查询 user 表。// UserDAO.java - 登录校验 public boolean checkLogin(String username, String password) { // 用 PreparedStatement 拼接查询防止 SQL 注入 String sql SELECT id FROM user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { // 查到记录说明用户名密码正确 return rs.next(); } } catch (SQLException e) { e.printStackTrace(); return false; } }try-with-resources 能自动关闭 Connection、Statement 和 ResultSet。PreparedStatement 的两个 setString 分别对应 SQL 里的两个问号顺序不能乱——第一个问号是用户名第二个是密码。界面上登录按钮的逻辑是// LoginFrame.java - 登录按钮事件 loginBtn.addActionListener(e - { String username usernameField.getText().trim(); String password passwordField.getText().trim(); if (.equals(username) || .equals(password)) { JOptionPane.showMessageDialog(null, 用户名和密码不能为空); return; } if (userDAO.checkLogin(username, password)) { new MainFrame().setVisible(true); // 登录成功打开主界面 dispose(); // 关闭当前登录窗口 } else { JOptionPane.showMessageDialog(null, 用户名或密码错误); } });这里有个细节getText() 之后一定要 trim()用户手滑敲了个空格不 trim 就会出现“我密码明明对的怎么登不进去”的情况答辩现场很尴尬。4.2 学生管理PreparedStatement 与结果集封装学生管理的增删改查围绕 student 表展开。添加学生的 DAO 方法如下// StudentDAO.java - 添加学生 public boolean insertStudent(Student stu) { String sql INSERT INTO student(sno, sname, ssex, sage, sdept) VALUES(?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stu.getSno()); ps.setString(2, stu.getSname()); ps.setString(3, stu.getSsex()); ps.setInt(4, stu.getSage()); ps.setString(5, stu.getSdept()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }executeUpdate() 返回受影响行数大于 0 说明插入成功。注意 sage 用的是 setInt性别存的是字符串这两个类型在实体类里要严格跟数据库字段类型对齐否则运行时 setString 给 int 字段会报参数类型不匹配。查询列表的方法需要把 ResultSet 逐行转换成实体对象// StudentDAO.java - 按姓名模糊查询 public ListStudent selectByName(String keyword) { ListStudent list new ArrayList(); String sql SELECT * FROM student WHERE sname LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student stu new Student(); stu.setSno(rs.getString(sno)); stu.setSname(rs.getString(sname)); stu.setSsex(rs.getString(ssex)); stu.setSage(rs.getInt(sage)); stu.setSdept(rs.getString(sdept)); list.add(stu); } } } catch (SQLException e) { e.printStackTrace(); } return list; }LIKE 查询的占位符写法是% keyword %百分号必须在 setString 里拼不能直接写进 SQL 字符串再拼接参数否则占位符只剩一个空串模糊查询变成全表扫描。这个细节我在帮人看代码时至少纠正过五次。拿到 List 之后Swing 界面把数据灌进 JTable// StudentPanel.java - 刷新 JTable 数据 DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); // 先清空旧数据 for (Student stu : list) { model.addRow(new Object[]{ stu.getSno(), stu.getSname(), stu.getSsex(), stu.getSage(), stu.getSdept() }); }setRowCount(0) 是刷新表格最常用的套路不清空直接 addRow 会导致旧数据残留每次查询结果逐次累加表格越滚越长。删除和修改的 DAO 方法结构跟插入完全一样只是 SQL 换成 DELETE 和 UPDATE参数顺序要跟 SET 子句的字段顺序一致。4.3 选课管理外键、联合索引与跨表校验选课模块是整个系统里最需要想清楚的地方。一次选课操作除了往 sc 表插入记录还要确认三件事学生存在、课程存在、该生未选过该课。// CourseSelectDAO.java - 添加选课记录 public boolean selectCourse(String sno, String cno) { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); // 1. 校验该生是否已经选过这门课 String checkSql SELECT COUNT(*) FROM sc WHERE sno ? AND cno ?; ps conn.prepareStatement(checkSql); ps.setString(1, sno); ps.setString(2, cno); try (ResultSet rs ps.executeQuery()) { rs.next(); if (rs.getInt(1) 0) { return false; // 已选过返回失败 } } // 2. 未选过则插入 String insertSql INSERT INTO sc(sno, cno) VALUES(?,?); ps conn.prepareStatement(insertSql); ps.setString(1, sno); ps.setString(2, cno); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } finally { // 关闭连接 if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这个方法的时序很关键先查后插两步之间如果并发执行理论上会插入两条重复选课记录。课设单机环境基本遇不到并发但对于生产环境这种做法有竞态条件所以我在数据库里还会额外建联合唯一索引做兜底。ALTER TABLE sc ADD UNIQUE KEY uk_sc_sno_cno (sno, cno);有了这个约束就算界面层漏判数据库层也会用 Duplicate entry 报错拦住重复数据。做课设时把这个索引写进 studb.sql 建表语句并在实训报告里说明“界面校验数据库约束双层保障”是非常加分的点。选课列表展示一般用三表联查把学号、姓名、课程号、课程名、教师列在一张表格里。这条 SQL 在前面已经给过DAO 层返回的是一组 VO 对象而不是单表实体实体类里需要额外定义几个联合字段课设代码里通常叫 StudentCourseVO。5. 避坑记录Java Swing 选课系统的五个常见问题课设项目跑不通八成不是业务逻辑难而是环境或细节问题。这里整理五个我在这个系统上实际见过、也帮人排查过的问题每条按现象、原因、解决的顺序写。5.1 驱动加载失败ClassNotFoundException现象程序一启动就报ClassNotFoundException: com.mysql.jdbc.Driver登录窗口都弹不出来。原因mysql-connector-java.jar 没有被工程引用。常见两种场景一是 IDEA 里 jar 放在了 lib 目录但没有右键 Add as Library二是工程从压缩包解压后 lib 目录路径变了导致引用失效。解决在 IDEA 里打开 Project Structure → Libraries把 lib 目录加进去或者右键 lib 目录 → Add as Library。如果是 EclipseBuild Path → Configure Build Path → Add External JARs把驱动的实际路径选进去。5.2 中文乱码查询结果全是问号现象JTable 里显示的学生姓名、院系全是 ????但数据库里直查是正常的。原因JDBC 连接字符串里没有指定 characterEncodingutf8或者 MySQL 库/表本身是 latin1 字符集。MySQL 5.7 默认字符集是 latin1创建库的时候没指定 utf8中文就存不进去。解决在 studb.sql 的建库语句后面加上DEFAULT CHARACTER SET utf8同时 JDBC URL 末尾加characterEncodingutf8。两个地方都改改完把库删掉重新导入脚本因为已经存进去的乱码数据不会自动恢复。5.3 重复选课同一学生选了同一门课两次现象选课管理里同一个学号能对同一门课程添加多条记录列表里出现重复行。原因界面层的校验逻辑漏了或者 checkSql 写错了比如WHERE sno ? AND cno ?写成了OR。更根本的原因是表结构上没有联合唯一索引数据库层没有兜底。解决先修代码把 AND 写对再修表结构给 sc 表加联合唯一索引双保险。加索引之后如果历史数据里已经有重复记录要先清理重复行再执行 ALTER TABLE否则会因为重复数据导致索引创建失败。5.4 删除学生被外键拦截现象删除一条学生记录时报Cannot delete or update a parent row: a foreign key constraint fails。原因这个学生在 sc 表里有选课记录student 是父表sc 是子表有外键依赖时父表记录不能直接删。解决先删 sc 表的选课记录再删 student。代码里删除学生的 DAO 方法要先执行DELETE FROM sc WHERE sno ?再执行DELETE FROM student WHERE sno ?。两步建议放在同一个事务里否则第一步成功第二步失败会出现孤儿选课记录。5.5 IDEA 控制台中文乱码现象代码里输出的中文在 IDEA 控制台全是乱码但界面窗口显示正常。原因IDEA 控制台的默认编码和工程编码不一致。工程是 UTF-8IDEA 控制台用了 GBK或者反过来。解决Help → Edit Custom VM Options加一行-Dfile.encodingUTF-8重启 IDEA。如果还不行在 Run Configuration 里把 VM options 也加上同样的参数。这是 IDEA 本身的老毛病和项目无关但每年都能看到有人在这上面耗半天。这五个问题里5.2 和 5.5 是环境概率最高的5.3 和 5.4 是代码逻辑上最能体现一个人有没有认真做的。答辩时被问到“为什么这么设计”能说出“联合唯一索引兜底重复选课”“删除学生先删选课记录再删学生”通过率会高不少。6. 进阶与验证把课设系统推向可扩展的工程形态系统能跑通只是第一步答辩和验收阶段真正拉开差距的是验证方式和扩展空间。我建议你至少做一轮完整的回归验证登录、添加学生、添加课程、给学生选课、查询选课信息、修改学生信息、删除该生选课、再删除学生。每一步都记录预期结果和实际结果这一张验证表写进实训报告比任何功能描述都有说服力。扩展方向上有两个优先级最高。一是登录密码的加密存储把 user 表 password 字段改成 MD5 密文登录校验时对输入做同样加密再比对这条能直接回应“系统安全性”的提问。二是选课时间记录在 sc 表加一个 select_time 字段把选课操作的时间戳写入选课记录就有了完整审计链路。// 计算某个学生的总学分验证选课数据的完备性 String sql SELECT SUM(c.credit) FROM sc JOIN course c ON sc.cno c.cno WHERE sc.sno ?;这条 SQL 把选课表和课程表关联按学号聚合学分总和。我在验证系统时习惯拿它做数据完整性检查先手动选两门课再执行这条语句学分加起来对得上说明选课链路没有丢数据。从那以后我每次拿到一个课设系统都会先做一轮完整的增删改查回归再检查数据库里的外键约束和唯一索引是否到位——这两件事做完了系统的底子才算真正稳了。希望这份笔记能帮你把这个选课系统顺利跑通也祝你答辩顺利。本文还有配套的精品资源点击获取