news 2026/10/18 9:47:30

基于Java Swing和MySQL的学生选课管理系统全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Java Swing和MySQL的学生选课管理系统全解析

简介:一套基于Java Swing与MySQL的学生选课管理系统项目,适配IntelliJ IDEA,面向Java初学者及需要完成课程设计、毕业设计的学生。系统划分为系统管理、数据管理和数据查询三大部分:系统管理实现密码设置、用户登录和退出;数据管理支持学生、课程、选课三个模块的增加、删除、修改操作;数据查询可分别检索学生、课程及选课信息,功能覆盖完整,层次分明,适合作为Java图形界面开发与MySQL数据库应用的参考模板,也方便在此基础上扩展业务逻辑。资源包共3个文件,压缩后大小3.34MB,其中zip为可运行的项目源码,sql为数据库初始化脚本,docx为实训报告文档,拿到手即可对照使用。运行环境要求JDK8和MySQL5.7以上,兼容IDEA/Eclipse,能够帮助读者逐步理解Swing界面构建、JDBC连接以及增删改查等关键实现。已有268人学习下载,对于需要快速搭建课设项目或借鉴选课系统业务流程的开发者,是一份实用的操作素材。

1. 学生选课管理系统:为什么我推荐用 Java Swing 而不是 Web 技术栈

做课程设计或者毕设的时候,很多同学第一反应是"选课系统肯定要用 Web 做,用 JSP、Spring Boot 加个前端页面"——但如果你只是想交付一份能运行、能讲清楚、能通过答辩的完整方案,Java Swing 加 MySQL 反而是更稳的选择。这套基于 IDEA 环境的学生选课管理系统,把系统管理、数据管理、数据查询三条主线全部落地,代码量适中,逻辑清晰,还附带实训报告和数据库脚本,属于典型的"开箱即改"型项目。适合正在做 Java 课程设计、需要一份完整可运行代码加文档的同学,也适合想快速搭一个桌面端管理系统练手 Swing 事件监听和 JDBC 操作的开发者。接下来我会按"需求拆解 → 总体设计 → 模块实现 → 常见坑 → 验收答辩"这条完整链路,把这个方案讲透,让你拿到手之后不只是会跑,还能讲清楚每一处设计为什么这么做。

2. 需求分析与功能拆解:三大模块到底在管什么

2.1 从用户故事看系统边界

这套系统面对的角色很清晰:管理员(教务人员)是唯一操作者。系统不做多角色权限分化,而是把精力集中在"数据维护"和"数据查询"两件核心事上。我一般拿到这种需求,先不急着写代码,而是把用户故事列出来:管理员要能登录系统并且修改自己的密码;管理员要能维护学生档案,包括新增、删除、修改学生信息;管理员要能维护课程库,课程的增加、删除、修改随时可做;最关键的是选课关系管理,学生和课程的多对多关系要通过一张选课表解耦,不能直接往学生表里塞课程字段。

从这些故事可以倒推:系统必须有三张核心表——学生表、课程表、选课表。用户信息单独一张表,存管理员账号密码。密码设置功能不是改别人的密码,而是改当前登录管理员的密码,这点在实现时容易做歪,很多新手会把密码管理做成用户管理,其实这里只需要对当前会话用户生效即可。退出功能更简单,清掉当前登录状态,回到登录窗口。整个系统没有注册入口,所有账号由数据库预置,这符合课设场景的封闭性。

2.2 数据管理模块的功能粒度划分

数据管理是工作量最大的部分,也被拆成了三块:学生管理、课程管理、选课管理。每块下面都有三个动作:添加、删除、修改。这里有一个设计决策值得注意:学生管理和课程管理的增删改,直接操作对应表;但选课管理的增删改,本质上是在维护选课关系表。添加选课记录时,学生 ID 和课程 ID 必须是下拉选择而不是手输,因为手输容易出现外键不存在的脏数据。删除选课记录时,直接按选课 ID 删一行即可。修改选课时,通常的做法是先删除再重新添加,或者把学生 ID、课程 ID 两个外键字段做成可更新的下拉框。

