news 2026/9/28 6:01:34

Java Swing+MySQL选课系统从零搭建实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Swing+MySQL选课系统从零搭建实战指南

简介:这是一套面向Java初学者的选课管理系统实战项目,专为课程设计、期末大作业打造,覆盖Swing图形界面开发与MySQL数据库应用全流程。资源包含15个文件(14个Java源码+1个SQL建库脚本),总大小仅19KB,轻量易部署;Java文件涵盖用户登录、课程管理、学生选课、成绩查询等核心模块,代码逐行注释清晰,SQL脚本可一键初始化数据库结构与示例数据,显著降低新手理解门槛。已有162人学习下载,适用于零基础学员快速掌握GUI开发逻辑、JDBC连接实践及MVC分层思想。项目功能完整、界面简洁、操作直观,既可直接运行演示,也便于二次扩展——如添加权限控制或导出报表,是夯实Java桌面应用开发能力的高性价比入门范例。

1. 为什么一个“Java+Swing+MySQL选课管理系统”现在还值得从零跑通?

这不是毕业设计交差用的玩具项目,而是一条被反复验证过的、能真正串起 Java 工程能力闭环的最小可行路径:它不依赖 Spring Boot 的自动装配黑盒,不靠前端框架遮掩 DOM 操作细节,也不用 Docker 隐藏数据库部署逻辑——所有环节都裸露在你眼皮底下。你得亲手写 JDBC 连接池参数、手调 Swing 布局嵌套层级、手动建表并处理外键约束冲突、甚至要面对 MySQL 8.0 默认 strict mode 下0000-00-00日期插入失败这种玄学报错。正因如此,它成了我带新人时必过的第一道关卡:能跑通这个系统的人,大概率不会在后续 Spring MVC 里被@Transactional失效搞懵,也不会在 MyBatis 动态 SQL 中漏掉AND 1=1导致全表扫描。它适合两类人:刚学完 Java 基础语法、连ArrayList和HashMap区别都还在查文档的新手;以及想快速验证自己是否真懂“三层架构”不是 PPT 术语的转岗者。注意,这里说的“小白适用”,是指代码结构清晰、无复杂反射/字节码操作、所有异常都有明确 try-catch 覆盖——但绝不等于“不用动脑”。你得理解为什么JTable要配TableModel而不是直接塞 List,为什么PreparedStatement的?占位符不能用在表名上,为什么 Swing 的 EDT(事件分发线程)里执行耗时查询会导致界面假死。这些坑,恰恰是面试官最爱问的“基础题”。


2. 从零搭建环境:JDK、MySQL、IDE 三件套的硬性版本约束

2.1 JDK 版本必须锁定在 8u291 或 11.0.15 —— 否则 Swing 界面会集体偏移

Swing 对 JVM 图形栈的依赖极深,尤其在 macOS Monterey 及 Windows 11 上,JDK 17+ 的 AWT 实现变更导致JFrame标题栏高度计算错误、JScrollPane滚动条消失、JComboBox下拉框位置错乱。实测 JDK 8u291(2021 年 4 月 LTS)和 JDK 11.0.15(2021 年 10 月 LTS)是目前最稳定的组合。安装时务必确认:

  • java -version输出中包含build 1.8.0_291-b10或11.0.15+10字样;
  • JAVA_HOME指向解压目录(非jre子目录);
  • PATH中bin目录排在系统自带 Java 前(echo $PATH | tr ':' '\n' | grep java验证)。

提示:Windows 用户若用 Chocolatey 安装,执行choco install openjdk --version=8.0.291;macOS 用户用 SDKMAN!:sdk install java 8.0.291-open。避免使用brew install openjdk,其默认安装最新版,极易翻车。

2.2 MySQL 必须关闭 strict mode 并启用本地认证插件

