Java Swing+JDBC+MySQL毕业设计选题系统:事务控制与避坑指南 简介这份资源是Java SwingJDBCMySQL实现的毕业设计选题管理系统完整项目包面向计算机相关专业学生、Java桌面开发初学者及需要课程设计或毕设参考的开发者帮助解决选题分配流程信息化的问题。系统覆盖学生选题、教师命题、管理员审核等环节包含用户权限管理、题目发布、选题申请、审核流程、查询统计与数据备份恢复等核心功能。压缩包共18个文件约4.61MB以12个java源码文件为主体另含sql建库脚本、doc与md格式的数据库表设计文档、jar依赖包及iml工程配置结构清晰便于导入运行与二次开发。目前已有1532人学习下载适合作为Java桌面应用与数据库综合实践的参考案例可据此理解Swing界面搭建、JDBC数据交互与MySQL表结构设计并借鉴异常处理、代码复用等编程实践快速完成类似系统的搭建与调试。1. 从一张选题志愿表说起SwingJDBCMySQL 的选题系统到底解决什么问题每年毕业季教务老师手里都会堆起一摞选题志愿表学生填三个志愿导师再逐个确认中间只要有一轮调剂Excel 就开始打架谁被谁选了、哪个题目还剩名额、哪个导师超了指标全靠人工核对。用 Java SwingJDBCMySQL 做一套毕业设计选题管理系统本质就是把这套「志愿填报—导师确认—名额调剂」的流程搬进一个带界面的桌面程序里。它适合两类人一类是正在做 Java 课程设计、需要一套能跑通 CRUD 和事务的完整案例另一类是想练手 JDBC 连接 MySQL、Swing 事件驱动和表关联查询的初学者。这套东西不追求高并发追求的是逻辑闭环和数据库操作规范把选题、导师、学生三张表的关系理清楚比堆界面控件重要得多。2. 选题系统的数据模型与 JDBC 连接层先把三张表和一条连接做扎实2.1 学生、导师、选题三张表怎么设计才不返工很多同学一上来就画界面结果做到一半发现字段不够用回头改表结构JDBC 代码全得重写。血泪经验是先把表定死。核心就三张student、teacher、topic再加一张selection记录选题关系。topic表里要有teacher_id外键和max_students名额字段selection表用student_id和topic_id做联合唯一约束防止一个学生重复选同一个题。CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, title VARCHAR(20), max_topics INT DEFAULT 3 ); CREATE TABLE topic ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, max_students INT DEFAULT 1, selected_count INT DEFAULT 0, FOREIGN KEY (teacher_id) REFERENCES teacher(id) ); CREATE TABLE selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, topic_id INT NOT NULL, status TINYINT DEFAULT 0, -- 0待确认 1已确认 2已拒绝 UNIQUE KEY uk_stu_topic (student_id, topic_id) );selected_count这个冗余字段是故意的每次选课成功就加一避免每次查名额都去selection表 countSwing 表格刷新时性能差别很明显。status用 TINYINT 而不是字符串省空间也方便做状态流转判断。2.2 JDBC 连接 MySQL 的三种写法与连接池取舍初学者最容易在jdbc连接mysql这一步翻车要么驱动没加载要么时区报错。最稳的写法是把连接参数抽到配置文件别硬编码在 Java 里。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/topic_sys?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动未找到, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } }useSSLfalse是为了绕开本地开发时的mysql ssl连接错误生产环境不该这么写但课程设计阶段这样最省事。serverTimezoneAsia/Shanghai不加的话MySQL 8.0 会直接抛时区异常。如果想让 Swing 界面在频繁刷新表格时不卡可以引入 Druid 或 HikariCP 连接池把getConnection换成从池里取但课程设计用DriverManager足够别为了炫技把依赖搞复杂。2.3 用 PreparedStatement 封装通用增删改查Swing 里每个按钮背后都是一次数据库操作如果每个都写一遍Statement代码会又臭又长。封装一个通用方法参数用Object...传。public static int update(String sql, Object... params) { try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return -1; } }setObject能自动处理 String、Integer、Date 等常见类型省去一堆if-else判断。返回值 -1 表示失败Swing 里拿到 -1 就弹JOptionPane提示别让异常静默吞掉。查询方法同理返回ListMapString,Object或者直接映射成实体类看你对面向对象编程java的掌握程度实体类更规范Map 更快出活。3. Swing 界面与选题业务逻辑从登录到选课的事务控制3.1 登录窗口与角色分流的最小实现Swing 做登录界面核心是JFrameJPanelGridBagLayout别用绝对定位否则换个分辨率就错位。登录成功后根据角色打开不同主窗口学生看到选题列表导师看到自己发布的题目和待确认列表。loginBtn.addActionListener(e - { String sql SELECT id, role FROM user WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, userField.getText()); ps.setString(2, new String(pwdField.getPassword())); ResultSet rs ps.executeQuery(); if (rs.next()) { int role rs.getInt(role); dispose(); if (role 1) new StudentFrame(rs.getInt(id)).setVisible(true); else new TeacherFrame(rs.getInt(id)).setVisible(true); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } } catch (SQLException ex) { ex.printStackTrace(); } });密码用JPasswordField拿到的char[]转 String别直接存明文到数据库课程设计至少做个 MD5 或者直接存明文但心里要有数。角色字段role用 1 和 2 区分分流逻辑写在监听器里别在构造函数里查数据库Swing 的 UI 构建和业务查询要分开。3.2 选题事务名额扣减和重复选择的原子操作选课按钮点下去背后要做三件事检查名额、检查是否已选、插入记录并更新selected_count。这三步必须在一个事务里否则并发下会出现名额超卖。JDBC 事务默认自动提交要手动关掉。public boolean selectTopic(int studentId, int topicId) { String checkSql SELECT max_students, selected_count FROM topic WHERE id? FOR UPDATE; String insertSql INSERT INTO selection(student_id, topic_id) VALUES(?,?); String updateSql UPDATE topic SET selected_countselected_count1 WHERE id?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement check conn.prepareStatement(checkSql)) { check.setInt(1, topicId); ResultSet rs check.executeQuery(); if (!rs.next()) { conn.rollback(); return false; } if (rs.getInt(selected_count) rs.getInt(max_students)) { conn.rollback(); return false; } } try (PreparedStatement ins conn.prepareStatement(insertSql); PreparedStatement upd conn.prepareStatement(updateSql)) { ins.setInt(1, studentId); ins.setInt(2, topicId); ins.executeUpdate(); upd.setInt(1, topicId); upd.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ignored) {} return false; } finally { if (conn ! null) try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } }FOR UPDATE是行级锁防止两个学生同时读到同一个剩余名额。setAutoCommit(false)之后所有操作要么全成功要么全回滚这就是jdbc transaction的典型用法。注意finally里要把autoCommit恢复否则连接放回池里会带着脏状态。如果插入时触发唯一约束异常说明学生重复选了catch 里回滚即可不用额外查一次。3.3 用 JTable 展示选题列表并实时刷新Swing 的JTable配DefaultTableModel是最顺手的组合数据从数据库查出来直接塞进二维数组。导师确认选课后学生的表格要能刷新别每次关窗口重开。String[] cols {题目ID, 题目名称, 导师, 已选/上限, 状态}; DefaultTableModel model new DefaultTableModel(cols, 0); JTable table new JTable(model); public void refreshTable() { model.setRowCount(0); String sql SELECT t.id, t.title, te.name, t.selected_count, t.max_students FROM topic t JOIN teacher te ON t.teacher_idte.id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { model.addRow(new Object[]{ rs.getInt(1), rs.getString(2), rs.getString(3), rs.getInt(4) / rs.getInt(5), 可选 }); } } catch (SQLException e) { e.printStackTrace(); } }setRowCount(0)清空旧数据再重新填比fireTableDataChanged更直接。状态列可以根据selected_count和max_students动态算满了就显示「已满」。刷新时机放在选课成功回调里别用定时器轮询Swing 的单线程模型经不起折腾。4. 避坑与排查SwingJDBC 选题系统最常见的五个翻车点4.1 现象界面卡死按钮点不动原因在事件监听器里直接执行了耗时查询Swing 的事件分发线程被阻塞。解决把数据库操作放到SwingWorker的doInBackground里done方法里再更新 UI。课程设计数据量小但养成习惯没坏处。4.2 现象中文乱码数据库里存的是问号原因连接 URL 没加characterEncodingutf8或者 MySQL 建库时字符集是 latin1。解决URL 补上参数建库语句用DEFAULT CHARSETutf8mb4两边对齐。4.3 现象选课成功但名额没变原因selected_count更新和插入不在同一个事务或者autoCommit没关。解决按 3.2 的写法把两步包进事务确认commit执行了。4.4 现象MySQL 8.0 连接报Public Key Retrieval is not allowed原因MySQL 8.0 默认认证插件是caching_sha2_password旧驱动不兼容。解决URL 加allowPublicKeyRetrievaltrue或者把用户认证方式改成mysql_native_password。4.5 现象导师删除题目后学生端还能看到原因查询没加WHERE条件过滤已删除状态或者用了软删除但查询没带is_deleted0。解决要么物理删除并级联清理selection要么统一在查询里加状态过滤别两套逻辑混用。5. 进阶技巧用存储过程做名额校验和状态流转课程设计做到后面导师端要批量确认选课一条条 update 太慢。可以把名额校验和状态流转写成 MySQL 存储过程Java 端只调一次CallableStatement。这样逻辑集中在数据库层Swing 端代码更薄。DELIMITER // CREATE PROCEDURE confirm_selection(IN sel_id INT, OUT result INT) BEGIN DECLARE v_topic_id INT; DECLARE v_status TINYINT; SELECT topic_id, status INTO v_topic_id, v_status FROM selection WHERE id sel_id; IF v_status 0 THEN UPDATE selection SET status 1 WHERE id sel_id; SET result 1; ELSE SET result 0; END IF; END // DELIMITER ;Java 调用时用CallableStatement注册出参CallableStatement cs conn.prepareCall({call confirm_selection(?, ?)}); cs.setInt(1, selectionId); cs.registerOutParameter(2, Types.INTEGER); cs.execute(); int result cs.getInt(2);result1表示确认成功0表示状态不对。存储过程的好处是把判断逻辑收拢坏处是调试麻烦MySQL 里DECLARE必须写在最前面顺序错了直接报语法错误。我一般会在selection表上加一个confirm_time字段确认时顺手写入时间戳后面统计用得上。验证方法很简单开两个学生账号同时选同一个只剩一个名额的题目看是否只有一个成功。如果两个都成功说明事务隔离级别或者FOR UPDATE没生效回去检查setAutoCommit和锁的范围。另一个习惯是每次改完 JDBC 代码先把 SQL 拿到 MySQL 命令行里跑一遍确认语句本身没问题再排查 Java 层。这套系统不大但把事务、连接、界面刷新这三块吃透后面做任何 JavaWeb 项目都能迁移过去。希望帮到你。本文还有配套的精品资源点击获取