
简介本资源是一份面向高校计算机专业本科生的MySQL课程设计实战项目聚焦学生宿舍管理场景完整呈现数据库设计、Java后端逻辑与Swing桌面界面的三层协同开发过程。适用于数据库原理、Java程序设计及软件工程类课程实践助力初学者掌握从E-R建模、SQL脚本编写、JDBC连接到GUI组件布局的全链路开发能力。压缩包共334个文件含47个核心Java源码如AdminManageComponent、StuManageComponent等模块类、202个编译后class文件、22张系统界面与数据库关系图PNG、9个配置与布局XML以及需求分析、ER图、数据字典、系统说明书等配套文档整体容量112.72MB。目前已有268人学习下载提供开箱即用的可运行jar包、完整IDEA/VS项目结构含sln、iml、vcxproj等、调试所需pdb/ilk等构建产物以及清晰分层的源码组织便于理解模块职责与系统调用流程。1. 学生宿舍管理系统为什么非得用 MySQL Java Swing——一个被低估的“老派组合”在真实教学场景中的不可替代性你可能刚看到这个标题就皱眉Swing2024 年还写 Swing是不是过时了但如果你正面临一门《数据库原理与应用》或《Java 程序设计》课程设计任务老师明确要求“独立完成、本地可运行、界面可交互、数据可持久”那这个看似“复古”的技术栈恰恰是最稳、最透明、最易调试、最能暴露底层逻辑的教学闭环。它不依赖云服务、不卡在前端框架版本兼容上、不因 Node.js 环境缺失而瘫痪MySQL 提供标准 SQL 实践环境Java 承担业务逻辑与 JDBC 桥梁Swing 则把“窗体怎么布局、事件怎么绑定、数据怎么刷进表格”这些抽象概念变成肉眼可见、断点可停、变量可查的实体。这不是炫技而是教学刚需学生要亲手写PreparedStatement防注入要手动处理ResultSet映射对象要在JTable的TableModel里重写getValueAt()—— 这些动作比拖一个 Vue 组件更能建立对“数据-逻辑-视图”三层关系的肌肉记忆。本项目不是为上线而生而是为“讲清楚每一行代码从哪来、到哪去”而建。接下来我会带你从零搭起这个系统不跳步、不封装黑盒、不隐藏 JDBC 连接细节每一步都对应一个可验证的课堂知识点。2. 从 ER 图到建库建表用 MySQL Workbench 反向驱动需求分析与规范化落地2.1 先画清楚“谁管谁”宿舍管理核心实体关系推演学生宿舍管理绝不是“学生宿舍”两张表就能打发的。真实教学场景中必须覆盖入住、调宿、报修、卫生检查、费用缴纳五类高频操作这就倒逼出至少 6 个强关联实体student学号、姓名、性别、院系、班级、联系电话dormitory楼号、房间号、床位数、当前入住数、管理员IDdorm_manager工号、姓名、联系电话、负责楼栋repair_record报修ID、学生学号、楼号房间号、故障描述、报修时间、处理状态、处理人、完成时间hygiene_inspection检查ID、楼号房间号、检查日期、卫生得分、检查人、备注fee_record缴费ID、学生学号、费用类型【住宿费/水电费/维修费】、金额、缴费日期、是否结清提示不要一上来就建表先用纸笔或白板推演“一个学生调宿”涉及哪些表更新student.dorm_id改、原dormitory.occupancy减1、新dormitory.occupancy加1、生成repair_record若旧床损坏这比写 CREATE TABLE 更关键。2.2 用 MySQL Workbench 做可视化建模与正向生成我们不用手敲 DDL而是用官方工具保证范式合规与外键显式化。打开 MySQL Workbench → Database → Reverse Engineer → 连接本地 MySQL默认 root/123456→ 选择空 schema如dorm_db→ 开始建模。在 EER Diagram 中拖出 6 个实体表按以下规则连线student→dormitory多对一一个房间住多人设外键student.dorm_id引用dormitory.iddormitory→dorm_manager多对一一栋楼一个管理员设外键dormitory.manager_idrepair_record→studentdormitory双外键确保报修人和房间存在hygiene_inspection→dormitory一对多检查针对房间而非学生fee_record→student严格一对一绑定缴费主体建模完成后右键 Diagram → “Forward Engineer…” → 生成 SQL 脚本。关键参数必须手动修正-- 生成脚本后务必修改这三处 CREATE TABLE student ( id VARCHAR(12) NOT NULL COMMENT 学号主键, name VARCHAR(20) NOT NULL, gender ENUM(男,女) DEFAULT 男, -- 用 ENUM 而非 VARCHAR教学时强调数据约束意义 dorm_id INT NULL, -- 允许为空新生未分配宿舍时 PRIMARY KEY (id), INDEX fk_student_dormitory_idx (dorm_id ASC) VISIBLE, CONSTRAINT fk_student_dormitory FOREIGN KEY (dorm_id) REFERENCES dormitory (id) ON DELETE SET NULL -- 关键删宿舍不删学生只清空 dorm_id ) ENGINE InnoDB;逻辑说明ON DELETE SET NULL是教学重点——它让学生直观理解“级联行为”的可配置性比 ORM 自动生成的CASCADE更具教学穿透力。参数说明VARCHAR(12)对应 10 位学号2 位校区码ENUM强制取值范围避免数据库存入“未知”“保密”等脏数据VISIBLE索引确保EXPLAIN时能被优化器识别。执行该脚本后在 MySQL CLI 中验证mysql -u root -p -e USE dorm_db; SHOW TABLES; # 应返回 6 张表名 mysql -u root -p -e USE dorm_db; DESCRIBE student; # 检查字段类型、NULL 属性、KEY 类型是否符合预期3. Java 工程骨架与 JDBC 连接池搭建拒绝 static DriverManager用 HikariCP 把连接管理讲透3.1 Maven 依赖精简到最小可行集新建 Maven 工程IDEA 中选Create New Project → Maven → archetype: maven-archetype-quickstartpom.xml只保留 4 个必要依赖dependencies !-- MySQL 8.0 驱动必须用 mysql-connector-j -- dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId version8.3.0/version /dependency !-- HikariCP 连接池轻量、快、监控友好 -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency !-- 日志门面 SLF4J 简单实现 SimpleLogger -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.12/version /dependency !-- JUnit 5 测试课程设计虽不强制测试但加单元测试是工程师习惯 -- dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.10.2/version scopetest/scope /dependency /dependencies逻辑说明不用 Spring Boot 是为了剥离自动配置黑盒mysql-connector-j替代已废弃的mysql-connector-javaHikariCP 的5.0.1版本支持 JDK 17且配置项更语义化。参数说明scopetest/scope表示 JUnit 仅编译期有效不打入最终 jar 包减小体积。3.2 写死配置不用config.properties实现环境解耦在src/main/resources/下新建config.properties# 数据库连接参数教学演示用实际应加密或由运维提供 jdbc.urljdbc:mysql://localhost:3306/dorm_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456 # HikariCP 核心参数教学必须讲清这 4 个 hikari.maximumPoolSize10 hikari.minimumIdle2 hikari.connectionTimeout30000 hikari.idleTimeout600000参数说明useSSLfalse是本地开发必需MySQL 8.0 默认强制 SSL但自签名证书在 Swing 客户端极难配置serverTimezoneAsia/Shanghai防止java.sql.Timestamp时区错乱maximumPoolSize10足够应付 50 人并发课程设计演示connectionTimeout30000即 30 秒超时抛异常而非无限等待这是教学生“资源获取必须设防”。3.3 封装DBUtil一行代码获取连接但内部全是教学知识点创建util/DBUtil.javapackage util; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static HikariDataSource dataSource; // 静态块类加载时初始化连接池单例模式 static { try (InputStream is DBUtil.class.getClassLoader().getResourceAsStream(config.properties)) { Properties props new Properties(); props.load(is); HikariConfig config new HikariConfig(); config.setJdbcUrl(props.getProperty(jdbc.url)); config.setUsername(props.getProperty(jdbc.username)); config.setPassword(props.getProperty(jdbc.password)); config.setMaximumPoolSize(Integer.parseInt(props.getProperty(hikari.maximumPoolSize))); config.setMinimumIdle(Integer.parseInt(props.getProperty(hikari.minimumIdle))); config.setConnectionTimeout(Long.parseLong(props.getProperty(hikari.connectionTimeout))); config.setIdleTimeout(Long.parseLong(props.getProperty(hikari.idleTimeout))); // 关键教学点开启连接泄漏检测5 秒未归还即告警 config.setLeakDetectionThreshold(5000); dataSource new HikariDataSource(config); } catch (Exception e) { throw new RuntimeException(初始化数据库连接池失败, e); } } // 对外提供 getConnection()但绝不暴露 dataSource public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 必须提供 close() 方法否则连接永不释放教学血泪经验 public static void close(Connection conn) { if (conn ! null !conn.isClosed()) { try { conn.close(); } catch (SQLException e) { // 记录日志但不抛出避免打断业务流 System.err.println(关闭数据库连接异常 e.getMessage()); } } } }逻辑说明static块确保全局唯一连接池try-with-resources自动关闭InputStreamsetLeakDetectionThreshold(5000)是神来之笔——当学生忘记conn.close()5 秒后控制台会打印Connection leak detection triggered立刻定位资源泄漏点。参数说明所有props.getProperty()都带默认值校验生产环境需增强此处为教学简化。验证连接是否成功写一个TestConnection.javapackage test; import util.DBUtil; import java.sql.Connection; public class TestConnection { public static void main(String[] args) { try (Connection conn DBUtil.getConnection()) { System.out.println(✅ 数据库连接成功连接池活跃连接数 ((com.zaxxer.hikari.HikariDataSource) util.DBUtil.dataSource).getHikariPoolMXBean().getActiveConnections()); } catch (Exception e) { System.err.println(❌ 连接失败 e.getMessage()); e.printStackTrace(); } } }运行输出✅ 数据库连接成功连接池活跃连接数1即通过。4. Swing 界面分层实战用 MVC 拆解“学生信息管理”模块拒绝上帝类4.1 界面结构设计三层分离每个类只做一件事Swing 不是“拖控件完事”而是用代码构建可维护 UI。以“学生信息录入”功能为例拆分为View视图StudentAddFrame.java—— 只负责创建窗体、按钮、文本框不碰业务逻辑Controller控制器StudentAddController.java—— 监听“保存”按钮点击调用 Service处理成功/失败反馈Model模型Student.javaPOJO StudentService.java业务类 —— 封装insertStudent()方法内含 JDBC 操作提示这是教学中最易崩塌的一环。学生常把 SQL 写在ActionListener里导致界面类膨胀、无法单元测试、修改 SQL 需改 UI 代码。MVC 是救赎。4.2 View 层用 GroupLayout 实现像素级可控布局比 GridBagLayout 更易教StudentAddFrame.javapackage view; import javax.swing.*; import java.awt.*; public class StudentAddFrame extends JFrame { // 所有组件声明为 private final禁止外部修改 private final JTextField tfId new JTextField(15); private final JTextField tfName new JTextField(15); private final JComboBoxString cbGender new JComboBox(new String[]{男, 女}); private final JTextField tfDept new JTextField(15); private final JTextField tfClass new JTextField(15); private final JTextField tfPhone new JTextField(15); private final JButton btnSubmit new JButton(保 存); private final JButton btnCancel new JButton(取 消); public StudentAddFrame() { setTitle(添加学生信息); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); setLayout(new BorderLayout()); // 顶部提示面板 JPanel topPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); topPanel.add(new JLabel( 请填写学生基本信息)); add(topPanel, BorderLayout.NORTH); // 中间表单面板核心GroupLayout JPanel formPanel new JPanel(); GroupLayout layout new GroupLayout(formPanel); formPanel.setLayout(layout); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); // 水平组按行排列 layout.setHorizontalGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING) .addComponent(new JLabel(学号)) .addComponent(new JLabel(姓名)) .addComponent(new JLabel(性别)) .addComponent(new JLabel(院系)) .addComponent(new JLabel(班级)) .addComponent(new JLabel(电话)) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(tfId) .addComponent(tfName) .addComponent(cbGender) .addComponent(tfDept) .addComponent(tfClass) .addComponent(tfPhone) ) ); // 垂直组按列排列 layout.setVerticalGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(new JLabel(学号)) .addComponent(tfId) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(new JLabel(姓名)) .addComponent(tfName) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(new JLabel(性别)) .addComponent(cbGender) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(new JLabel(院系)) .addComponent(tfDept) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(new JLabel(班级)) .addComponent(tfClass) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(new JLabel(电话)) .addComponent(tfPhone) ) ); add(formPanel, BorderLayout.CENTER); // 底部按钮面板 JPanel buttonPanel new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttonPanel.add(btnSubmit); buttonPanel.add(btnCancel); add(buttonPanel, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); // 居中显示 setResizable(false); } // 提供 getter供 Controller 获取用户输入 public String getStudentId() { return tfId.getText().trim(); } public String getStudentName() { return tfName.getText().trim(); } public String getGender() { return (String) cbGender.getSelectedItem(); } public String getDepartment() { return tfDept.getText().trim(); } public String getClassName() { return tfClass.getText().trim(); } public String getPhone() { return tfPhone.getText().trim(); } public JButton getSubmitButton() { return btnSubmit; } public JButton getCancelButton() { return btnCancel; } }逻辑说明GroupLayout是 Swing 中最接近“CSS Flexbox”的布局管理器setAutoCreateGaps(true)自动加间距避免手动add(new JLabel( ))setLocationRelativeTo(null)比setLocation(300,200)更健壮。参数说明JTextField(15)的 15 是列宽非像素适配不同字体cbGender用String[]初始化比DefaultComboBoxModel更直观。4.3 Controller 层事件绑定与错误防御的完整链路StudentAddController.javapackage controller; import model.Student; import model.StudentService; import util.DBUtil; import view.StudentAddFrame; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.sql.SQLException; public class StudentAddController { private final StudentAddFrame view; private final StudentService service; public StudentAddController(StudentAddFrame view) { this.view view; this.service new StudentService(); // 依赖注入雏形 // 绑定保存按钮 view.getSubmitButton().addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { handleSave(); } }); // 绑定取消按钮 view.getCancelButton().addActionListener(e - view.dispose()); } private void handleSave() { // 1. 输入校验教学重点永远不要相信用户输入 if (view.getStudentId().isEmpty()) { JOptionPane.showMessageDialog(view, ❌ 学号不能为空, 输入错误, JOptionPane.ERROR_MESSAGE); view.tfId.requestFocus(); return; } if (view.getStudentName().isEmpty()) { JOptionPane.showMessageDialog(view, ❌ 姓名不能为空, 输入错误, JOptionPane.ERROR_MESSAGE); view.tfName.requestFocus(); return; } // 2. 构造 Student 对象 Student student new Student(); student.setId(view.getStudentId()); student.setName(view.getStudentName()); student.setGender(view.getGender()); student.setDepartment(view.getDepartment()); student.setClassName(view.getClassName()); student.setPhone(view.getPhone()); // 3. 调用 Service 插入数据库 try { int rows service.insertStudent(student); if (rows 0) { JOptionPane.showMessageDialog(view, ✅ 添加成功共插入 rows 条记录。, 操作成功, JOptionPane.INFORMATION_MESSAGE); // 清空表单准备下一次录入 view.tfId.setText(); view.tfName.setText(); view.cbGender.setSelectedIndex(0); view.tfDept.setText(); view.tfClass.setText(); view.tfPhone.setText(); view.tfId.requestFocus(); // 光标回到学号框 } } catch (SQLException ex) { // 4. 数据库异常分类处理教学价值极高 String msg ex.getMessage(); if (msg.contains(Duplicate entry)) { JOptionPane.showMessageDialog(view, ❌ 学号 view.getStudentId() 已存在, 主键冲突, JOptionPane.ERROR_MESSAGE); view.tfId.requestFocus(); } else if (msg.contains(Data truncation)) { JOptionPane.showMessageDialog(view, ❌ 输入内容过长请检查院系、班级字段长度, 数据截断, JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(view, ❌ 数据库操作失败 msg, 系统错误, JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); // 开发时打印堆栈教学时可讲解异常链 } } } }逻辑说明JOptionPane是 Swing 原生对话框比System.out.println更符合 GUI 场景requestFocus()提升体验catch (SQLException)中的分支判断是教学生读异常信息的第一课。参数说明service.insertStudent()返回int表示影响行数0即成功比boolean更严谨可区分INSERT IGNORE场景。4.4 Model 层JDBC 模板代码的最小化封装model/StudentService.javapackage model; import util.DBUtil; import java.sql.*; public class StudentService { // 插入学生使用 PreparedStatement 防 SQL 注入教学核心 public int insertStudent(Student student) throws SQLException { String sql INSERT INTO student (id, name, gender, department, class_name, phone) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, student.getId()); pstmt.setString(2, student.getName()); pstmt.setString(3, student.getGender()); pstmt.setString(4, student.getDepartment()); pstmt.setString(5, student.getClassName()); pstmt.setString(6, student.getPhone()); return pstmt.executeUpdate(); // 返回影响行数 } } }逻辑说明try-with-resources确保Connection和PreparedStatement自动关闭比finally{close()}更安全pstmt.setString(1, ...)的索引从 1 开始这是 JDBC 规范必须强调executeUpdate()用于 INSERT/UPDATE/DELETEexecuteQuery()用于 SELECT。参数说明student对象字段名与数据库列名严格对应class_name对应className避免setString(1, student.getClassName())写成setString(1, student.getClass())后者是 Java Class 对象必报错。启动入口Main.javapackage main; import controller.StudentAddController; import view.StudentAddFrame; public class Main { public static void main(String[] args) { // Swing 线程安全所有 UI 操作必须在 Event Dispatch Thread SwingUtilities.invokeLater(() - { StudentAddFrame frame new StudentAddFrame(); new StudentAddController(frame); // Controller 持有 View启动监听 frame.setVisible(true); }); } }运行后弹出窗体输入学号2023001、姓名张三点保存 → 控制台无报错 → MySQL 中SELECT * FROM student WHERE id2023001;应返回一行数据。5. 避坑指南课程设计中最常翻车的 4 个现场与后悔药5.1 现象Swing 窗体启动后一片空白或按钮点击无反应原因未调用SwingUtilities.invokeLater()UI 在主线程创建违反 Swing 单线程规则或Controller构造函数中未正确绑定ActionListener如btn.addActionListener(this)但类未实现ActionListener接口。解决严格使用SwingUtilities.invokeLater()包裹所有new JFrame().setVisible(true)监听器必须用匿名内部类或 Lambda 表达式btn.addActionListener(e - {...})避免继承ActionListener带来的接口污染。5.2 现象中文插入 MySQL 后显示为???或乱码原因MySQL 服务端字符集非utf8mb4JDBC URL 缺少characterEncodingutf8或config.properties中未设置useUnicodetrue。解决MySQL 中执行SHOW VARIABLES LIKE character_set%;确认character_set_serverutf8mb4修改 JDBC URLjdbc:mysql://localhost:3306/dorm_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseUnicodetruecharacterEncodingutf8mb4创建表时显式指定字符集CREATE TABLE student (...) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;5.3 现象HikariCP报Connection is not available, request timed out after 30000ms原因连接池耗尽如maximumPoolSize5但并发开 10 个窗体或某次查询未关闭ResultSet/Statement导致连接被长期占用。解决教学演示时将maximumPoolSize设为10在StudentService等所有 DAO 方法中必须用try-with-resources禁用conn.createStatement()后手动close()启用 HikariCP 泄漏检测前文已设setLeakDetectionThreshold(5000)控制台出现Connection leak即刻检查close()调用位置。5.4 现象JTable显示数据为空或列名不显示原因DefaultTableModel构造时传入null数据或JTable未设置setAutoCreateRowSorter(true)导致排序失效或TableModel的getColumnCount()返回 0。解决初始化JTable时用new JTable(new DefaultTableModel(data, columnNames))data为Object[][]columnNames为String[]若动态刷新用((DefaultTableModel) table.getModel()).setRowCount(0)清空后addRow()必须调用table.getTableHeader().setReorderingAllowed(false)禁用列拖拽教学演示需稳定界面。6. 进阶技巧用JTable实现宿舍信息实时联动与条件筛选让静态表格活起来6.1 用TableRowSorter实现点击列头排序无需重写 TableModel很多学生以为排序必须重写TableModel的getValueAt()其实 Swing 内置TableRowSorter就够用。在DormitoryListFrame.java中// 假设已有 JTable dormTable 和 DefaultTableModel model TableRowSorterDefaultTableModel sorter new TableRowSorter(model); dormTable.setRowSorter(sorter); // 设置每列排序方式可选 sorter.setSortable(0, true); // 学号列可排序 sorter.setSortable(1, true); // 楼号列可排序 sorter.setSortable(2, false); // 床位数列禁用排序数值意义不大技巧说明TableRowSorter是装饰器模式不侵入原有TableModelsetSortable(colIndex, false)可禁用特定列排序避免用户误操作。教学价值让学生理解“功能扩展”不等于“重写核心”。6.2 实现“按楼号筛选”的搜索框JTextField DocumentListener在DormitoryListFrame底部加搜索区JPanel searchPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); searchPanel.add(new JLabel( 按楼号搜索)); JTextField searchField new JTextField(10); searchPanel.add(searchField); add(searchPanel, BorderLayout.SOUTH); // 绑定实时搜索DocumentListener 比 ActionListener 更及时 searchField.getDocument().addDocumentListener(new DocumentListener() { Override public void insertUpdate(DocumentEvent e) { filterTable(); } Override public void removeUpdate(DocumentEvent e) { filterTable(); } Override public void changedUpdate(DocumentEvent e) { filterTable(); } private void filterTable() { String keyword searchField.getText().trim(); if (keyword.isEmpty()) { sorter.setRowFilter(null); // 清空过滤 } else { // 使用 RowFilter.regexFilter支持模糊匹配 sorter.setRowFilter(RowFilter.regexFilter((?i) keyword, 1)); // 第1列是楼号 } } });技巧说明(?i)使正则忽略大小写RowFilter.regexFilter是 Swing 内置过滤器无需手写循环遍历TableModelDocumentListener响应每次按键比“搜索”按钮更现代。教学价值展示 GUI 事件驱动与数据视图分离。6.3 宿舍-学生双向联动点击宿舍行下方JTable自动刷出该宿舍学生这是课程设计的高光时刻。需两个JTabledormTable宿舍列表和studentTable学生列表共享一个StudentService// 在 dormTable 的鼠标点击监听中 dormTable.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() 1) { // 单击触发 int viewRow dormTable.getSelectedRow(); if (viewRow 0) { int modelRow dormTable.convertRowIndexToModel(viewRow); String buildingNo (String) dormModel.getValueAt(modelRow, 1); // 楼号列 String roomNo (String) dormModel.getValueAt(modelRow, 2); // 房间号列 // 查询该宿舍所有学生 ListStudent students service.findStudentsByDorm(buildingNo, roomNo); // 刷新 studentTable 的 model DefaultTableModel stuModel (DefaultTableModel) studentTable.getModel(); stuModel.setRowCount(0); // 清空 for (Student s : students) { stuModel.addRow(new Object[]{ s.getId(), s.getName(), s.getGender(), s.getDepartment() }); } } } } });StudentService.findStudentsByDorm()实现public ListStudent findStudentsByDorm(String buildingNo, String roomNo) throws SQLException { String sql SELECT s.id, s.name, s.gender, s.department FROM student s JOIN dormitory d ON s.dorm_id d.id WHERE d.building_no ? AND d.room_no ?; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, buildingNo); pstmt.setString(2, roomNo); try (ResultSet rs pstmt.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getString(id)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setDepartment(rs.getString(department)); list.add(s); } } } return list; }技巧说明dormTable.convertRowIndexToModel(viewRow)是关键Swing 排序后视图行号与模型行号不一致必须转换JOIN查询比两次SELECT更高效也教学生理解关联查询价值。教学价值把“数据库关系”真正映射到“界面交互”上学生一眼看懂“楼号-房间号”如何拉出学生列表。我带过的某高校课程设计班有学生坚持用ArrayList存所有学生再stream().filter()实现搜索结果数据量超 200 行就卡顿。当他第一次看到RowFilter.regexFilter毫秒响应又看到JOIN查询联动宿舍与学生时那种“原来数据库不是摆设”的眼神就是这门课最值得的时刻。技术没有新旧只有是否匹配场景。MySQL Java Swing 这个组合在教学闭环里依然锋利如初——它不遮掩任何一层把连接、SQL、事件、渲染全摊开给你看。希望帮到你。本文还有配套的精品资源点击获取