但这里有个常见设计陷阱:课程管理里如果删除一门已经有学生选的课程,数据库会因为外键约束报错。所以代码里必须先做"该课程是否被引用"的检查,引用数大于 0 就提示"课程已存在选课记录,禁止删除"。同样,删除学生前也要检查该学生是否有选课记录。这个细节不做,答辩时老师随便一测就会翻车。

2.3 数据查询模块:按维度下钻而不是做一张大表

查询模块也分三个入口:查询学生信息、查询课程信息、查询选课信息。最简单的做法是每个查询窗口放一个 JTable,把所有数据塞进去。但更好的方案是带条件查询:学生信息按学号或姓名模糊匹配,课程信息按课程名模糊匹配,选课信息按学生 ID 关联查询出学生姓名加课程名称的组合结果。这三条查询分别对应三条 SQL,选课查询必须用 JOIN 把学生姓名和课程名称带出来,否则只显示两个 ID,老师会认为你没有理解关系型数据库的核心价值。

从需求到实现的映射关系很明确:系统管理对应登录验证和会话控制,数据管理对应增删改事务,数据查询对应 JOIN 查询和模糊匹配。功能点不多,但覆盖了 Java 课设几乎所有的必考知识点——GUI 事件处理、JDBC 预编译、ResultSet 遍历、外键约束处理、聚合查询。这套结构如果讲不清,后面代码写得再花哨也白搭。

3. 总体架构设计与数据库建模:三张核心表加一张用户表的拆分逻辑

3.1 分层结构:界面、业务、数据三层怎么划分

整个项目不是把 SQL 写在按钮事件里就完事,而是按三层思路组织。界面层是各个 JFrame 窗口,负责收集输入、展示结果、触发事件;业务层处理逻辑判断,比如删除前检查引用、登录时比对密码、修改时校验必填项;数据层用 JDBC 封装增删改查方法,统一管理 Connection、PreparedStatement、ResultSet 的关闭。项目里没有强行引入 Dao 接口和 Service 接口的抽象层,而是用类名区分职责,比如 StudentDao、CourseDao、SelectCourseDao 各管各的表,这在课设体量下是最务实的做法。

这样做的好处是答辩时能说清楚"高内聚低耦合"。比如把数据库连接参数抽到配置里,换数据库只需要改 URL;把每个表的增删改查单独放一个类,查询窗口要复用添加窗口的学生列表时,直接调用 StudentDao.getAllStudents() 即可。如果学号文本框和删除按钮的事件里各写一遍 SQL,那代码维护性就是负数,老师看到会追问重构思路。

3.2 数据库设计:字段、类型、约束一次到位

数据库脚本由 studb.sql 提供,核心是四张表。学生表字段包括学号(主键)、姓名、性别、年龄、班级、入学年份等;课程表字段包括课程号(主键)、课程名、学时、学分、任课教师;选课表是关系表,包含选课记录 ID 主键、学生学号外键、课程号外键,并加 UNIQUE 约束防止同一个人重复选同一门课;用户表保存管理员账号和密码,密码字段建议存储加密后的值,但课设项目里通常直接明文存储,只要能跑通即可,答辩时可以主动提一句"生产环境必须加盐加密"来展示你的安全意识。

选课表的联合唯一索引是这个设计的点睛之笔。如果不在数据库层面加约束,代码里要判断"这个学生是否已经选了这门课"只能先查一遍再插入,存在并发窗口期。加了 UNIQUE(学号, 课程号) 之后,重复插入直接抛异常,代码里捕获 DuplicateKeyException 提示"该学生已选此课程"即可。建议你在设计文档里把这条索引明确写出来,它体现了数据库层的防重设计,是加分项。

3.3 运行环境与版本匹配