选课系统源码普遍基于 MySQL 5.7 编写,而新装 MySQL 8.0 默认开启STRICT_TRANS_TABLES,导致INSERT INTO course (name, credit) VALUES ('数据结构', NULL)直接报错(Field 'credit' doesn't have a default value),而非静默设为 0。必须修改my.cnf(Linux/macOS 在/etc/mysql/my.cnf,Windows 在C:\ProgramData\MySQL\MySQL Server 8.0\my.ini):

[mysqld] sql_mode = "NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" default_authentication_plugin = mysql_native_password

重启服务后,用 root 登录执行:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;

注意:default_authentication_plugin是关键!MySQL 8.0 默认用caching_sha2_password,而 Swing 的mysql-connector-java:5.1.47驱动不支持该插件,连接时抛出Unable to load authentication plugin 'caching_sha2_password'。必须降级为mysql_native_password。

2.3 IDE 选 IntelliJ IDEA Community(非 Eclipse)—— 因为 Swing Designer 早已死亡

Eclipse 的 WindowBuilder 插件自 2019 年起停止维护,拖拽生成的代码大量使用已废弃的GroupLayout,且与 JDK 11+ 兼容性极差。IntelliJ IDEA Community 版虽无可视化设计器,但其Swing UI Designer(需手动启用)生成的Form文件可稳定反编译为 Java 代码,且支持实时预览。配置步骤:

  1. File → Project Structure → Project:SDK 设为 JDK 8 或 11;
  2. File → Settings → Editor → GUI Designer:勾选Generate GUI into separate class;
  3. 新建Swing UI Designer文件时,选择JFrame模板,生成.form+.java两文件;
  4. 所有业务逻辑写在.java文件中,.form仅存布局定义——这是避免后期维护灾难的铁律。

3. 数据库建模:用真实选课场景倒推表结构与约束边界

3.1 四张核心表的设计逻辑与字段陷阱

选课系统绝非简单三张表(学生、课程、选课)。实际运行中必须处理:教师跨院系开课、同一课程多个教学班、学生重修覆盖原成绩、课程容量动态调整。因此必须建四张表,并严格设置外键与索引:

表名关键字段约束说明为什么这么设
studentid(PK),name,major,gradeid用CHAR(10)存学号(如20210001),禁用AUTO_INCREMENT学号含年份信息,且需与教务系统对接,自增 ID 无法映射
courseid(PK),name,credit,dept_idcredit设TINYINT UNSIGNED DEFAULT 0,非INT学分最大为 6,用TINYINT节省空间,UNSIGNED防负值
teacherid(PK),name,dept_iddept_id引用department.id,非VARCHAR院系用编码(如CS001)而非名称,避免更新时全表更新
selectionstudent_id,course_id,teacher_id,semester,score联合主键(student_id, course_id, semester),加唯一索引(student_id, course_id)防止同一学期重复选同一门课;semester格式固定为2023-2(年+学期)

提示:selection表的score字段必须允许NULL(未录入成绩时),但业务层需校验score BETWEEN 0 AND 100 OR score IS NULL,数据库层面用CHECK约束(MySQL 8.0.16+ 支持):

ALTER TABLE selection ADD CONSTRAINT chk_score CHECK (score IS NULL OR (score >= 0 AND score <= 100));

3.2 初始化数据脚本必须包含“脏数据”测试用例

很多源码附带的init.sql只有干净数据,导致上线后遇到真实数据立刻崩溃。你必须手动添加以下测试数据:

  • 学生20210001选了CS101但未录入成绩(score=NULL);
  • 教师T001同时在CS001和EE001两个院系任职(teacher_dept关联表);
  • 课程CS101在2023-1和2023-2两个学期均开设(course_offering表记录开课学期与容量)。
-- 插入带 NULL 成绩的选课记录 INSERT INTO selection (student_id, course_id, teacher_id, semester) VALUES ('20210001', 'CS101', 'T001', '2023-1'); -- 插入跨院系教师 INSERT INTO teacher_dept (teacher_id, dept_id) VALUES ('T001', 'CS001'), ('T001', 'EE001');

这些数据会暴露源码中常见的空指针:比如JTable渲染时未对score做null判断,直接调用String.valueOf(score)报错。


4. Swing 界面开发:三层解耦不是口号,而是救命绳

4.1 绝对禁止在ActionListener里写数据库操作

新手常把按钮点击逻辑全塞进addActionListener(e -> { /* 100 行 JDBC 代码 */ }),结果导致:

  • 界面卡死(EDT 被阻塞);
  • 异常堆栈指向AWT-EventQueue-0,根本找不到业务代码位置;
  • 无法复用查询逻辑(比如“查询学生”按钮和“导出 Excel”按钮要执行相同 SQL)。

正确做法是严格分三层:

  1. View 层:只负责组件创建、事件注册、数据展示(JTable.setModel());
  2. Controller 层:实现ActionListener,调用 Service 方法,用SwingWorker异步执行;
  3. Service 层:纯业务逻辑,含 JDBC 操作、事务控制、数据校验。
// Controller 层示例:登录按钮 loginBtn.addActionListener(e -> { String username = userField.getText(); String password = passField.getText(); // 启动异步任务 new LoginTask(username, password).execute(); }); // SwingWorker 子类:隔离 EDT private class LoginTask extends SwingWorker<Boolean, Void> { private final String username, password; LoginTask(String u, String p) { this.username = u; this.password = p; } @Override protected Boolean doInBackground() throws Exception { return new UserService().validateLogin(username, password); // Service 层调用 } @Override protected void done() { try { if (get()) { showMainView(); // 成功后切换界面 } else { JOptionPane.showMessageDialog(null, "用户名或密码错误"); } } catch (Exception ex) { JOptionPane.showMessageDialog(null, "登录失败:" + ex.getMessage()); } } }

注意:SwingWorker的doInBackground()在后台线程执行,done()回到 EDT,所以JOptionPane可安全调用。若忘记done(),所有 UI 更新都会在后台线程执行,引发IllegalStateException。

4.2 JTable 数据绑定必须用 DefaultTableModel,且禁用直接 setRowCount

源码中常见错误:tableModel.setRowCount(0); for (Student s : list) tableModel.addRow(new Object[]{...})。这会导致:

  • 每次addRow()触发一次TableModelEvent,界面频繁重绘;
  • 大量数据时卡顿(1000 行需触发 1000 次事件);
  • JTable滚动条位置丢失。

正确做法:构造二维数组一次性填充:

// Service 层返回 List<Student> List<Student> students = studentService.findAll(); // View 层:批量构建数据 Object[][] data = new Object[students.size()][4]; for (int i = 0; i < students.size(); i++) { Student s = students.get(i); data[i][0] = s.getId(); data[i][1] = s.getName(); data[i][2] = s.getMajor(); data[i][3] = s.getGrade(); } // 一次性设置模型 DefaultTableModel model = new DefaultTableModel(data, columnNames); table.setModel(model);

提示:columnNames必须是String[],不可用List<String>,否则JTable无法识别列标题。


5. JDBC 连接池与事务:手写 C3P0 配置比 HikariCP 更适合小白

5.1 为什么不用 Spring JDBC Template?因为你要看清 Connection 如何泄漏

Spring 的JdbcTemplate封装了Connection获取/释放逻辑,新手根本看不到conn.close()是否被调用。而选课系统必须直面:

  • 一个查询未关闭ResultSet,下一个查询就报Connection is closed;
  • SELECT后未close(),UPDATE时因连接数超限失败;
  • 事务中conn.setAutoCommit(false)后忘记commit()或rollback()。

所以必须手写连接池。C3P0 比 Druid 更适合教学:配置项少、日志清晰、异常堆栈直指问题根源。c3p0-config.xml放在src/main/resources:

<c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/course_db?useSSL=false&amp;serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">your_password</property> <!-- 关键参数 --> <property name="initialPoolSize">3</property> <property name="maxPoolSize">10</property> <property name="checkoutTimeout">3000</property> <!-- 3秒超时,避免线程挂起 --> <property name="acquireRetryAttempts">2</property> <!-- 连接失败重试2次 --> </default-config> </c3p0-config>

注意:jdbcUrl中useSSL=false是必须的(MySQL 8.0 默认要求 SSL),serverTimezone=Asia/Shanghai防止时间戳错乱。若漏掉&amp;(XML 实体),URL 解析失败,报Unknown database。

5.2 事务控制必须用 Connection 透传,而非 ThreadLocal

很多源码用ThreadLocal<Connection>管理事务,看似简洁,实则埋雷:

  • Swing 的SwingWorker创建新线程,ThreadLocal为空,getConnection()返回新连接;
  • 事务跨方法调用时,若某方法未从ThreadLocal取连接,就会开启新事务,导致数据不一致。

正确做法:将Connection作为参数显式传递:

// Service 层方法签名强制传 conn public void enrollCourse(Connection conn, String studentId, String courseId) throws SQLException { conn.setAutoCommit(false); try { // 插入选课记录 String sql = "INSERT INTO selection (student_id, course_id) VALUES (?, ?)"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, studentId); ps.setString(2, courseId); ps.executeUpdate(); } // 更新课程余量 updateCourseCapacity(conn, courseId, -1); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } }

提示:try-with-resources自动关闭PreparedStatement,但Connection必须由调用方管理——因为事务需要跨多个 DAO 方法。


6. 避坑:那些让小白调试到凌晨三点的真实报错与解法

6.1 现象:JFrame 窗口打开后立即关闭,控制台无任何报错

原因:main方法中未调用frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE),或误设为DISPOSE_ON_CLOSE。Swing 窗口默认关闭行为是HIDE_ON_CLOSE,窗口隐藏但 JVM 不退出,新手误以为程序崩溃。
解决:在JFrame初始化后立即设置:

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 必须! frame.setVisible(true); // 必须在 setBounds() 之后调用

