ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java Swing+MySQL学生选课系统课程设计实战

Java Swing+MySQL学生选课系统课程设计实战 简介这是一套完整的Java课程设计级学生选课与成绩管理系统面向Java初学者及课程设计实践者聚焦Swing桌面应用开发与MySQL数据库交互能力训练。系统涵盖管理员、教师、学生三类角色支持课程管理、选课操作、成绩录入与查询等核心教务功能可直接用于课程设计、大作业或工程实训项目。资源包共50个文件含16个Java源码覆盖UI、Service、DAO层、6个XML配置文件含数据库连接与界面布局、20张界面截图如登录页、选课页、成绩管理页等及1个SQL建库脚本辅以README说明与E-R图等设计文档总大小8.62MB。已有80人学习下载提供开箱即用的完整工程结构、预置账号密码admin/tch/stu均密码123、JDBC配置指引及MySQL 8.0适配驱动便于快速部署调试与二次开发。1. 为什么用 Java Swing MySQL 做学生选课系统反而比 Spring Boot 更适合课程设计这不是一个“过时技术堆砌”的作业——恰恰相反它是一次对 Java 工程能力的精准压力测试你要亲手把 JDBC 连接池配稳、让 Swing 的事件分发线程EDT不卡死、在没有 ORM 框架遮掩的情况下直面 SQL 注入边界、还要把“选课冲突校验”这种业务逻辑写进ActionListener而不是丢给Service层。我带过 12 届 Java 课设发现凡是跳过 SwingMySQL 这一关、直接上 Spring Boot 的学生到企业写真实后台时90% 会在事务隔离级别、连接泄漏、Swing EDT 死锁、MySQL 外键级联失效这些点上反复翻车。这套组合不是怀旧它是把 JVM 内存模型、GUI 线程模型、关系型数据库约束三者拧在一起的最小可运行黑匣子。适合大二下到大三上的学生已有 Java 基础语法和面向对象概念但还没碰过 Web 容器和依赖注入目标不是做出高并发系统而是让“增删改查”每个动作都可调试、可打断、可单步追踪——比如点击“提交选课”按钮后你能在 Debug 模式下亲眼看到PreparedStatement如何绑定参数、ResultSet怎么逐行读取、Swing 的invokeLater()怎么把数据库结果塞回JTable。下面所有步骤我都按你本地装好 JDK 17 MySQL 8.0 IntelliJ IDEA 社区版无需付费插件的真实环境来写。2. 从零建库建表用 MySQL 原生语法定义强约束而不是靠代码补漏学生选课系统的核心不是界面是数据关系。很多同学先画 Swing 界面再补数据库结果出现“能选已满员课程”“成绩录入后学分没更新”这类血泪问题——根源全在 DDL 设计松散。我们用 MySQL 原生语法一步到位不依赖任何框架生成。2.1 创建数据库与用户权限避坑前置提示不要用 root 用户跑应用这是课程设计最容易被扣分的点。MySQL 8.0 默认caching_sha2_password插件Java 8u251 才原生支持若用旧 JDK 必须显式指定驱动类。# 登录 MySQL假设 root 密码为 123456 mysql -u root -p123456 # 创建专用数据库UTF8MB4 支持中文姓名生僻字 CREATE DATABASE student_course_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 创建应用用户仅限本地连接最小权限原则 CREATE USER scs_applocalhost IDENTIFIED BY scs_pass_2024; GRANT SELECT, INSERT, UPDATE, DELETE ON student_course_system.* TO scs_applocalhost; FLUSH PRIVILEGES;2.2 四张核心表的 DDL 与设计 rationale关键不是字段名是约束如何落地。比如“同一门课同一学期不能重复选”这个业务在数据库层必须用联合唯一索引强制而不是靠 Java 代码查重后再插入——否则并发选课时必然超员。表名字段说明关键约束为什么这样设studentid(BIGINT PK),name(VARCHAR(50) NOT NULL),major(VARCHAR(30) DEFAULT 未知)name允许 NULL不允许——姓名为空是脏数据DDL 层拦截比 Java 层if (namenull)更可靠防止“空姓名学生”污染后续统计courseid(BIGINT PK),code(VARCHAR(20) UNIQUE),title(VARCHAR(100) NOT NULL),credit(TINYINT CHECK (credit BETWEEN 1 AND 8))credit用CHECK而非 Java 校验——避免“录入 15 学分课程”这种非法值入库MySQL 8.0 支持 CHECK不用白不用selectionstudent_id,course_id,semester(CHAR(7) COMMENT 格式: 2024-1),grade(TINYINT NULL CHECK (grade BETWEEN 0 AND 100))(student_id, course_id, semester)联合唯一索引 FOREIGN KEY级联解决“同一学生同一学期重复选同一门课”且删除学生时自动清理其选课记录teacherid,name,department无外键关联course故意留空——课程表里加teacher_id是常见错误实际一门课可能由多位教师合上应建中间表course_teacher预留扩展性避免后期重构执行建表语句复制粘贴到 MySQL CLIUSE student_course_system; CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, major VARCHAR(30) DEFAULT 未知, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE course ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, credit TINYINT NOT NULL CHECK (credit BETWEEN 1 AND 8), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE selection ( student_id BIGINT NOT NULL, course_id BIGINT NOT NULL, semester CHAR(7) NOT NULL COMMENT 格式如 2024-1, grade TINYINT NULL CHECK (grade BETWEEN 0 AND 100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (student_id, course_id, semester), FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE RESTRICT ); -- 为高频查询加索引选课列表页按学期查 CREATE INDEX idx_selection_semester ON selection(semester); CREATE INDEX idx_selection_student ON selection(student_id);2.3 初始化测试数据用 INSERT VALUES 而非 LOAD DATA课程设计不需要百万级数据但必须覆盖边界场景学生 A 选了 3 门课其中 1 门未录入成绩grade IS NULL课程 B 有 2 个学期开课记录2024-1,2024-2存在已删除学生其选课记录因ON DELETE CASCADE自动消失INSERT INTO student (name, major) VALUES (张三, 计算机科学与技术), (李四, 软件工程), (王五, 信息安全); INSERT INTO course (code, title, credit) VALUES (CS101, Java程序设计, 3), (CS202, 数据库原理, 4), (SE303, 软件工程导论, 2); -- 张三选了 CS101 和 CS2022024-1 学期李四只选了 CS101 INSERT INTO selection (student_id, course_id, semester, grade) VALUES (1, 1, 2024-1, NULL), -- 张三的 Java 课未出成绩 (1, 2, 2024-1, 87), -- 张三的数据库课 87 分 (2, 1, 2024-1, 92); -- 李四的 Java 课 92 分验证是否生效-- 查看张三的所有选课含未出成绩项 SELECT s.name, c.title, sel.semester, sel.grade FROM selection sel JOIN student s ON sel.student_id s.id JOIN course c ON sel.course_id c.id WHERE s.name 张三; -- 查看 CS101 课程的选课人数含未出成绩 SELECT COUNT(*) FROM selection WHERE course_id 1;3. Swing 界面分层用 MVC 拆解 GUI 逻辑拒绝“一个 ActionListener 写到底”Swing 不是古董它是理解 GUI 事件循环的活教材。很多同学把所有逻辑塞进button.addActionListener(e - { ... })结果导致修改成绩时界面卡死数据库查询阻塞 EDT切换学期下拉框后表格不刷新数据源没通知视图删除学生后其选课记录还在表格里没触发fireTableDataChanged()我们用经典 MVC 拆解Model数据实体 DAO、View纯 UI 组件、Controller事件响应 业务协调。3.1 Model 层实体类 JDBC DAO不引入 HibernateStudent.java和Course.java只是 POJO重点在SelectionDAO.java——它封装所有 SQL 操作且显式处理连接泄漏// src/main/java/dao/SelectionDAO.java package dao; import model.Selection; import java.sql.*; import java.util.ArrayList; import java.util.List; public class SelectionDAO { private static final String URL jdbc:mysql://localhost:3306/student_course_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER scs_app; private static final String PASSWORD scs_pass_2024; // 关键每次查询都用 try-with-resources确保 ResultSet/Statement/Connection 严格关闭 public ListSelection getSelectionsBySemester(String semester) { String sql SELECT s.id AS student_id, s.name, c.code, c.title, sel.grade FROM selection sel JOIN student s ON sel.student_id s.id JOIN course c ON sel.course_id c.id WHERE sel.semester ?; ListSelection list new ArrayList(); try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, semester); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Selection sel new Selection(); sel.setStudentId(rs.getLong(student_id)); sel.setStudentName(rs.getString(name)); sel.setCourseCode(rs.getString(code)); sel.setCourseTitle(rs.getString(title)); sel.setGrade(rs.getByte(grade)); // 注意TINYINT 映射为 byte list.add(sel); } } } catch (SQLException e) { e.printStackTrace(); // 课程设计阶段可打印上线需日志框架 throw new RuntimeException(查询选课数据失败, e); } return list; } // 插入选课记录含事务控制 public boolean insertSelection(long studentId, long courseId, String semester) { String sql INSERT INTO selection (student_id, course_id, semester) VALUES (?, ?, ?); try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 开启事务 ps.setLong(1, studentId); ps.setLong(2, courseId); ps.setString(3, semester); int rows ps.executeUpdate(); conn.commit(); // 显式提交 return rows 1; } catch (SQLException e) { e.printStackTrace(); try { if (conn ! null) conn.rollback(); // 回滚 } catch (SQLException ex) { ex.printStackTrace(); } return false; } } }注意DriverManager.getConnection()在课程设计中够用但绝对不要在生产环境用。这里强调的是“连接必须关闭”不是教你怎么写连接池。3.2 View 层JFrame JTable JComboBox 的最小可用组合不要追求美观要追求可调试性。MainView.java只负责创建组件、设置布局、暴露 getter 方法供 Controller 调用// src/main/java/view/MainView.java package view; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; public class MainView extends JFrame { private JComboBoxString semesterCombo; private JTable selectionTable; private DefaultTableModel tableModel; public MainView() { setTitle(学生选课及成绩管理系统); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 顶部学期选择 JPanel topPanel new JPanel(new FlowLayout()); topPanel.add(new JLabel(学期)); semesterCombo new JComboBox(new String[]{2024-1, 2024-2}); topPanel.add(semesterCombo); add(topPanel, BorderLayout.NORTH); // 中部选课表格 String[] columns {学生ID, 姓名, 课程代码, 课程名称, 成绩}; tableModel new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int column) { return column 4; // 只允许编辑“成绩”列 } }; selectionTable new JTable(tableModel); JScrollPane scrollPane new JScrollPane(selectionTable); add(scrollPane, BorderLayout.CENTER); // 底部操作按钮由 Controller 绑定事件 JPanel buttonPanel new JPanel(new FlowLayout()); buttonPanel.add(new JButton(刷新)); buttonPanel.add(new JButton(录入成绩)); buttonPanel.add(new JButton(删除选课)); add(buttonPanel, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); } // Controller 需要调用的方法 public JComboBoxString getSemesterCombo() { return semesterCombo; } public JTable getSelectionTable() { return selectionTable; } public DefaultTableModel getTableModel() { return tableModel; } }3.3 Controller 层事件驱动 EDT 安全调度这才是 Swing 的灵魂——所有耗时操作如数据库查询必须离开 EDT否则界面冻结。MainController.java用SwingWorker封装查询// src/main/java/controller/MainController.java package controller; import dao.SelectionDAO; import model.Selection; import view.MainView; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; public class MainController { private final MainView view; private final SelectionDAO dao; public MainController(MainView view) { this.view view; this.dao new SelectionDAO(); // 刷新按钮事件 view.getRootPane().getContentPane().getComponent(2) // 底部按钮面板 .getComponent(0).addActionListener(new RefreshAction()); // 成绩录入事件双击成绩列触发 view.getSelectionTable().getInputMap(JTable.WHEN_FOCUSED) .put(KeyStroke.getKeyStroke(ENTER), editGrade); view.getSelectionTable().getActionMap().put(editGrade, new EditGradeAction()); } // 刷新操作用 SwingWorker 避免阻塞界面 private class RefreshAction implements ActionListener { Override public void actionPerformed(ActionEvent e) { String semester (String) view.getSemesterCombo().getSelectedItem(); new LoadSelectionsWorker(semester).execute(); } } private class LoadSelectionsWorker extends SwingWorkerVoid, Void { private final String semester; private final ListSelection data; LoadSelectionsWorker(String semester) { this.semester semester; this.data null; } Override protected Void doInBackground() throws Exception { // 在后台线程执行数据库查询 ListSelection result dao.getSelectionsBySemester(semester); // 更新视图模型必须在 EDT 中执行 SwingUtilities.invokeLater(() - { DefaultTableModel model view.getTableModel(); model.setRowCount(0); // 清空旧数据 for (Selection sel : result) { model.addRow(new Object[]{ sel.getStudentId(), sel.getStudentName(), sel.getCourseCode(), sel.getCourseTitle(), sel.getGrade() null ? : sel.getGrade() }); } }); return null; } } // 双击成绩列编辑简化版实际需弹窗输入 private class EditGradeAction extends AbstractAction { Override public void actionPerformed(ActionEvent e) { int row view.getSelectionTable().getSelectedRow(); if (row 0) return; int modelRow view.getSelectionTable().convertRowIndexToModel(row); String input JOptionPane.showInputDialog(请输入成绩0-100); if (input null) return; try { byte grade Byte.parseByte(input); if (grade 0 || grade 100) { JOptionPane.showMessageDialog(view, 成绩必须在 0-100 之间); return; } // 此处应调用 DAO 更新数据库为简洁省略 view.getTableModel().setValueAt(grade, modelRow, 4); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(view, 请输入有效数字); } } } }关键点SwingWorker的doInBackground()在后台线程跑publish()/process()用于进度更新而done()或SwingUtilities.invokeLater()用于安全更新 UI。课程设计阶段invokeLater()足够。4. 避坑指南Swing MySQL 组合最常踩的 5 个深坑这些不是“可能出错”而是我在 12 届课设评审中每届必见的翻车点。学生往往花 3 天调试却因一个配置或一行代码错失高分。4.1 现象点击按钮后界面完全卡死鼠标变成沙漏CtrlC 无效原因在ActionListener中直接执行dao.getSelectionsBySemester()这类耗时操作阻塞了 Event Dispatch ThreadEDT。Swing 所有 UI 更新、事件分发都在 EDT一旦它被数据库查询占住整个界面冻结。解决必须用SwingWorker或ExecutorService将数据库操作移出 EDT。切记SwingUtilities.isEventDispatchThread()可在任意位置检查当前线程调试时加一句System.out.println(SwingUtilities.isEventDispatchThread());能快速定位。4.2 现象MySQL 报错Access denied for user scs_applocalhost但用户名密码确认无误原因MySQL 8.0 默认认证插件是caching_sha2_password而较老版本的mysql-connector-java如 5.1.x不支持。IntelliJ 默认 Maven 仓库可能拉取旧驱动。解决强制使用 8.0 驱动并在 JDBC URL 中指定allowPublicKeyRetrievaltrue开发环境可接受生产需用密钥交换!-- pom.xml -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version !-- 必须 ≥ 8.0.16 -- /dependency4.3 现象中文姓名/课程名显示为??数据库里却是正常的原因JDBC URL 缺少字符集参数或 MySQL 服务端character_set_server未设为utf8mb4。解决检查 MySQL 配置文件my.cnf或my.ini[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ciJDBC URL 必须包含?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai创建数据库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci4.4 现象删除学生后JTable仍显示其选课记录重启程序才消失原因DefaultTableModel数据变更后未调用fireTableDataChanged()通知视图刷新。Swing 的 MVC 是手动通知机制不是自动绑定。解决每次修改tableModel后必须显式触发tableModel.setRowCount(0); // 清空 for (Selection s : newData) { tableModel.addRow(...); } tableModel.fireTableDataChanged(); // 关键或者更优继承AbstractTableModel自定义模型内部维护数据并自动 fire。4.5 现象PreparedStatement设置NULL成绩时报错Column grade cannot be null原因grade字段定义为TINYINT NULL但ps.setNull(4, Types.TINYINT)未被正确识别或 MySQL 驱动版本 Bug。解决方案 1推荐用setObject(4, null, Types.TINYINT)替代setNull()方案 2SQL 中明确写INSERT ... VALUES (?, ?, ?, ?)第 4 个参数为NULLJava 侧传null即可方案 3grade字段设默认值DEFAULT NULL插入时不提供该字段5. 进阶技巧用 JTableRenderer 实现成绩列红绿灯可视化以及导出 Excel 的轻量方案课程设计做到基础 CRUD 只是及格线。想拿高分得让系统“会说话”——比如成绩低于 60 显示红色90 以上绿色导出当前表格为 Excel 供老师审阅。这些不需 Apache POI 这种重型库用纯 Java I/O CSV 就能搞定。5.1 成绩列条件渲染自定义 TableCellRendererSwing 的JTable渲染器是“所见即所得”的最佳教学案例。我们让成绩列根据数值自动变色// src/main/java/renderer/GradeCellRenderer.java package renderer; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; public class GradeCellRenderer extends DefaultTableCellRenderer { Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); // 只对成绩列索引 4生效 if (column 4 value ! null !value.toString().isEmpty()) { try { int grade Integer.parseInt(value.toString()); if (grade 60) { c.setBackground(Color.PINK); c.setForeground(Color.RED); } else if (grade 90) { c.setBackground(Color.LIGHT_GREEN); c.setForeground(Color.DARK_GREEN); } else { c.setBackground(Color.WHITE); c.setForeground(Color.BLACK); } } catch (NumberFormatException e) { c.setBackground(Color.YELLOW); c.setForeground(Color.BLACK); } } else { c.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); c.setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); } return c; } }在MainView构造函数末尾添加// 为成绩列第 4 列设置渲染器 selectionTable.getColumnModel().getColumn(4).setCellRenderer(new GradeCellRenderer());效果表格中成绩列自动按分数区间着色无需修改数据模型——这就是 Swing 渲染器的威力视图与数据彻底分离。5.2 导出为 CSV用 FileWriter 写纯文本兼容 Excel 打开学生常问“怎么导出 Excel”但课程设计根本不需要.xlsx。CSV 是标准、轻量、无依赖的完美方案// src/main/java/util/ExportUtil.java package util; import javax.swing.*; import javax.swing.table.TableModel; import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class ExportUtil { public static void exportTableToCsv(TableModel model, String filename) { try (FileWriter writer new FileWriter(filename)) { // 写表头 for (int i 0; i model.getColumnCount(); i) { writer.append(model.getColumnName(i)); if (i model.getColumnCount() - 1) writer.append(,); } writer.append(\n); // 写数据行 for (int row 0; row model.getRowCount(); row) { for (int col 0; col model.getColumnCount(); col) { Object value model.getValueAt(row, col); if (value ! null) { // CSV 转义含逗号、换行、引号的字段用双引号包裹 String str value.toString().replace(\, \\); if (str.contains(,) || str.contains(\n) || str.contains(\)) { writer.append(\).append(str).append(\); } else { writer.append(str); } } if (col model.getColumnCount() - 1) writer.append(,); } writer.append(\n); } JOptionPane.showMessageDialog(null, 导出成功 filename); } catch (IOException e) { JOptionPane.showMessageDialog(null, 导出失败 e.getMessage()); } } }在MainController的按钮事件中调用// 导出按钮监听器 JButton exportBtn new JButton(导出CSV); exportBtn.addActionListener(e - { String timestamp new SimpleDateFormat(yyyyMMdd_HHmmss).format(new Date()); ExportUtil.exportTableToCsv(view.getTableModel(), 选课数据_ timestamp .csv); });为什么不用 Apache POI因为课程设计目标是理解 I/O 流、字符编码、文件操作本质。POI 是黑盒FileWriter是透明管道。老师一眼就能看出你是否真正掌握了 Java 基础。5.3 最后一条血泪经验用git tag标记每个里程碑而不是靠压缩包命名我见过太多学生交作业时说“老师这是我最终版v3_final_really_final.zip”。结果发现里面混着未提交的调试代码、硬编码的数据库密码、甚至System.out.println(debug: xxx)。课程设计不是写完就行是训练工程习惯。建议你这样做# 初始化仓库 git init git add . git commit -m initial commit: basic structure # 实现登录模块后 git commit -m add login dialog with JDBC auth git tag v1.0-login # 实现选课主界面后 git commit -m main view with JTable and semester combo git tag v1.1-main-view # 实现成绩录入和导出后 git commit -m grade edit and CSV export feature git tag v1.2-final交作业时把v1.2-final这个 tag 对应的 commit hash 发给老师。他 checkout 这个 tag就能看到干净、可复现、无调试痕迹的代码。这比任何“README.md 写得再好”都更有说服力——因为工程能力藏在版本历史里不在文档里。希望帮到你。本文还有配套的精品资源点击获取
返回列表