项目基于 JDK 8 开发,数据库要求 MySQL 5.7 以上,IDE 用 IDEA。这里有一个隐蔽的坑:MySQL 8.0 的 JDBC 驱动和 5.x 的驱动在连接 URL 上不一样。MySQL 5.x 的连接串是 jdbc:mysql://localhost:3306/studb?useSSL=false,MySQL 8.x 需要加 serverTimezone=Asia/Shanghai 或者 useUnicode=true&characterEncoding=utf8,否则驱动会报时区错误。项目里如果用的是 mysql-connector-java 5.1.x 的 jar 包,连 MySQL 8.0 时会连上但中文乱码,连 MySQL 5.7 时建议把 useSSL 关掉避免警告刷屏。后面避坑章节我会细讲这个问题。

数据库命名建议统一用 studb,表名前缀不加模块名,直接 student、course、sc、user。字段命名全小写下划线,避免用 name、id 这种过于泛化的名字。建表语句里要写清楚外键约束和索引,这样导入 studb.sql 后有据可查。

4. 模块实现与关键代码:登录、增删改查、查询联动的落地写法

4.1 登录窗口与会话状态控制

登录窗口的逻辑集中在"验证账号密码"这一个动作上。点击登录按钮后,从文本框取出用户名和密码,调用 UserDao 的 login 方法,返回布尔值。成功后把当前用户信息保存在一个全局静态变量里,主窗口通过这个变量判断是否已登录,未登录直接关闭主窗口退出系统。密码修改功能读取当前用户,先验证旧密码,再更新为新密码。

public boolean login(String username, String password) { 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); ResultSet rs = ps.executeQuery(); return rs.next(); // 只要有一条记录就说明账号密码匹配 } catch (SQLException e) { e.printStackTrace(); return false; } }

这段代码用了 PreparedStatement 的占位符,而不是拼接字符串,可以防止 SQL 注入。这是课设答辩的高频考点——老师会问"为什么不直接用 Statement",你要能回答出参数化查询的两个好处:防注入、免去手动处理单引号转义。登录成功后,主窗体的 JMenuBar 按功能分组,系统管理菜单放密码设置和退出,数据管理菜单放学生、课程、选课三个入口,查询菜单放三个查询窗口。菜单的 enable 状态在登录前全部置灰,登录后才放开,这个小细节能体现会话控制意识。

4.2 学生管理的增删改:窗口复用和校验逻辑

学生管理的主界面是一个 JTable 加三个按钮。选表格哪一行,点击修改按钮时,弹出一个带学生信息的编辑窗口,窗口内文本框预填当前行的值。添加和修改共用一个窗口类,通过构造参数传入"是添加还是修改"的标记,以及被修改行的主键值,减少重复代码。