6.2 现象:MySQL 连接报错Access denied for user 'root'@'localhost',但密码确认正确

原因:MySQL 8.0 创建用户时,默认主机名是'root'@'%',而本地连接尝试的是'root'@'localhost',两者权限独立。'root'@'%'的密码不适用于localhost。
解决:用 MySQL 8.0 安装时生成的临时密码登录,然后执行:

CREATE USER 'root'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES;

6.3 现象:JTable显示中文为方块(□□□),但控制台System.out.println("中文")正常

原因:Swing 组件默认字体不支持中文(如 Windows 上是Dialog,macOS 是Lucida Grande),而JLabel等组件会继承系统字体,JTable却不会自动适配。
解决:全局设置 UI 字体(放在main方法最开头):

UIManager.put("Label.font", new Font("微软雅黑", Font.PLAIN, 12)); UIManager.put("Table.font", new Font("微软雅黑", Font.PLAIN, 12)); UIManager.put("Button.font", new Font("微软雅黑", Font.PLAIN, 12));

6.4 现象:执行INSERT INTO selection后,JTable不刷新,但数据库已写入

原因:DefaultTableModel的addRow()方法会触发fireTableRowsInserted(),但若JTable的autoCreateRowSorter为true(默认),排序器会拦截事件,导致视图不更新。
解决:在JTable初始化后关闭自动排序:

