SpringBoot3整合JavaFx与MyBatisPlus:桌面端开发实战指南

发布时间:2026/10/18 18:28:26
SpringBoot3整合JavaFx与MyBatisPlus:桌面端开发实战指南 简介本资源面向具备一定Java基础的开发者提供SpringBoot3整合JavaFx与MyBatisPlus的完整项目实践帮助解决桌面端应用快速搭建、界面与数据层协同开发的问题。项目以SpringBoot3为底层框架借助JavaFx构建跨平台富客户端界面并通过MyBatisPlus简化数据持久化操作适合学习企业级桌面应用架构与前后端一体化开发。压缩包共79个文件约110.76MB包含16个java源码、19个class、12个fxml界面文件、7个xml配置、2个yml配置及sql脚本、css样式、图标与演示视频等覆盖从界面布局到数据访问的完整结构。目前已有2422人学习下载。通过该资源读者可参考项目目录组织与依赖配置理解JavaFx场景启动、MyBatisPlus数据源整合及通用CRUD、分页等用法并借助演示视频与SQL脚本快速运行调试为自身项目开发提供可复用的整合思路。1. 桌面端不想写两套代码SpringBoot3 整合 JavaFx MyBatisPlus 到底能解决什么很多做后台管理系统的团队都遇到过这个场景业务逻辑、权限、数据访问全在 SpringBoot 里跑得好好的突然产品说要做个桌面客户端要求能离线查数据、能连本地数据库、还要跟服务端共用一套实体和 Mapper。这时候如果重新用 Electron 或者 WPF 写一遍等于把 Service 层和 DAO 层全部重写维护成本直接翻倍。SpringBoot3 整合 JavaFx MyBatisPlus 这套组合核心思路就是让 JavaFx 只做界面渲染和事件分发Spring 容器负责 Bean 管理、事务和数据库访问MyBatisPlus 继续干它最擅长的单表 CRUD 和条件构造。适合谁适合已经有 SpringBoot 后端代码、想低成本加一个桌面壳的团队或者做内部工具、数据采集客户端、本地报表工具的开发者。它不是让你抛弃 Web而是让同一套业务代码多一个桌面入口。2. 环境搭建与依赖选型JDK17、SpringBoot3 和 JavaFx 的版本对齐2.1 为什么 JavaFx 必须单独引依赖JDK11 之后 JavaFx 从 JDK 里剥离了所以哪怕你用的是 JDK17也得显式引入javafx-controls、javafx-fxml这些模块。SpringBoot3 要求 JDK17 起步JavaFx 这边建议用 17.0.6 以上的 LTS 版本跟 JDK 大版本对齐避免UnsupportedClassVersionError。常见做法是在pom.xml里用javafx-maven-plugin来管理这样mvn javafx:run能直接启动不用手动配 VM 参数。properties java.version17/java.version javafx.version17.0.6/javafx.version /properties dependencies !-- SpringBoot3 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter/artifactId /dependency !-- MyBatisPlus 对 SpringBoot3 的适配 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency !-- JavaFx 控件与 FXML -- dependency groupIdorg.openjfx/groupId artifactIdjavafx-controls/artifactId version${javafx.version}/version /dependency dependency groupIdorg.openjfx/groupId artifactIdjavafx-fxml/artifactId version${javafx.version}/version /dependency /dependencies这里有个参数要盯住MyBatisPlus 3.5.5 是明确支持 SpringBoot3 的版本低于 3.5.3 的会报NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationProperties这类错。JavaFx 的classifier一般不用手动写javafx-maven-plugin会根据当前操作系统自动拉对应平台的 native 包。2.2 启动类怎么把 Spring 容器和 JavaFx 线程接起来JavaFx 的Application.launch()会阻塞主线程而 SpringBoot 的SpringApplication.run()也要占住主线程。常见做法是让 JavaFx 的Application子类持有 Spring 的ConfigurableApplicationContext在init()里启动 Spring在start()里从容器拿 Controller 或 View 对象。public class Launcher extends Application { private ConfigurableApplicationContext context; Override public void init() { // 启动 Spring 容器注册所有 Bean context SpringApplication.run(SpringBootApp.class); } Override public void start(Stage stage) throws Exception { // 从 Spring 容器获取 FXML 加载器保证 Controller 能被注入 FXMLLoader loader context.getBean(FXMLLoader.class); Parent root loader.load(getClass().getResource(/view/main.fxml)); stage.setScene(new Scene(root, 900, 600)); stage.setTitle(桌面客户端); stage.show(); } Override public void stop() { // 关闭时释放 Spring 容器避免数据库连接池悬挂 context.close(); Platform.exit(); } public static void main(String[] args) { launch(args); } }逻辑说明init()在 JavaFx 启动线程之前执行此时启动 Spring 不会跟 UI 线程抢资源。start()里用context.getBean(FXMLLoader.class)是关键因为默认的new FXMLLoader()不会走 Spring 的依赖注入Controller 里的Autowired全是 null。参数上Scene的宽高按你实际布局调stage.setTitle只是窗口标题不影响功能。3. 把 MyBatisPlus 塞进 JavaFx数据层与 UI 层的解耦写法3.1 Controller 里直接调 Service 还是走事件总线JavaFx 的 FXML Controller 本质上就是个普通 Java 对象只要它被 Spring 管理就能直接Autowired注入 Service。但这里有个坑如果你在 Controller 里直接调userService.list()数据库查询会跑在 JavaFx 的 UI 线程上数据量一大界面就卡死。常见做法是引入一个简单的线程池或者用 JavaFx 的Task把查询包起来。Controller public class UserController { Autowired private UserService userService; FXML private TableViewUser userTable; FXML public void initialize() { // 用 Task 把数据库查询放到后台线程 TaskListUser task new Task() { Override protected ListUser call() { return userService.list(); } }; // 查询成功后回填 UIsetOnSucceeded 保证在 UI 线程执行 task.setOnSucceeded(e - userTable.getItems().setAll(task.getValue())); new Thread(task).start(); } }逻辑说明Task的call()跑在后台线程setOnSucceeded的回调自动切回 JavaFx Application Thread这样既不会卡 UI也不用自己写Platform.runLater()。参数上userService.list()是 MyBatisPlus 的默认全表查询实际项目里要换成lambdaQuery().eq(...).list()并加分页。3.2 MyBatisPlus 分页插件在桌面端的配置差异Web 项目里分页插件通常配在MybatisPlusInterceptor里桌面端一样要配但有个区别桌面端没有 HTTP 请求上下文Page对象的current和size得从 UI 控件手动读。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件指定数据库类型为 MySQL interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }// 在 Controller 里手动构造分页参数 PageUser page new Page(currentPage, pageSize); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), User::getName, keyword); PageUser result userService.page(page, wrapper); userTable.getItems().setAll(result.getRecords());参数说明DbType.MYSQL要跟你实际数据库一致换成DbType.POSTGRE_SQL或DbType.H2都行。currentPage和pageSize一般绑定到 JavaFx 的Pagination控件或两个TextField。注意PaginationInnerInterceptor必须放在其他内部拦截器之前否则分页 SQL 可能不生效。3.3 实体类与表映射的桌面端注意点MyBatisPlus 的TableName、TableId在桌面端和 Web 端没区别但桌面端经常要连本地 SQLite 或 H2这时候主键策略要改。MySQL 用IdType.AUTOSQLite 也用AUTO但 H2 在某些版本下要显式指定IdType.INPUT并手动赋值。另外桌面端如果要做离线缓存实体类里加TableField(exist false)的临时字段别太多否则selectList返回的对象会带一堆 null序列化到本地文件时体积会涨。4. 避坑排查JavaFx 与 SpringBoot3 整合时最容易翻车的五个点4.1 现象启动报 “No toolkit found” 或 “Toolkit not initialized”原因JavaFx 的 native 库没被正确加载常见于用spring-boot:run直接启动而不是javafx:run。SpringBoot 的 Maven 插件不会自动加--module-path和--add-modules。解决要么改用javafx-maven-plugin的javafx:run要么在 IDE 的 VM options 里手动加--module-path /path/to/javafx-sdk/lib --add-modules javafx.controls,javafx.fxml。我一般直接在 pom 里配好插件省得每台机器重新配。4.2 现象Controller 里的Autowired注入为 null原因FXML 文件里用fx:controller指定了 Controller 类JavaFx 默认会用反射 new 一个实例不走 Spring 容器。解决把FXMLLoader交给 Spring 管理用loader.setControllerFactory(context::getBean)或者在FXMLLoader的Bean方法里设置setControllerFactory。这样 FXML 里写的 Controller 类名会被 Spring 解析成 Bean。4.3 现象数据库连接池在窗口关闭后不释放进程残留原因JavaFx 的stop()方法里没有关闭 Spring 容器HikariCP 的线程还在跑。解决在stop()里调context.close()并且加Platform.exit()。如果用了System.exit(0)硬杀连接池可能来不及回滚事务下次启动会看到锁表。4.4 现象MyBatisPlus 的selectById返回 null但数据库里明明有数据原因桌面端连的数据库文件路径是相对路径工作目录变了之后连到了另一个空库。解决在application.yml里把url写成绝对路径或者用System.getProperty(user.dir)拼。另外SQLite 的jdbc:sqlite:./data.db在打包成 exe 后./会变成安装目录不是 jar 所在目录。4.5 现象打包成可执行 jar 后 JavaFx 报 “Missing JavaFX runtime components”原因JavaFx 的 native 库没被打进 fat jar或者Launcher类没有继承Application导致 JavaFx 启动器识别不到。解决用javafx-maven-plugin的jlink目标生成运行时镜像或者用shadow插件时把 JavaFx 的 classifier 依赖也打进去。更稳的做法是直接用jpackage打成 exe/msi把 JDK 运行时一起带上。5. 进阶技巧用 Spring 事件机制做桌面端模块解耦与数据刷新桌面端做到后面最烦的不是 CRUD而是“A 窗口改了数据B 窗口的表格怎么自动刷新”。常见做法是搞一个全局静态的ObservableList但那样模块之间耦合太紧测试也没法写。我一般会用 Spring 的ApplicationEventPublisher配合 JavaFx 的Platform.runLater让数据变更事件在 Spring 容器里广播UI 层按需订阅。先定义一个事件类携带变更的实体类型和操作类型public class DataChangeEvent extends ApplicationEvent { private final Class? entityType; private final String action; // INSERT / UPDATE / DELETE public DataChangeEvent(Object source, Class? entityType, String action) { super(source); this.entityType entityType; this.action action; } // getter 省略 }然后在 Service 层写完数据库操作后发事件Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { Autowired private ApplicationEventPublisher publisher; Override public boolean saveUser(User user) { boolean saved save(user); if (saved) { // 发布事件通知所有订阅者 publisher.publishEvent(new DataChangeEvent(this, User.class, INSERT)); } return saved; } }UI 层用一个 Spring Bean 来订阅收到事件后切回 JavaFx 线程刷新表格Component public class UserTableRefresher { Autowired private UserService userService; private TableViewUser tableView; public void bind(TableViewUser tableView) { this.tableView tableView; } EventListener public void onDataChange(DataChangeEvent event) { if (event.getEntityType() User.class tableView ! null) { // 切回 UI 线程更新控件 Platform.runLater(() - tableView.getItems().setAll(userService.list())); } } }这套写法的好处是Service 层完全不知道 UI 的存在只负责发事件UI 层只订阅自己关心的实体类型新增一个窗口只要加一个EventListener就行。参数上要注意Platform.runLater不能省否则setAll会在非 UI 线程抛IllegalStateException。另外事件是同步发布的如果订阅者里有耗时操作会拖慢 Service 的返回所以订阅者里只做 UI 刷新别塞数据库查询。验证方法很简单开两个窗口一个改数据另一个看表格是否自动变。如果没变先检查EventListener所在类有没有被 Spring 扫描到再检查bind方法有没有在initialize里调用。我踩过的坑是EventListener方法所在的 Bean 如果是Component但没被ComponentScan覆盖事件发了也没人接日志里连报错都没有纯玄学。从那以后我每次加事件订阅都强制在启动日志里打一行“listener registered”确认它真的进了容器。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询