☰
JavaFx+JDBC+MySQL学生宿舍管理系统:桌面端CRUD与事务处理实战
2026/10/8 13:53:40 网站建设 项目流程

简介:这是一套面向高校计算机相关专业学生的JavaFx+JDBC+MySQL学生宿舍管理系统完整项目,适合用作期末大作业、课程设计或毕业设计参考,难度适中,源码经本地编译可运行,评审分达98分,内容经助教老师审定。资源包共101个文件,约472KB,以36个java源文件、28个fxml界面文件、10个xml配置、4个css样式及1个sql脚本为主,另含png、jpg图片素材与md说明文档,覆盖登录、管理员、学生、宿舍楼等模块的界面与业务逻辑。已有105人学习下载。读者可获得完整可运行的项目源码、数据库脚本与文档说明,便于快速理解JavaFx界面布局、JDBC数据访问与MySQL表结构设计,对照fxml与css文件掌握界面美化思路,并在此基础上进行二次开发或功能扩展,是学习桌面端管理系统开发的实用参考。

1. JavaFx+JDBC+MySQL 学生宿舍管理系统:一套能跑通的桌面端 CRUD 骨架

很多同学做课程设计时,第一反应是打开浏览器搜「学生宿舍管理系统源码」,下载下来发现要么是 JavaWeb 的 JSP 版本,要么是 SpringBoot 前后端分离,跟自己要交的「JavaFx 桌面程序」对不上。这套 JavaFx+JDBC+MySQL 的组合,本质上是把宿舍管理拆成三张核心表——学生、宿舍、入住记录,用 JavaFx 做界面、JDBC 做数据通道、MySQL 做持久化,跑起来就是一个带增删改查和条件筛选的桌面客户端。它适合两类人:一是要交 Java 课程设计、需要一套结构清晰能讲明白的源码;二是刚学完 JDBC、想找个真实场景把 Connection、PreparedStatement、ResultSet 串起来练手的人。下面我按「环境怎么搭、表怎么建、界面怎么连数据、坑在哪」的顺序,把这套东西从零讲透。

2. 环境搭建与 MySQL 建库建表:从 mysql 安装到 jdbc 连接 mysql

2.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.Driver;5.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?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true

serverTimezone不设会报时区错误;useSSL=false是本地开发关掉 SSL,避免「mysql ssl 连接错误」;allowPublicKeyRetrieval=true是 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 宿舍表 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 '已住人数' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 入住记录表,关联学生和宿舍 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字段设计里有几个点值得说。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?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"; 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 里的characterEncoding=utf8保证 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> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>17.0.2</version> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>17.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 查全部学生 → 返回List<Student>→tableView.setItems(FXCollections.observableArrayList(list))。DAO 层用 JDBC 查询:

public List<Student> findAll() { List<Student> 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 TableView<Student> stuTable; @FXML private TableColumn<Student, String> colStuNo; @FXML private TableColumn<Student, String> colName; @FXML public void initialize() { colStuNo.setCellValueFactory(new PropertyValueFactory<>("stuNo")); colName.setCellValueFactory(new PropertyValueFactory<>("name")); refreshTable(); } private void refreshTable() { List<Student> 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 jar;8.0 用com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver。如果报Access denied for user 'root'@'localhost',是密码错了或用户没授权,用mysql -u root -p登进去确认密码,必要时ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';。

4.2 中文乱码的三层排查

现象:界面显示「??」或「测试」。原因可能在三层:数据库字符集、连接串编码、Java 文件编码。解决顺序:先SHOW CREATE DATABASE dorm_db;看是不是 utf8mb4;再看连接串有没有characterEncoding=utf8;最后看 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,但参数仍然走PreparedStatement:

public List<Student> search(String keyword, String gender) { StringBuilder sql = new StringBuilder( "SELECT id, stu_no, name, gender, phone FROM student WHERE 1=1"); List<Object> 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); List<Student> 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 1=1是个小技巧,后面每个条件都能无脑AND,不用判断是不是第一个条件。params列表按顺序收集参数值,最后统一setObject,位置和?一一对应。LIMIT ?, ?第一个是偏移量,第二个是每页条数,MySQL 分页就靠它。注意setObject对字符串和整数都适用,省得区分setString和setInt。

分页还要配一个 count 查询算总页数:

SELECT COUNT(*) FROM student WHERE 1=1 /* 同样的条件 */

界面底部放「上一页/下一页」按钮,维护一个currentPage变量,点一次改一次再刷新表格。这套东西加起来不到一百行,但答辩时能讲出「动态条件 + 参数化查询 + 分页」三个点,比只会增删改查扎实得多。

最后说个我自己的习惯:每次改完 DAO 方法,先在main里单独跑一遍打印结果,确认 SQL 没问题再接界面。界面调试成本高,数据层先验证能省一半时间。这套 JavaFx+JDBC+MySQL 的骨架不复杂,但把事务、参数化查询、分页这些点吃透,换成任何管理系统都能套。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询