private void saveStudent() { String idText = txtId.getText().trim(); String nameText = txtName.getText().trim(); if (idText.isEmpty() || nameText.isEmpty()) { JOptionPane.showMessageDialog(this, "学号和姓名不能为空"); return; } Student stu = new Student(idText, nameText, txtGender.getText().trim(), Integer.parseInt(txtAge.getText().trim()), txtClass.getText().trim()); if (isEditMode) { studentDao.updateStudent(stu); // 修改模式按主键更新 } else { studentDao.insertStudent(stu); // 添加模式执行插入 } refreshTableData(); // 刷新主表格 dispose(); // 关闭当前编辑窗口 }

校验逻辑要前置,不要等数据库抛异常再弹错误。学号为空、姓名为空这些必填项,在按钮事件里先拦下来。年龄字段严格来说是数值类型,但 JTextField 拿到的永远是字符串,转 int 时如果用户输入非数字会抛 NumberFormatException,所以要么用 JSpinner,要么加 try-catch 给友好提示。我这边的做法是换行 Int 解析结果存进 int 变量时用 try-catch,失败则提示"年龄必须为数字"。

删除按钮的逻辑要分两步走。先查该学生是否存在于选课表,存在就提示先删除选课记录,不存在才执行 DELETE FROM student WHERE id = ?。这个检查建议在业务层做,而不是用数据库级联删除。因为课设场景下老师希望看到你自己处理关联数据的逻辑,直接 ON DELETE CASCADE 虽然省事,但掩盖了业务规则。

4.3 选课管理的核心:外键下拉框和防重复选课

选课管理是整个系统里最容易写乱的部分。添加选课时,学生 ID 和课程 ID 不能用文本框,必须用两个 JComboBox,数据分别来自 StudentDao.getAllStudents() 和 CourseDao.getAllCourses()。下拉框里显示"学号 + 姓名"或者"课程号 + 课程名",但存储时只取对应 ID。

public void addSelectCourse(String stuId, String courseId) { String sql = "INSERT INTO sc(stu_id, course_id, select_time) VALUES(?, ?, NOW())"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, stuId); ps.setString(2, courseId); ps.executeUpdate(); } catch (SQLIntegrityConstraintViolationException e) { // 学号或课程号不存在,或者选课关系重复 JOptionPane.showMessageDialog(null, "该学生已选此课程或学生/课程不存在"); } catch (SQLException e) { e.printStackTrace(); } }

这里的核心思路是:数据库已有的 UNIQUE 约束是最后一道防线,代码里不要先 SELECT 再 INSERT。直接尝试插入,唯一索引冲突时捕获特定异常提示即可。SQLIntegrityConstraintViolationException 是 JDBC 抛出的约束违反异常,MySQL 的重复键错误也会归到这里,用这个捕获比 catch(Exception) 更精准。选课时间字段用数据库的 NOW() 函数填充,而不是在 Java 里 new Date(),这样时间以数据库服务器为准,避免本机时间不准的问题。

4.4 三条查询链路的 SQL 写法

查询学生信息和课程信息比较简单,单表查询加模糊条件。选课查询必须 JOIN 两张表才能显示名字而不是 ID。建议把选课查询做成可以看到完整信息的视图:选课记录号、学号、学生姓名、课程号、课程名、学分、选课时间。SQL 大概是 SELECT sc.id, s.id, s.name, c.id, c.name, c.credit, sc.select_time FROM sc JOIN student s ON sc.stu_id = s.id JOIN course c ON sc.course_id = c.id。条件查询时在 WHERE 后面加 s.name LIKE ? 参数,参数值传 %关键字% 即可。

三个查询窗口建议共用同一个刷新方法,查询按钮触发时按条件查询,点击"显示全部"按钮时清空条件重新查。查询结果放到 JTable 时,要把 ResultSet 的数据先存进 DefaultTableModel,设置好列名数组,再把 JTable.setModel 换掉。每次查询前先清空旧 model,避免点击多次按钮后数据叠加。

private void refreshTable(String conditionSql, Object... params) { DefaultTableModel model = new DefaultTableModel(); model.setColumnIdentifiers(new String[]{"选课记录号", "学号", "姓名", "课程号", "课程名", "学分", "选课时间"}); List<Object[]> rows = selectCourseDao.queryWithCondition(conditionSql, params); for (Object[] row : rows) { model.addRow(row); } table.setModel(model); }

5. 避坑指南与常见问题排查:运行期最容易翻车的五个点

5.1 现象:IDEA 运行后报 java.sql.SQLException: The server time zone value

原因:MySQL 8.x 驱动要求连接 URL 必须指定时区,项目里如果用的是 JDK 8 自带的时间区或默认时区,会直接抛异常。解决:在 JDBC URL 后面加 serverTimezone=Asia/Shanghai 以及 useSSL=false。如果你的 mysql-connector-java 是 8.0 以上版本,还需要确认 mysql-connector-java 和 MySQL 服务器版本兼容。通常的做法是统一用 mysql-connector-java 8.0.28,它可以连 MySQL 5.7 和 8.0。

5.2 现象:中文乱码,学生姓名显示为问号

原因:数据库连接 URL 没指定字符集,或者建库时字符集不是 utf8mb4。解决:建库语句用 CREATE DATABASE studb DEFAULT CHARACTER SET utf8mb4;连接 URL 加 useUnicode=true&characterEncoding=utf8。如果已经导入 studb.sql 但乱码,用命令 ALTER DATABASE studb CHARACTER SET utf8mb4 再重跑程序。IDEA 方面,检查 Run Configuration 的 VM options 是否需要加 -Dfile.encoding=UTF-8,大多数情况不需要,但如果 JVM 默认编码是 GBK,就加一下。

5.3 现象:删除学生时报外键约束错误,但代码里明明没有级联

原因:选课表 sc 里还残留着该学生的选课记录。解决:删除学生前先执行删除选课记录的 SQL,或者按我前面说的,先查询 sc 表有没有引用,有就拦截提示。更稳妥的方式是把删除操作包在一个事务里:先 DELETE FROM sc WHERE stu_id = ?,再 DELETE FROM student WHERE id = ?,两个都成功才提交。这样可以避免"选课记录删了但学生没删掉"的中间状态。

5.4 现象:修改学生信息时,主键学号被改动导致关联数据断裂

原因:学生表的学号是主键,如果修改窗口允许编辑学号,更新后 sc 表里的 stu_id 还指向旧学号,数据就乱了。解决:修改模式下学号文本框设置为不可编辑(setEditable(false)),只允许改姓名、班级、年龄等非主键字段。如果非要支持改学号,必须同步更新 sc 表里的 stu_id,但课设里不建议做这个,容易引出一些列级联更新问题。我一般会在编辑窗口的初始化方法里做判断——isEditMode 为 true 时,把主键输入框 setEnabled(false)。

5.5 现象:登录成功后主窗口可以打开多个,点击按钮也没反应

原因:登录窗口没有 dispose,主窗口 new 了多次,多个窗口互相覆盖;或者线程没处理 Swing 事件队列,按钮监听器里执行了耗时 SQL 导致界面卡死。解决:登录成功执行 dispose 关闭登录窗口,主窗口只 new 一次。查询和刷新操作如果确实慢,用 SwingWorker 放到后台线程执行,防止 UI 冻结。不过课设数据量很小,通常不需要多线程,但要养成习惯:不要在 EDT(事件派发线程)里做长耗时操作。排查方法是看控制台有没有异常堆栈,很多同学报"没反应"其实是 SQL 报错但弹窗代码被吞了。

6. 验收清单与答辩准备:怎么证明这套系统是完整的

6.1 按功能点逐项验收,把测试记录写进实训报告

你拿到项目后不要急着打开就跑,先按功能清单逐项验证。我建议做一个验收表格:登录模块验证正确密码能进、错误密码有提示、密码修改后旧密码失效;学生管理验证添加成功后表格刷新、学号重复时有报错提示、删除被选课的学生被拦截;课程管理验证课程学分字段只能输数字、删除被引用课程被拦截;选课管理验证同一学生重复选同一课程被拦截、下拉框数据来自数据库而非写死;查询模块验证模糊查询能命中、选课查询结果带学生姓名和课程名。每过一条打一个勾,把测试结果截图放进实训报告,这就是最强的答辩材料。

6.2 答辩时必被追问的五个技术点,提前准备答案

第一个问题:PreparedStatement 和 Statement 的区别。标准答案是预编译、防 SQL 注入、处理特殊字符更方便。第二个问题:Swing 的事件监听机制,你要能说出 ActionListener 和 ItemListener 分别在什么场景用——按钮点击用前者,下拉框切换用后者。第三个问题:数据库表关系,你要画出 student、course、sc 的多对多关系图,并解释为什么需要中间表。第四个问题:为什么选择 Swing 而不是 JavaFX 或 Web,可以从课设复杂度、桌面管理系统的适用性、Swing 与 JDBC 结合简单三个角度回答。第五个问题:项目里哪里体现了面向对象思想——Dao 类封装数据库操作、Student/Course 实体类对应表结构、StudentManagerFrame 组合了 TableModel 和 StudentDao。

6.3 最后一轮自查:从代码规范到文档完整性

代码层面检查是否每个类都有注释说明职责、是否有 System.out.println 残留调试输出(有就删掉或改成日志)、连接资源是否在 finally 或 try-with-resources 中关闭。数据库层面检查 studb.sql 能否在干净的 MySQL 实例上重复导入不报错,建议你在另一台电脑上重新导入测试一遍,避免存在环境依赖。文档层面检查实训报告是否包含项目概述、需求分析、数据库设计、核心代码说明、测试截图、总结展望。完整资源里自带课程设计报告文档,你只需要把运行截图和个人信息替换进去即可。

从我的经验来看,答辩最大的风险不是代码写不出来,而是讲不清楚设计决策。比如"选课记录为什么用单独的表""为什么删除要做引用检查""数据库连接串为什么那么长",每一个点都是展示你理解深度的机会。我后来每次做类似项目,都强制自己走一遍验收清单流程——先把数据库导入干净库,再把每个功能点操作一遍并截图,最后把异常场景(密码错误、重复选课、删除被引用数据)也测一遍写进报告。这样最终交付的不只是一段能跑的代码,而是一套能说服人的完整方案。希望这套思路能帮到你,让课设从"能运行"变成"能讲透"。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 9:45:18

AI写Rust代码为何总卡壳?从所有权与生命周期看底层原因

1. 一次典型的卡壳现场&#xff1a;AI生成Rust代码编译不过的完整回放上个月我在重构一个内部工具的内存缓存模块&#xff0c;手头代码量大&#xff0c;就顺手把一段“按条件过滤集合并返回切片”的小函数丢给了AI代码助手去补全。原以为这不是什么复杂逻辑&#xff0c;结果AI连…

作者头像 李华
网站建设 2026/10/18 9:43:29

边缘端视觉架构实践:C#集成YOLO的产线缺陷检测与部署复盘

几个月前在一个现场做产线视觉改造&#xff0c;遇到一个让我印象特别深的问题&#xff1a;视觉检测工位离机房明明只有百来米&#xff0c;检测结果却时不时延迟&#xff0c;节拍一压紧就报警。折腾了几天之后我彻底想明白了&#xff0c;问题不在算法&#xff0c;而在架构——工…

作者头像 李华
网站建设 2026/10/18 9:42:24

虚拟演唱会视频制作全解析:从PJSK渲染到中日双语字幕压制

很多读者看到“【中日双字/PJSK】初音未来生日2026 虚拟演唱会”这样的视频标题时&#xff0c;第一反应是“去哪个平台找资源”&#xff0c;第二反应是“这个字幕组剪辑得真快”。但如果从技术角度去看&#xff0c;这一类内容并不是简单“录屏 打轴”就能完成的&#xff1a;它…

作者头像 李华
网站建设 2026/10/18 9:42:19

Markdown流式渲染:构建AI生成式UI的结构信标

1. 项目概述&#xff1a;这不是一次简单的格式转换&#xff0c;而是一场交互范式的迁移“从 Markdown 到生成式 UI&#xff1a;AI 应用中的流式渲染实践”——这个标题里藏着三个关键动作&#xff1a;“从…到…”是路径&#xff0c;“生成式 UI”是目标形态&#xff0c;“流式…

作者头像 李华
网站建设 2026/10/18 9:42:05

古旧却有效的IDA伪代码模块化拆分法:用编译器做依赖校验

刚接手一批老代码的逆向分析&#xff0c;项目里满满当当堆了几十个跟业务强耦合的模块。我用IDA把关键函数的伪代码导出之后&#xff0c;发现所有函数全挤在一个超大C文件里&#xff0c;头文件也长得离谱&#xff0c;总共上万行。我当时脑子里只有一句话&#xff1a;这东西要是…

作者头像 李华