JavaFX + MySQL 图书管理系统:桌面应用开发与数据库事务实战

发布时间:2026/9/10 4:09:14
JavaFX + MySQL 图书管理系统:桌面应用开发与数据库事务实战 简介一份使用JavaFX与MySQL构建的图书管理系统完整源码面向正在学习桌面应用开发、Java GUI编程或数据库连接技术的初学者。项目以FXML实现界面布局通过JDBC完成与MySQL的数据交互清晰展示MVC分层设计读者可在该基础上扩展借阅、分类检索等业务功能。压缩包共98个文件包含24个fxml界面文件、22个java源文件、22个class编译文件、8个xml配置、6个png图标、5个jar依赖库等fxml负责界面布局java对应业务逻辑class为编译输出jar汇聚第三方依赖整体9.38MB目录归类清晰。目前已有299人学习适合从零搭建图书管理项目也可用于课程设计逆向分析JavaFX事件处理、SQL增删改查与连接管理。通过阅读源码可以掌握JavaFX控件的实际运用、数据库表设计与连接池思路是一部兼顾理论参考与工程实践的入门案例能帮助读者形成界面层与数据层分离的完整项目认知。1. 为什么还需要一套 JavaFX MySQL 的图书管理系统桌面应用很多开发者看到“桌面应用”四个字第一个反应是淘汰。但走进任何一家中型企业的行政部或高校院系的图书室借阅登记往往还留在 Excel 和纸质本子上。原因很简单在一台 Windows 机器上双击启动比维护一套 Web 服务要直接得多。这套标题所代表的源码做的就是把这件小事落地JavaFX 负责界面MySQL 负责持久化中间的 JDBC DAO 层负责查询和事务。它适合三类人做课程设计或毕业设计的学生、需要快速交付内部工具的全栈工程师以及想弄清桌面应用开发技术如何与关系型数据库配合的 Java 开发者。整个项目不复杂但对 JavaFX 生命周期、MySQL 驱动加载、TableView 绑定这三个点都有要求看懂它等于把桌面 CRUD 的常见写法完整过了一遍。2. 项目骨架与选型JavaFX 桌面应用的模块化拆分拿到源码包第一件事不是双击运行而是先看它怎么组织。常见做法是 Maven 或 Gradle 工程JavaFX 代码放src/main/javaFXML 和 CSS 放src/main/resourcesMySQL 访问逻辑集中在 DAO 包里。这个结构本身就是一种 MVC 变体model里的实体类对应 book 表dao里的类写 SQLcontroller里的类处理按钮和表格事件。把这个骨架看懂比看懂任何一行业务代码都重要因为后面所有功能都是在这个分层里加东西。2.1 为什么选 JavaFX 而不是 SwingSwing 不是不能写而是写起来太“平”。Swing 的界面完全靠代码一行行排列组件布局管理器一旦嵌套复杂后续调整间距和字体大小就要翻代码。JavaFX 把界面定义拆到了 FXML 文件里长得像前端模板配合 CSS 可以一套皮肤统管全局。对图书管理系统这种表格密集型应用JavaFX 的TableView比 Swing 的JTable好用得多数据直接绑到ObservableList上列表刷新后表格自动跟着变不需要手动调fireTableDataChanged。另外JavaFX 从 JDK 11 开始独立发布版本节奏更快第三方控件库也更丰富比如借阅统计想画仪表盘时JavaFX Medusa 这类库可以直接挂到 Scene 上。还有一层更实际的考虑JavaFX 的 UI 线程模型非常明确。所有界面节点的读写必须在唯一的 Application Thread 上执行数据库查询如果直接写在按钮事件里窗口就会卡住不动。这个约束反过来逼着你把数据访问逻辑拆到 DAO 层用Task或线程池包一层然后通过Platform.runLater回填结果。对实习生或者刚接触桌面应用开发技术的人来说这比 Swing 那种“怎么都能跑但怎么都乱”的状态更容易写出结构清晰的代码。2.2 源码包里常见的 Maven 目录结构下载到的源码一般会按下面这种方式组织拿到手后先对照一遍缺了哪个目录运行阶段大概率会出对应的问题。book-manager/ ├── pom.xml ├── src/main/java/ │ ├── application/ │ │ ├── Main.java # 启动类继承 Application │ │ ├── LoginController.java # 登录窗口控制器 │ │ └── MainController.java # 主界面控制器 │ ├── model/ │ │ └── Book.java # 图书实体 │ ├── dao/ │ │ ├── DBUtil.java # MySQL 连接工具 │ │ └── BookDAO.java # 图书增删改查 │ └── service/ │ └── BorrowService.java # 借还书事务 └── src/main/resources/ ├── fxml/ │ ├── LoginView.fxml │ └── MainView.fxml ├── css/ │ └── style.css └── application.properties # 数据库连接配置各层职责可以用一张表说清楚。目录/文件职责applicationJavaFX 启动入口负责构建 Stage 和 Scenemodel与数据库表字段对应的实体类包含 JavaFX 属性dao直接操作 MySQL只暴露查询和更新方法service承载借还书这类跨表事务协调多个 DAOresources/fxml界面布局文件由FXMLLoader加载resources/css控件样式统管按钮、表格、输入框外观application.properties存放 JDBC URL、用户名、密码避免硬编码这里有个常见的误用把 SQL 直接写在 Controller 里。图书管理系统的功能少Controller 里写几个 SQL 也能跑但一旦加权限、加统计报表Controller 会迅速膨胀。DAO 层单独存在还有个好处就是后面如果把DriverManager换成连接池调用方一行都不用改。2.3 启动类与 FXML 的加载机制JavaFX 程序的入口类继承Application在start方法里加载 FXML、构建场景、显示窗口。下面这段是一个典型的启动类写法。public class Main extends Application { Override public void start(Stage primaryStage) throws Exception { Parent root FXMLLoader.load( getClass().getResource(/fxml/LoginView.fxml) ); primaryStage.setTitle(图书管理系统); primaryStage.setScene(new Scene(root, 1080, 720)); primaryStage.show(); } public static void main(String[] args) { launch(args); } }FXMLLoader.load会根据 FXML 文件里的fx:controller声明的类名通过反射创建控制器实例并把FXML标注的字段注入进去。按钮上的onAction#handleLogin也是反射调用所以控制器里的方法名必须和 FXML 里拼写一致否则启动阶段直接抛异常。需要提醒的是如果源码里带了module-info.javafxml包必须exports或open出去否则FXMLLoader反射访问不到控制器启动就报模块访问错误。老一点的非模块化项目不会遇到这个问题但 JDK 17 之后新建的项目大多默认模块化这是最常见的启动失败原因之一。3. 先把环境跑通JDK、JavaFX 与 MySQL 8.0 的版本匹配标题里同时出现了 JavaFX 和 MySQL说明这个项目的运行环境有两套一套是 Java 运行时的桌面界面环境一套是数据库服务。新手最容易在这里卡住不是 JavaFX 起不来就是 MySQL 连接报驱动错误。下面按顺序把每一层理顺。3.1 JDK 与 JavaFX 版本对应关系JavaFX 不再随 JDK 发布后版本需要单独对齐。项目 pom.xml 或 build.gradle 里声明的 JavaFX 版本最好和本机 JDK 的主版本一致否则可能出现不兼容警告极端情况下会有类加载错误。JDK 版本对应 JavaFX 版本常见构建工具配置JDK 11JavaFX 11.0.2org.openjfx:javafx-controls:11.0.2JDK 17JavaFX 17.0.2org.openjfx:javafx-controls:17.0.2JDK 21JavaFX 21.0.xorg.openjfx:javafx-controls:21.0.x如果你拿到的是源码而非可执行文件运行方式通常是先编译再启动mvn clean javafx:run这里有一个容易误解的点就算不使用模块化JavaFX 的 jar 包仍然要求启动时带上--module-path参数。Maven 的javafx-maven-plugin会自动处理这些参数但如果你直接java -jar打包出来的 fat jar十有八九会报JavaFX runtime components are missing。常见做法是用jpackage打原生安装包或者制作一个带有启动参数的 bat 脚本java --module-path lib --add-modules javafx.controls,javafx.fxml -jar book-manager.jar--module-path指向 JavaFX 的 jmods 或 jar 目录--add-modules显式声明需要的模块。缺少任何一个参数JavaFX 窗口都不会出现。3.2 MySQL 8.0 安装配置要盯住的连接参数数据库侧建议直接装 MySQL 8.0.x 的稳定版安装包安装时选 Developer Default。安装过程中设置 root 密码后记得确认一下使用的认证插件。MySQL 8.0 默认使用caching_sha2_password如果源码里用的是老版本的 Connector/J 5.x连接时直接报认证失败。所以连接驱动统一用com.mysql.cj.jdbc.Driver对应mysql-connector-j8.0.x。Maven 依赖声明如下dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependencyJDBC URL 建议按下面这种方式写每个参数都有它存在的理由。String url jdbc:mysql://localhost:3306/library ?useUnicodetrue characterEncodingutf8 serverTimezoneAsia/Shanghai useSSLfalse allowPublicKeyRetrievaltrue;useUnicodetrue和characterEncodingutf8保证中文书名和作者名不乱码serverTimezone必须显式指定否则 MySQL 8.0 会报时区错误useSSLfalse是本地开发环境的标准配置不加的话会有大量 SSL 握手警告allowPublicKeyRetrievaltrue配合caching_sha2_password使用不加会在连接阶段卡住或抛异常。这几个参数在 MySQL 8.0 安装配置教程里不算新鲜但排查连接问题时挨个验一遍最有效。3.3 初始化数据库脚本与字段类型项目里一般会带一个sql/init.sql或doc/book_manager.sql直接用 MySQL Workbench 把脚本贴进去执行即可。没有也没关系下面这份建表脚本可以覆盖图书和借阅两个核心业务。CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(200) NOT NULL, author VARCHAR(100) NOT NULL, publisher VARCHAR(100), quantity INT DEFAULT 0, remaining INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_name VARCHAR(50) NOT NULL, borrow_date DATE NOT NULL, return_date DATE, FOREIGN KEY (book_id) REFERENCES book(id) ) ENGINEInnoDB; INSERT INTO book (isbn, title, author, publisher, quantity, remaining) VALUES (9787111213826, Java 编程思想, Bruce Eckel, 机械工业出版社, 5, 5);库和表的字符集用utf8mb4而不是utf8是因为utf8在 MySQL 里最多存 3 字节遇到生僻人名或书名注音符号会直接报Incorrect string value。两张表都用 InnoDB外键约束保证借阅记录不会指向不存在的图书。quantity是馆藏总量remaining是当前可借数量这两个字段的差值就是被借走的本数比每次统计借阅表再换算要快得多。4. 图书管理系统的 DAO 层与 JavaFX 界面事件绑定架构跑通后接下来做的是把 MySQL 数据搬到 JavaFX 表格里。核心是两条线DAO 层封装 SQL界面层只负责绑定和监听。按顺序实现连接工具、查询方法、表格绑定和借还书事务一个最小可用的图书管理系统就立起来了。4.1 连接管理先用 DriverManager 就够了图书管理系统的并发量很低常见做法是直接用DriverManager创建连接不引入连接池。引入 HikariCP 会增加源码理解成本对课设和内部工具来说没有意义。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }注意这个工具类里没有Class.forName(com.mysql.cj.jdbc.Driver)。JDBC 4.0 之后驱动会通过META-INF/services自动注册只要依赖里有mysql-connector-jDriverManager就能找到驱动。之所以很多教程还保留这行代码是为了兼容极端类加载器场景当前这项已经不需要。密码直接写在代码里只适合本地调试。更稳妥的方式是从application.properties读取用Properties.load加载到DBUtil里这样别人拿到源码后只需要改配置文件不需要碰代码。4.2 图书 DAO模糊查询的参数化写法图书管理系统的核心操作是搜书搜书名、搜作者、搜 ISBN。模糊查询看起来简单但很多源码在这里踩了 SQL 注入的坑。public ListBook searchBooks(String keyword) throws SQLException { String sql SELECT id, isbn, title, author, publisher, quantity, remaining FROM book WHERE title LIKE ? OR author LIKE ? OR isbn LIKE ? ORDER BY id DESC; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword.trim() %; ps.setString(1, like); ps.setString(2, like); ps.setString(3, like); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(new Book( rs.getInt(id), rs.getString(isbn), rs.getString(title), rs.getString(author), rs.getString(publisher), rs.getInt(quantity), rs.getInt(remaining) )); } } } return list; }这里用PreparedStatement而不是字符串拼接两个原因一是参数会被驱动转义避免引号破坏 SQL 结构二是同一 SQL 在 MySQL 服务端有缓存概率重复查询更快。LIKE配合%通配符实现两端匹配所以传入的keyword一定要trim()否则搜索框里多一个空格结果就会不一样。try-with-resources 保证Connection、PreparedStatement、ResultSet在方法结束或异常时自动关闭图书系统里这是最简单可靠的资源释放方式。4.3 JavaFX 界面事件绑定TableView 与 PropertyValueFactoryDAO 层把ListBook返回后界面层要做两件事把实体字段映射到表格列把按钮和搜索框绑定到 DAO 方法。FXML private TableViewBook bookTable; FXML private TableColumnBook, String titleCol; FXML private TableColumnBook, String authorCol; FXML private TextField keywordField; private final ObservableListBook tableData FXCollections.observableArrayList(); FXML private void initialize() { titleCol.setCellValueFactory(new PropertyValueFactory(title)); authorCol.setCellValueFactory(new PropertyValueFactory(author)); bookTable.setItems(tableData); loadAllBooks(); } FXML private void onSearch() { tableData.setAll(bookDAO.searchBooks(keywordField.getText())); }PropertyValueFactory通过反射查找Book类里的titleProperty()方法所以实体类里必须定义StringProperty title并生成对应的getTitle和setTitle缺一个都会导致列内容为空。tableData.setAll()是整表替换的推荐方式比反复add再remove少很多中间的界面闪烁JavaFX 会监测ObservableList的变化自动刷新表格。按钮绑定在 FXML 里完成Button text查询 onAction#onSearch /这意味着controller里的方法签名必须是public void onSearch(ActionEvent event)或无参写法方法名要和 FXML 严格一致。最常见的报错是javafx.fxml.LoadException: Error resolving onAction#onSearch优先检查方法名和控制器的完整类名路径。4.4 借书还书的 MySQL 事务代码图书管理系统的重点不在界面而在借书和还书这两个操作必须保持数据一致。借书要完成两件事borrow表插入一条记录同时book表的remaining减一。两个动作必须放在同一个事务里否则要么借阅记录存在但库存没扣要么库存扣了但查不到借给谁。public void borrowBook(int bookId, String readerName) throws SQLException { Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); try { int remaining; try (PreparedStatement ps conn.prepareStatement( SELECT remaining FROM book WHERE id ? FOR UPDATE)) { ps.setInt(1, bookId); try (ResultSet rs ps.executeQuery()) { rs.next(); remaining rs.getInt(1); } } if (remaining 0) { throw new SQLException(该图书已无可借副本); } try (PreparedStatement ps conn.prepareStatement( INSERT INTO borrow(book_id, reader_name, borrow_date) VALUES (?, ?, CURRENT_DATE))) { ps.setInt(1, bookId); ps.setString(2, readerName); ps.executeUpdate(); } try (PreparedStatement ps conn.prepareStatement( UPDATE book SET remaining remaining - 1 WHERE id ?)) { ps.setInt(1, bookId); ps.executeUpdate(); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw new SQLException(借书失败事务已回滚, e); } finally { conn.close(); } }事务从setAutoCommit(false)开始然后先锁住图书行再判断库存。SELECT ... FOR UPDATE是 InnoDB 的行锁两个客户端同时借同一本书时后者必须等前者提交或回滚才能执行避免超借。最后在finally里关闭连接事务的提交状态不会因为连接复用而污染下一次操作。还书逻辑刚好相反borrow表更新return_datebook表remaining加一同样包在事务里。5. 源码排错卡顿、连不上库与验证技巧拿到源码能跑起来只是第一步实际运行中高频出现的问题集中在三个方面。第一个是启动时报驱动类找不到十有八九是mysql-connector-j依赖没有进最终运行的 classpathMaven 项目先执行mvn dependency:tree看依赖是否解析第二个是搜索图书时界面无响应因为 DAO 查询直接跑在 JavaFX Application Thread 上数据量大时先把查询放进TaskVoid回调里用Platform.runLater更新ObservableList第三个是 FXML 加载时报NullPointerException通常是FXML字段的类型和 FXML 控件类型不一致或者字段是private但没加FXML注解。验证功能是否真正可用不要只盯着界面看。数据库侧可以先用命令行确认数据状态。mysql -uroot -p library -e SELECT COUNT(*) FROM book; mysql -uroot -p library -e SELECT * FROM borrow WHERE return_date IS NULL;界面侧如果复现了一个“借书后库存没变”的 bug优先在 DAO 层补一个独立测试入口比如在main方法里直接调用borrowBook(1, 张三)看是否抛异常。排查事务问题时可以在conn.rollback()前后各打一行日志确认回滚真的被执行。最后还有一个容易被忽略的细节项目里的application.properties如果设置了连接超时时间MySQL 的wait_timeout默认八小时早上启动的程序到下午再操作就会报连接已关闭这种情况在getConnection里重新获取即可不要手动缓存Connection跨方法传递。把这几个点检查完这套图书管理系统就能稳定跑完借书、还书、查询和统计的完整流程。本文还有配套的精品资源点击获取