ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

JavaFx+JDBC+MySQL学生宿舍管理系统:桌面端CRUD与事务处理实战

JavaFx+JDBC+MySQL学生宿舍管理系统:桌面端CRUD与事务处理实战 简介这是一套面向高校计算机相关专业学生的JavaFxJDBCMySQL学生宿舍管理系统完整项目适合用作期末大作业、课程设计或毕业设计参考难度适中源码经本地编译可运行评审分达98分内容经助教老师审定。资源包共101个文件约472KB以36个java源文件、28个fxml界面文件、10个xml配置、4个css样式及1个sql脚本为主另含png、jpg图片素材与md说明文档覆盖登录、管理员、学生、宿舍楼等模块的界面与业务逻辑。已有105人学习下载。读者可获得完整可运行的项目源码、数据库脚本与文档说明便于快速理解JavaFx界面布局、JDBC数据访问与MySQL表结构设计对照fxml与css文件掌握界面美化思路并在此基础上进行二次开发或功能扩展是学习桌面端管理系统开发的实用参考。1. JavaFxJDBCMySQL 学生宿舍管理系统一套能跑通的桌面端 CRUD 骨架很多同学做课程设计时第一反应是打开浏览器搜「学生宿舍管理系统源码」下载下来发现要么是 JavaWeb 的 JSP 版本要么是 SpringBoot 前后端分离跟自己要交的「JavaFx 桌面程序」对不上。这套 JavaFxJDBCMySQL 的组合本质上是把宿舍管理拆成三张核心表——学生、宿舍、入住记录用 JavaFx 做界面、JDBC 做数据通道、MySQL 做持久化跑起来就是一个带增删改查和条件筛选的桌面客户端。它适合两类人一是要交 Java 课程设计、需要一套结构清晰能讲明白的源码二是刚学完 JDBC、想找个真实场景把 Connection、PreparedStatement、ResultSet 串起来练手的人。下面我按「环境怎么搭、表怎么建、界面怎么连数据、坑在哪」的顺序把这套东西从零讲透。2. 环境搭建与 MySQL 建库建表从 mysql 安装到 jdbc 连接 mysql2.1 选 MySQL 8.0 还是 5.7驱动包怎么配先说版本选择。热搜里「mysql 5.7.44 安装过程详细」「mysql 安装教程 8.0」都有人搜说明这两个版本都还在用。我的建议是新装就上 MySQL 8.0原因是 8.0 默认字符集是 utf8mb4中文宿舍名、学生姓名不会出现乱码而 5.7 默认 latin1建库时不显式指定字符集插入中文直接变问号。如果你学校机房锁死了 5.7也能用但建库语句必须带CHARACTER SET utf8mb4。驱动包这块是新手第一个翻车点。MySQL 8.0 要用mysql-connector-j-8.x.jar驱动类名是com.mysql.cj.jdbc.Driver5.7 用mysql-connector-java-5.1.x.jar类名是com.mysql.jdbc.Driver。两者不能混用用错了会报No suitable driver found。把 jar 放进项目后IDEA 里要右键Add as Library否则编译过、运行时报找不到驱动。连接 URL 也有讲究8.0 必须带时区和 SSL 参数否则控制台会刷一堆警告甚至连接失败# MySQL 8.0 推荐连接串本地开发 jdbc:mysql://localhost:3306/dorm_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueserverTimezone不设会报时区错误useSSLfalse是本地开发关掉 SSL避免「mysql ssl 连接错误」allowPublicKeyRetrievaltrue是 8.0 用 caching_sha2_password 认证时需要的。生产环境别关 SSL但课程设计本地跑关掉最省事。2.2 三张核心表的建表语句与字段设计宿舍管理系统的数据模型不用复杂三张表足够撑起所有功能学生表存人宿舍表存房间入住表把人和房间关联起来。这样设计的好处是学生换宿舍只改入住表不用动学生表符合最基本的范式。-- 建库显式指定字符集避免中文乱码 CREATE DATABASE IF NOT EXISTS dorm_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE dorm_db; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(2) DEFAULT 男 COMMENT 性别, phone VARCHAR(20) COMMENT 联系电话, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 宿舍表 CREATE TABLE dorm ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) NOT NULL UNIQUE COMMENT 房间号, building VARCHAR(20) NOT NULL COMMENT 楼栋, capacity INT DEFAULT 4 COMMENT 可住人数, used 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, check_date DATE DEFAULT (CURRENT_DATE), status TINYINT DEFAULT 1 COMMENT 1在住 0已退, CONSTRAINT fk_stu FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_dorm FOREIGN KEY (dorm_id) REFERENCES dorm(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段设计里有几个点值得说。stu_no加了 UNIQUE防止重复录入学号dorm.used是冗余字段用来快速显示「已住 3/4」不用每次 count 入住表代价是入住退宿时要同步更新它这个后面在事务里处理。checkin.status用 1/0 标记在住和退宿退宿不删记录保留历史这是「mysql update 还原」场景里最实用的做法——数据出问题还能查回退宿前的状态。建完表插几条测试数据方便后面调界面INSERT INTO student (stu_no, name, gender, phone) VALUES (2021001, 张三, 男, 13800000001), (2021002, 李四, 女, 13800000002); INSERT INTO dorm (room_no, building, capacity, used) VALUES (101, A栋, 4, 0), (102, A栋, 4, 0);2.3 用 JDBC 工具类统一管理连接新手最容易犯的错是每个方法里都DriverManager.getConnection用完不关跑几次就报Too many connections。正确做法是抽一个工具类把连接获取和资源关闭统一收口。public class DBUtil { // 8.0 驱动类名5.7 换成 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/dorm_db?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PWD 你的密码; static { try { Class.forName(DRIVER); // 只加载一次 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动未找到检查 jar 是否加入 classpath, e); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 统一关闭顺序ResultSet - Statement - Connection public static void close(ResultSet rs, Statement st, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (st ! null) st.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }Class.forName放在 static 块里只执行一次避免每次连接都反射加载。close方法按 ResultSet、Statement、Connection 的顺序关反了会抛异常。参数说明URL 里的characterEncodingutf8保证 Java 字符串和数据库之间编码一致serverTimezone解决 8.0 的时区报错。这套工具类后面所有 DAO 都复用是整套系统的地基。3. JavaFx 界面与 JDBC 数据打通TableView 绑定与增删改查落地3.1 JavaFx 工程结构与 FXML 布局的最小骨架JavaFx 项目推荐用 FXML 分离界面和逻辑主界面一个MainView.fxml控制器一个MainController.java。Maven 项目里 JavaFx 依赖要显式引入JDK 11 之后 JavaFx 不再随 JDK 发布得单独加dependency groupIdorg.openjfx/groupId artifactIdjavafx-controls/artifactId version17.0.2/version /dependency dependency groupIdorg.openjfx/groupId artifactIdjavafx-fxml/artifactId version17.0.2/version /dependency主界面用BorderPane布局顶部放搜索框和按钮中间放TableView显示学生列表底部放状态栏。TableView的列用TableColumn定义每列通过setCellValueFactory绑定到实体类的属性。这里的关键是实体类属性要用 JavaFx 的Property类型或者用ReadOnlyStringWrapper包装否则表格显示空白。// Student 实体用 Property 支持表格自动刷新 public class Student { private final IntegerProperty id new SimpleIntegerProperty(); private final StringProperty stuNo new SimpleStringProperty(); private final StringProperty name new SimpleStringProperty(); private final StringProperty gender new SimpleStringProperty(); private final StringProperty phone new SimpleStringProperty(); // 省略 getter/setter注意 getter 命名要符合 JavaFx 约定 public StringProperty nameProperty() { return name; } public StringProperty stuNoProperty() { return stuNo; } // ... }nameProperty()这种命名不是随便起的JavaFx 的PropertyValueFactory(name)会去找nameProperty()方法找不到就显示空。这是新手最常见的「表格有行没数据」的原因。3.2 TableView 加载数据的完整链路界面初始化时要把数据库数据灌进表格。链路是initialize()里调 DAO 查全部学生 → 返回ListStudent→tableView.setItems(FXCollections.observableArrayList(list))。DAO 层用 JDBC 查询public ListStudent findAll() { ListStudent list new ArrayList(); String sql SELECT id, stu_no, name, gender, phone FROM student ORDER BY id DESC; try (Connection conn DBUtil.getConn(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStuNo(rs.getString(stu_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setPhone(rs.getString(phone)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; }这里用了 try-with-resources连接、语句、结果集自动关闭比手动 close 更安全。ORDER BY id DESC让最新录入的排最前符合使用习惯。注意rs.getInt(id)用的是列名不是索引列名改了代码不用动但列名拼错会抛异常所以 SQL 里的别名要和 get 的字符串严格一致。控制器里绑定FXML private TableViewStudent stuTable; FXML private TableColumnStudent, String colStuNo; FXML private TableColumnStudent, String colName; FXML public void initialize() { colStuNo.setCellValueFactory(new PropertyValueFactory(stuNo)); colName.setCellValueFactory(new PropertyValueFactory(name)); refreshTable(); } private void refreshTable() { ListStudent list studentDao.findAll(); stuTable.setItems(FXCollections.observableArrayList(list)); }refreshTable()抽成独立方法增删改之后都调它保证界面和数据库一致。这是最朴素也最不容易出错的刷新策略数据量小的时候完全够用。3.3 新增、修改、删除与事务处理新增学生用弹窗表单点保存时收集输入调 DAO 的 insert。这里必须用PreparedStatement而不是字符串拼接 SQL否则姓名里带单引号就报错还有 SQL 注入风险。public boolean insert(Student s) { String sql INSERT INTO student(stu_no, name, gender, phone) VALUES(?,?,?,?); try (Connection conn DBUtil.getConn(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, s.getStuNo()); ps.setString(2, s.getName()); ps.setString(3, s.getGender()); ps.setString(4, s.getPhone()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }参数按?的位置从 1 开始 set顺序不能错。executeUpdate()返回受影响行数大于 0 表示成功。真正需要事务的是「入住」这个操作往 checkin 插一条记录同时把 dorm.used 加一。这两步必须同成功或同失败否则会出现「记录有了但人数没加」的脏数据。JDBC 默认自动提交要手动关掉public boolean checkIn(int studentId, int dormId) { String sql1 INSERT INTO checkin(student_id, dorm_id, status) VALUES(?,?,1); String sql2 UPDATE dorm SET used used 1 WHERE id ? AND used capacity; Connection conn null; try { conn DBUtil.getConn(); conn.setAutoCommit(false); // 开启事务 try (PreparedStatement ps1 conn.prepareStatement(sql1); PreparedStatement ps2 conn.prepareStatement(sql2)) { ps1.setInt(1, studentId); ps1.setInt(2, dormId); ps1.executeUpdate(); ps2.setInt(1, dormId); int rows ps2.executeUpdate(); if (rows 0) { conn.rollback(); // 宿舍满了回滚 return false; } conn.commit(); return true; } } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } }setAutoCommit(false)之后所有操作都在一个事务里commit()才真正落库中途任何异常都rollback()。UPDATE语句里带了used capacity条件宿舍满了受影响行数为 0直接回滚避免超员。这是「mysql 事务处理」在业务里最典型的用法比单纯背 ACID 概念有用得多。4. 避坑与排查那些让系统跑不起来的常见问题4.1 驱动加载失败与连接被拒现象启动就报java.sql.SQLException: No suitable driver found for jdbc:mysql://...。原因通常是 jar 没进 classpath或者驱动类名和版本不匹配。解决确认 IDEA 的Project Structure → Libraries里有 connector jar8.0 用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver。如果报Access denied for user rootlocalhost是密码错了或用户没授权用mysql -u root -p登进去确认密码必要时ALTER USER rootlocalhost IDENTIFIED BY 新密码;。4.2 中文乱码的三层排查现象界面显示「??」或「测试」。原因可能在三层数据库字符集、连接串编码、Java 文件编码。解决顺序先SHOW CREATE DATABASE dorm_db;看是不是 utf8mb4再看连接串有没有characterEncodingutf8最后看 IDEA 的File Encodings是不是 UTF-8。三层都对了才不会乱码。血泪经验是建库时没指定字符集后面改库改表改字段很麻烦不如一开始就写对。4.3 TableView 有行无数据现象表格能查出 5 行但每列都是空白。原因基本是实体类没按 JavaFx 约定写xxxProperty()方法或者PropertyValueFactory里的字符串和属性名大小写不一致。解决检查实体类有没有nameProperty()PropertyValueFactory(name)里的 name 要和属性名完全对应。这个坑很玄学编译不报错运行时静默失败只能靠打印 list 内容确认数据到底有没有查出来。4.4 外键约束导致删除失败现象删除学生时报Cannot delete or update a parent row: a foreign key constraint fails。原因是这个学生在 checkin 表里有入住记录外键挡住了。解决要么先删入住记录再删学生要么把外键改成ON DELETE CASCADE。课程设计里我一般建议先查有没有在住记录有就提示「请先退宿」比级联删除更符合业务逻辑也避免误删。4.5 连接未关闭导致连接数耗尽现象反复操作几次后报Too many connections。原因是每次查询都新建连接却没关。解决统一用DBUtil.close()或 try-with-resources确保 ResultSet、Statement、Connection 都关闭。如果并发量稍大可以引入连接池但课程设计阶段用工具类收口就够了别过早引入 Druid 增加复杂度。5. 进阶技巧用条件查询和分页把系统做得像样基础 CRUD 跑通后答辩时最容易被问的是「数据多了怎么办」。这时候加一个条件查询加分页系统立刻上一个档次。条件查询用动态拼 SQL但参数仍然走PreparedStatementpublic ListStudent search(String keyword, String gender) { StringBuilder sql new StringBuilder( SELECT id, stu_no, name, gender, phone FROM student WHERE 11); ListObject params new ArrayList(); if (keyword ! null !keyword.isEmpty()) { sql.append( AND (stu_no LIKE ? OR name LIKE ?)); params.add(% keyword %); params.add(% keyword %); } if (gender ! null !gender.isEmpty()) { sql.append( AND gender ?); params.add(gender); } sql.append( ORDER BY id DESC LIMIT ?, ?); // 分页参数page 从 0 开始size 每页条数 int page 0, size 10; params.add(page * size); params.add(size); ListStudent list new ArrayList(); try (Connection conn DBUtil.getConn(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStuNo(rs.getString(stu_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setPhone(rs.getString(phone)); list.add(s); } } } catch (SQLException e) { e.printStackTrace(); } return list; }WHERE 11是个小技巧后面每个条件都能无脑AND不用判断是不是第一个条件。params列表按顺序收集参数值最后统一setObject位置和?一一对应。LIMIT ?, ?第一个是偏移量第二个是每页条数MySQL 分页就靠它。注意setObject对字符串和整数都适用省得区分setString和setInt。分页还要配一个 count 查询算总页数SELECT COUNT(*) FROM student WHERE 11 /* 同样的条件 */界面底部放「上一页/下一页」按钮维护一个currentPage变量点一次改一次再刷新表格。这套东西加起来不到一百行但答辩时能讲出「动态条件 参数化查询 分页」三个点比只会增删改查扎实得多。最后说个我自己的习惯每次改完 DAO 方法先在main里单独跑一遍打印结果确认 SQL 没问题再接界面。界面调试成本高数据层先验证能省一半时间。这套 JavaFxJDBCMySQL 的骨架不复杂但把事务、参数化查询、分页这些点吃透换成任何管理系统都能套。希望帮到你。本文还有配套的精品资源点击获取
返回列表