ARTICLE DETAIL

资讯详情

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

MySQL + Java Swing 宿舍管理系统:从建库到退宿的完整实现与避坑指南

MySQL + Java Swing 宿舍管理系统:从建库到退宿的完整实现与避坑指南 简介这是一套面向高校计算机相关专业学生的MySQL课程设计完整方案以Java Swing构建桌面端学生宿舍管理系统适合正在准备课程设计、毕业设计或需要Java数据库综合实战练习的初学者与中级开发者。资源包共334个文件约108.12MB其中47个java源文件承载核心业务逻辑202个class为编译产物另有22个png界面素材、9个xml配置及若干cpp、h等辅助文件并附带db数据库文件与jar依赖开箱即可导入运行。内容覆盖管理员管理、宿舍管理、学生信息管理、学生宿舍分配及多角色权限接口等模块目录结构清晰便于按功能拆分阅读与二次开发。目前已有192人学习下载可作为课程设计参考模板帮助读者快速理解Swing界面搭建、JDBC数据库连接与增删改查实现思路节省从零搭建的时间成本。1. 从一份课程设计说起MySQL Java Swing 的宿舍管理系统到底能跑多远每年毕业季总有一批同学在搜索框里敲下「学生宿舍管理系统 源代码」想找一个能跑、能改、能交差的完整项目。这个标题指向的东西很具体用 MySQL 做数据存储用 Java Swing 做桌面端界面实现宿舍分配、学生入住、退宿、报修、来访登记这些日常事务。它适合两类人一是正在做课程设计、需要一套可运行代码和数据库脚本的学生二是刚学完 Java 基础、想找一个带完整 CRUD 和业务逻辑的练手项目的新手。我见过太多人卡在第一步——代码下载了数据库脚本也有但 MySQL 连不上、Swing 界面乱码、JDBC 驱动版本对不上折腾两天就放弃了。这篇笔记就按我实际带人做这类项目的顺序把环境、建库、核心模块、避坑和进阶技巧拆开讲目标是让你照着能跑起来并且知道每一处为什么这么写。2. 环境准备与数据库设计先把地基打对2.1 JDK、MySQL、IDE 的版本选择与安装顺序很多人一上来就打开 IDE 新建项目结果 JDK 版本和 MySQL 驱动不匹配报一堆No suitable driver found。我一般按这个顺序来先装 JDK再装 MySQL最后配 IDE。JDK 选 8 或 11 都行Swing 对高版本 JDK 兼容性没问题但如果你后面要打包成 exeJDK 8 的坑最少。MySQL 用 5.7 或 8.0 都可以8.0 的默认认证插件是caching_sha2_password老版本驱动连不上这是新手翻车最多的地方。安装 MySQL 时记住 root 密码后面 JDBC 连接要用。Windows 下可以用 MSI 安装包Linux 下用apt或yum都行。安装完先验证服务能不能起来# Windows 检查 MySQL 服务状态 net start | findstr MySQL # Linux 检查服务状态 systemctl status mysql # 登录测试 mysql -u root -p如果登录成功说明数据库服务正常。接下来建库建表不要用 Navicat 手动点直接写 SQL 脚本方便版本管理和重复执行。2.2 宿舍管理系统的表结构设计与建库脚本这个系统的核心表不多但关系要理清。学生表、宿舍表、入住记录表、报修表、管理员表五张表就能撑起主要业务。学生和宿舍是多对一入住记录是学生和宿舍的关联表同时记录入住时间、退宿时间。报修表关联学生和宿舍。-- 创建数据库字符集用 utf8mb4避免中文乱码 CREATE DATABASE dormitory_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dormitory_db; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender VARCHAR(4) COMMENT 性别, class_name VARCHAR(50) COMMENT 班级, phone VARCHAR(20) COMMENT 联系电话 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 宿舍表 CREATE TABLE dorm ( id INT PRIMARY KEY AUTO_INCREMENT, dorm_no VARCHAR(10) NOT NULL UNIQUE COMMENT 宿舍号, building VARCHAR(20) COMMENT 楼栋, capacity INT DEFAULT 4 COMMENT 床位容量, occupied INT DEFAULT 0 COMMENT 已住人数 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 入住记录表 CREATE TABLE checkin ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, dorm_id INT NOT NULL, checkin_date DATE, checkout_date DATE, status VARCHAR(10) DEFAULT 在住 COMMENT 在住/已退宿, FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (dorm_id) REFERENCES dorm(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 报修表 CREATE TABLE repair ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, dorm_id INT NOT NULL, content VARCHAR(255), report_date DATE, status VARCHAR(10) DEFAULT 待处理, FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (dorm_id) REFERENCES dorm(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 插入默认管理员密码存明文仅用于课程设计演示 INSERT INTO admin (username, password) VALUES (admin, 123456);建表时几个参数要留意utf8mb4是必须的不然学生姓名里的生僻字会变问号InnoDB引擎支持外键和事务退宿时同时更新入住记录和宿舍已住人数需要事务保证一致性occupied字段是冗余设计避免每次查已住人数都去 count 入住记录但更新时要和入住记录同步这是后面容易出 bug 的点。2.3 JDBC 连接封装与驱动版本匹配Swing 项目里数据库连接不能每次操作都DriverManager.getConnection那样连接数会爆。常见做法是写一个DBUtil工具类用静态方法返回连接或者用简单的连接池。课程设计级别用静态工具类就够了但要注意关闭资源。import java.sql.*; public class DBUtil { // MySQL 8.0 驱动类名是 com.mysql.cj.jdbc.Driver // MySQL 5.x 是 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/dormitory_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }URL 里的参数逐个说useSSLfalse关掉 SSL 警告本地开发不需要加密连接serverTimezoneAsia/Shanghai解决时区报错MySQL 8.0 不配这个会抛The server time zone value异常characterEncodingutf8保证中文不乱码。驱动 jar 包用mysql-connector-java-8.0.x.jar放到项目的lib目录并添加到 classpath。如果你用 Maven加依赖就行但课程设计通常直接拷 jar 包。3. Swing 界面与核心业务模块从登录到退宿的完整链路3.1 登录窗口与主界面布局的代码骨架Swing 的布局管理器是新手最头疼的地方null布局虽然能绝对定位但窗口一拉伸就乱。我一般用BorderLayout做整体中间放JTabbedPane分页每个 Tab 里用GridBagLayout或BoxLayout排表单。登录窗口用JDialog或JFrame都行关键是密码框用JPasswordField登录按钮绑定ActionListener。import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.sql.*; public class LoginFrame extends JFrame { private JTextField userField; private JPasswordField passField; public LoginFrame() { setTitle(宿舍管理系统 - 登录); setSize(350, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 居中显示 JPanel panel new JPanel(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(8, 8, 8, 8); gbc.fill GridBagConstraints.HORIZONTAL; gbc.gridx 0; gbc.gridy 0; panel.add(new JLabel(用户名), gbc); gbc.gridx 1; userField new JTextField(15); panel.add(userField, gbc); gbc.gridx 0; gbc.gridy 1; panel.add(new JLabel(密码), gbc); gbc.gridx 1; passField new JPasswordField(15); panel.add(passField, gbc); gbc.gridx 0; gbc.gridy 2; gbc.gridwidth 2; JButton loginBtn new JButton(登录); loginBtn.addActionListener(this::doLogin); panel.add(loginBtn, gbc); add(panel); setVisible(true); } private void doLogin(ActionEvent e) { String username userField.getText().trim(); String password new String(passField.getPassword()); String sql SELECT * FROM admin 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(); if (rs.next()) { dispose(); // 关闭登录窗 new MainFrame(); // 打开主界面 } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, 数据库连接失败 ex.getMessage()); } } public static void main(String[] args) { // 在事件调度线程中启动 Swing避免线程安全问题 SwingUtilities.invokeLater(LoginFrame::new); } }这里用PreparedStatement而不是Statement一是防 SQL 注入二是参数里有中文时不用手动加引号。SwingUtilities.invokeLater是 Swing 的规矩所有界面创建都放在事件调度线程里不然偶尔会出界面卡死或组件不显示的玄学问题。登录成功后dispose()关掉登录窗再 new 主界面不要用System.exit否则整个程序都退了。3.2 学生信息管理增删改查与表格刷新主界面里学生管理是最典型的 CRUD 模块。左边放JTable显示数据上面放搜索框和按钮下面放表单。表格数据用DefaultTableModel查询后先setRowCount(0)清空再逐行添加不然会重复叠加。import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.sql.*; public class StudentPanel extends JPanel { private JTable table; private DefaultTableModel model; private JTextField searchField; public StudentPanel() { setLayout(new BorderLayout()); // 顶部搜索栏 JPanel top new JPanel(); searchField new JTextField(15); JButton searchBtn new JButton(查询); JButton addBtn new JButton(新增); JButton delBtn new JButton(删除); top.add(new JLabel(学号/姓名)); top.add(searchField); top.add(searchBtn); top.add(addBtn); top.add(delBtn); add(top, BorderLayout.NORTH); // 表格 model new DefaultTableModel(new String[]{ID, 学号, 姓名, 性别, 班级, 电话}, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 表格不可直接编辑 } }; table new JTable(model); add(new JScrollPane(table), BorderLayout.CENTER); searchBtn.addActionListener(e - loadData(searchField.getText().trim())); delBtn.addActionListener(e - deleteSelected()); addBtn.addActionListener(e - showAddDialog()); loadData(); } private void loadData(String keyword) { model.setRowCount(0); String sql SELECT * FROM student WHERE student_no LIKE ? OR name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); ResultSet rs ps.executeQuery(); while (rs.next()) { model.addRow(new Object[]{ rs.getInt(id), rs.getString(student_no), rs.getString(name), rs.getString(gender), rs.getString(class_name), rs.getString(phone) }); } } catch (SQLException ex) { ex.printStackTrace(); } } private void deleteSelected() { int row table.getSelectedRow(); if (row -1) { JOptionPane.showMessageDialog(this, 请先选中一行); return; } int id (int) model.getValueAt(row, 0); int confirm JOptionPane.showConfirmDialog(this, 确定删除该学生, 确认, JOptionPane.YES_NO_OPTION); if (confirm ! JOptionPane.YES_OPTION) return; String sql DELETE FROM student WHERE id?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); ps.executeUpdate(); loadData(searchField.getText().trim()); } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, 删除失败可能该学生有入住记录); } } private void showAddDialog() { // 弹窗表单省略核心是收集输入后执行 INSERT JTextField noField new JTextField(); JTextField nameField new JTextField(); Object[] message {学号, noField, 姓名, nameField}; int option JOptionPane.showConfirmDialog(this, message, 新增学生, JOptionPane.OK_CANCEL_OPTION); if (option JOptionPane.OK_OPTION) { String sql INSERT INTO student (student_no, name) VALUES (?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, noField.getText().trim()); ps.setString(2, nameField.getText().trim()); ps.executeUpdate(); loadData(); } catch (SQLException ex) { ex.printStackTrace(); } } } }DefaultTableModel重写isCellEditable返回 false防止用户直接双击改表格导致数据没进库。删除时先弹确认框这是基本交互。如果学生已经有入住记录外键约束会阻止删除catch 里给个友好提示而不是把堆栈打到控制台就完事。查询用LIKE加%实现模糊匹配参数化查询避免拼接字符串。3.3 宿舍分配与退宿事务处理与并发更新宿舍分配是业务核心。分配时要做三件事检查宿舍是否满员、插入入住记录、更新宿舍已住人数。这三步必须在一个事务里否则可能出现宿舍满了但记录没插进去或者记录插了但人数没加。退宿同理更新记录状态、减少已住人数。public class CheckinService { // 分配宿舍返回 null 表示成功否则返回错误信息 public String assignDorm(int studentId, int dormId) { String checkSql SELECT capacity, occupied FROM dorm WHERE id? FOR UPDATE; String insertSql INSERT INTO checkin (student_id, dorm_id, checkin_date, status) VALUES (?, ?, CURDATE(), 在住); String updateSql UPDATE dorm SET occupied occupied 1 WHERE id?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 锁定宿舍行防止并发超员 try (PreparedStatement ps conn.prepareStatement(checkSql)) { ps.setInt(1, dormId); ResultSet rs ps.executeQuery(); if (!rs.next()) { conn.rollback(); return 宿舍不存在; } int capacity rs.getInt(capacity); int occupied rs.getInt(occupied); if (occupied capacity) { conn.rollback(); return 该宿舍已满员; } } try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, studentId); ps.setInt(2, dormId); ps.executeUpdate(); } try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, dormId); ps.executeUpdate(); } conn.commit(); return null; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return 分配失败 e.getMessage(); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } // 退宿 public String checkout(int checkinId) { String querySql SELECT dorm_id FROM checkin WHERE id? AND status在住; String updateCheckinSql UPDATE checkin SET status已退宿, checkout_dateCURDATE() WHERE id?; String updateDormSql UPDATE dorm SET occupied occupied - 1 WHERE id?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); int dormId; try (PreparedStatement ps conn.prepareStatement(querySql)) { ps.setInt(1, checkinId); ResultSet rs ps.executeQuery(); if (!rs.next()) { conn.rollback(); return 记录不存在或已退宿; } dormId rs.getInt(dorm_id); } try (PreparedStatement ps conn.prepareStatement(updateCheckinSql)) { ps.setInt(1, checkinId); ps.executeUpdate(); } try (PreparedStatement ps conn.prepareStatement(updateDormSql)) { ps.setInt(1, dormId); ps.executeUpdate(); } conn.commit(); return null; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return 退宿失败 e.getMessage(); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }FOR UPDATE是行级锁在事务里锁住宿舍记录防止两个管理员同时给同一个宿舍分配导致超员。setAutoCommit(false)开启事务所有操作成功后commit任何一步失败就rollback。finally 里恢复自动提交并关闭连接避免连接池里的连接状态被污染。退宿时先查记录状态只有「在住」的才能退防止重复退宿把人数减成负数。4. 避坑与排查那些让你熬夜的报错和乱码4.1 数据库连接失败从 error 2002 到 SSL 警告现象运行程序报Communications link failure或error 2002 (HY000): Cant connect to local MySQL server through socket。原因通常是 MySQL 服务没启动或者 JDBC URL 里的端口不对。解决先net start mysql或systemctl start mysql再用mysql -u root -p确认能登录。如果报 SSL 警告在 URL 后面加useSSLfalse。如果报时区错误加serverTimezoneAsia/Shanghai。还有一个隐蔽的坑MySQL 8.0 默认认证插件是caching_sha2_password老版本驱动连不上要么换 8.0 的驱动 jar要么改用户认证方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;。4.2 中文乱码数据库、连接、Swing 三处都要查现象学生姓名显示成问号或乱码。原因可能在三处数据库字符集不是 utf8mb4、JDBC URL 没加 characterEncoding、Swing 组件字体不支持中文。解决建库时指定DEFAULT CHARACTER SET utf8mb4JDBC URL 加characterEncodingutf8Swing 里如果中文显示成方块设置全局字体UIManager.put(Label.font, new Font(微软雅黑, Font.PLAIN, 14));。Linux 下如果没有微软雅黑用WenQuanYi Micro Hei或Noto Sans CJK SC。4.3 表格数据重复叠加setRowCount(0) 的位置现象每次查询后表格数据翻倍。原因DefaultTableModel是累加的查询前没清空。解决在loadData方法开头调用model.setRowCount(0)再执行查询和添加。注意setRowCount(0)要放在查询之前不要放在while循环里否则每次添加一行就清空一次最后只剩最后一行。4.4 外键约束导致删除失败先查关联再删现象删除学生时报Cannot delete or update a parent row: a foreign key constraint fails。原因该学生在checkin或repair表里有记录外键阻止删除。解决要么先删关联记录要么在删除前查询并提示用户「该学生有入住记录请先退宿」。课程设计里更稳妥的做法是逻辑删除加is_deleted字段而不是物理删除。4.5 界面卡死数据库操作别放在事件调度线程现象点击查询按钮后界面卡住几秒按钮点不动。原因Swing 的事件调度线程被 JDBC 阻塞了。解决把数据库操作放到SwingWorker的doInBackground里完成后在done方法里更新表格。课程设计数据量小偶尔卡一下能忍但如果要做得像样SwingWorker是必须掌握的。5. 进阶技巧让课程设计多拿几分如果你已经把基本功能跑通了想让这个项目在答辩时更有说服力有几个方向可以加。第一把密码明文存储改成 MD5 或 SHA-256 加盐哈希登录时比对哈希值这是安全意识的体现。第二给表格加排序和过滤功能TableRowSorter一行代码就能实现点击表头排序。第三导出 Excel用 Apache POI 把学生列表写成.xlsx答辩时演示「一键导出」很加分。第四加一个简单的日志记录用java.util.logging把登录、分配、退宿操作写到文件里出问题能追溯。// 表格排序示例 TableRowSorterDefaultTableModel sorter new TableRowSorter(model); table.setRowSorter(sorter); // 密码哈希示例 import java.security.MessageDigest; public static String sha256(String input) throws Exception { MessageDigest md MessageDigest.getInstance(SHA-256); byte[] hash md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : hash) { sb.append(String.format(%02x, b)); } return sb.toString(); }我自己的习惯是每做完一个模块就手动跑一遍完整流程新增学生、分配宿舍、退宿、删除学生看数据对不对、界面刷不刷新、异常有没有提示。不要等全部写完再测那时候报错堆在一起排查成本翻倍。数据库脚本和代码分开管理脚本放sql目录每次改表结构就加一个alter脚本别直接改原始建表语句不然别人拿到你的项目不知道执行哪个版本。希望帮到你。本文还有配套的精品资源点击获取
返回列表