table.setAutoCreateRowSorter(false); // 必须! // 或者,若需排序,改用 table.getRowSorter().toggleSortOrder(columnIndex);

6.5 现象:导出 Excel 时NullPointerException,堆栈指向poi的XSSFWorkbook构造函数

原因:源码中用了Apache POI 5.x,但 JDK 8 不兼容(POI 5+ 要求 JDK 9+)。XSSFWorkbook内部调用java.timeAPI,JDK 8 无此包。
解决:降级 POI 到4.1.2(JDK 8 兼容最后一版):

<!-- Maven 依赖 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency>

7. 进阶验证:用三个真实场景检验你是否真掌握了这套技术链

7.1 场景一:模拟教务处并发选课,验证连接池与事务是否生效

写一个压力测试脚本,启动 50 个线程同时为同一学生选同一门课(课程容量设为 1):

// 模拟 50 个并发请求 ExecutorService pool = Executors.newFixedThreadPool(50); for (int i = 0; i < 50; i++) { pool.submit(() -> { try { // 调用 enrollCourse() 方法 studentService.enrollCourse("20210001", "CS101"); } catch (Exception e) { System.err.println("选课失败:" + e.getMessage()); // 应看到 "课程已满" 错误 } }); } pool.shutdown();

预期结果:

  • 数据库selection表只插入 1 条记录(容量限制生效);
  • 控制台输出 49 次"课程已满"(事务回滚后抛出业务异常);
  • 连接池监控显示numBusyConnections峰值 ≤ 10(maxPoolSize限制生效)。

若出现 50 条记录或连接超时,则说明事务未开启或连接未归还池。

7.2 场景二:修改课程名称,验证外键级联是否破坏数据一致性

执行UPDATE course SET name = '人工智能导论' WHERE id = 'CS101',然后检查selection表中对应记录是否仍能通过JOIN查出原课程名。
关键点:course表的name字段不能设为外键(否则UPDATE会因外键约束失败),而应在selection表中冗余存储课程名称(course_name VARCHAR(50)),或用视图关联。源码若直接JOIN course查询,修改course.name后界面立即显示新名称——这正是业务所需,而非 bug。

7.3 场景三:将系统打包为可执行 jar,验证跨平台运行能力

用maven-assembly-plugin打包(非spring-boot-maven-plugin):

<plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>com.example.Main</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin>

执行mvn clean compile assembly:single生成target/course-system-jar-with-dependencies.jar。在另一台未装 JDK 的 Windows 电脑上双击运行(需预装 JRE 8),观察:

  • 界面正常渲染(验证 Swing 兼容性);
  • 连接本地 MySQL 成功(验证驱动内嵌);
  • 中文显示正常(验证字体资源打包)。

若失败,90% 是c3p0-config.xml未打进 jar,或mysql-connector-java依赖范围设为provided。

我带过的 37 个新人里,有 29 个卡在SwingWorker的done()方法没写get()导致 UI 不更新,有 12 个因my.cnf没关 strict mode 被INSERT NULL报错折磨超 4 小时。这些坑不是用来筛选人的,而是帮你建立对 Java 生态真实水位的认知——当你亲手填平它们,再看 Spring Boot 的@Transactional,就不会再把它当成魔法。希望帮到你。

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

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

基于OpenCV的烟丝图像分割与长度统计实战指南

简介&#xff1a;面向计算机视觉与烟草质检场景&#xff0c;这份基于Python和OpenCV实现的烟丝检测分割资源&#xff0c;适合有基础图像处理知识、希望落地目标检测与分割任务的开发者。项目围绕烟丝图像读入、颜色空间转换、滤波去噪、边缘检测、轮廓提取与形态学处理展开&…

作者头像 李华
网站建设 2026/9/28 6:00:47

JavaWeb旅游系统:JSP+Servlet+MySQL分层架构实战指南

简介&#xff1a;这是一套基于JavaWeb技术栈开发的旅游管理系统完整源码&#xff0c;面向Java初学者与Web开发入门者&#xff0c;聚焦ServletJSPMySQL三层架构实践&#xff0c;帮助学习者掌握用户注册审核、景点信息检索、酒店在线预订及后台多模块管理等典型业务场景。资源包共…

作者头像 李华
网站建设 2026/9/28 5:59:21

Python京东手机数据分析与推荐系统:爬虫到可视化全栈实战

1. 为什么我盯上了这个"毕业设计题目"每年到了毕业季&#xff0c;计算机相关专业的学生就开始集体焦虑&#xff1a;论文题目选什么&#xff1f;系统做什么&#xff1f;既要体现技术含量&#xff0c;又不能复杂到做不完。我见过太多人栽在"选题一时爽&#xff0c…

作者头像 李华
网站建设 2026/9/28 5:57:48

医学图像2D分割数据集构建:冠状面与矢状面切片策略

简介&#xff1a;本资源是面向医学图像分割初学者与算法工程师的高质量2D脾脏分割数据集&#xff0c;源自公开3D脾脏分割数据&#xff0c;在冠状面与矢状面两个解剖视角下精细抽取并严格筛选&#xff08;剔除前景占比低于0.1%的样本&#xff09;&#xff0c;专用于训练和验证轻…

作者头像 李华
网站建设 2026/9/28 5:56:23

Flutter鸿蒙原生集成实战:从工程配置到MethodChannel通信

Flutter要跑鸿蒙这事&#xff0c;圈里讨论了大半年&#xff0c;最绕不开的就是"原生集成"这一关。说白了&#xff0c;跨平台框架落地的最后一步永远是&#xff1a;怎么把Flutter引擎和页面&#xff0c;嵌入到你现有的鸿蒙原生工程&#xff08;ArkTS/ArkUI&#xff09…

作者头像 李华
网站建设 2026/9/28 5:54:54

基数排序详解:非比较排序的分桶原理与工程实战

我在带算法集训的时候&#xff0c;排序专题习惯分成两个阶段&#xff1a;前段日子把比较排序挨个啃完&#xff0c;第九天刚把快排和归并的递归栈捋顺&#xff0c;到第十天聊基数排序&#xff0c;学生普遍会愣一下——这不是“按大小排队”吗&#xff0c;跟“位数”有什么关系&a…

作者头像 李华