SpringBoot3整合JavaFx与MyBatisPlus实战:启动配置与分页避坑 📅 发布时间:2026/9/1 8:46:53 👁 浏览次数: 简介一份面向Java开发者的SpringBoot3JavaFxMyBatisPlus整合示例项目适合需要快速构建桌面应用并集成数据库持久层的场景可有效降低配置成本并提升开发效率。压缩包约110.76MB共79个文件涵盖java源码、fxml界面布局、xml映射与配置、yml配置文件、SQL脚本、jar依赖以及class编译产物同时保留完整Maven工程结构便于直接运行、调试或二次开发。该项目已有2415人学习下载实例演示了SpringBoot3自动配置、JavaFx现代化富客户端界面以及MyBatisPlus通用CRUD、条件构造器、分页插件等核心功能的完整整合链路。除代码外包内还提供操作录屏、界面截图和项目配置说明能帮助开发者避开集成过程中的常见坑点适合从入门到进阶的Java开发者作为工程化参考。1. 为什么偏要在桌面上跑一个SpringBoot——这次整合的动机先说结论SpringBoot3整合JavaFx并不是什么炫技而是被业务逼出来的。我手上有一个老旧的桌面客户端数据访问层是手写JDBC界面是JavaFx 8那套老代码。业务越加越多之后问题就非常明显数据库连接没人管事务散落在各个Service里参数校验靠复制粘贴想要加一个简单的分页查询得写小一百行样板代码。最难受的是任何一次数据库结构变更都要手动去改DAO层的SQL和结果集映射改错一个字段名就是运行时异常排查起来特别费劲。后来我决定用SpringBoot3做底座把JavaFx当纯视图层数据访问统一交给MyBatisPlus。这样一来依赖注入、事务管理、连接池、配置中心这些Spring生态的东西全都能用上而JavaFx只需要负责把界面渲染出来就行。整个项目从“一个传统的桌面程序”变成了“一个跑在桌面上的SpringBoot应用”这本质上改变了项目的架构级别。这套整合方案适合谁如果你也在维护JavaFx桌面程序并且遇到下面这些问题数据访问代码混乱、事务管理靠手动、想用MyBatisPlus的代码生成器和分页插件、或者想把Service层抽出来复用给Web端那这篇文章就是写给你的。我会把整个整合过程的依赖选型、启动方式、配置文件、常见坑都讲一遍尽量做到照着走就能跑通。2. 依赖与版本选型SpringBoot3时代的硬性门槛2.1 JDK版本与JavaFx的模块化问题SpringBoot3要求JDK 17起步这是第一个硬门槛。如果你还在用JDK 8那不用往下看了要么升级JDK要么继续用SpringBoot2.x。JDK 11以后JavaFx就从JDK中剥离出来了需要单独引入依赖。这一点很多人第一次接触会踩坑以为引入spring-boot-starter就能自动带上JavaFx实际完全不是一回事。JavaFx现在走的是OpenJFX的独立坐标。dependency groupIdorg.openjfx/groupId artifactIdjavafx-controls/artifactId version17.0.8/version /dependency dependency groupIdorg.openjfx/groupId artifactIdjavafx-fxml/artifactId version17.0.8/version /dependency这里有一个关键点如果项目用了模块化module-info.java那需要在module-info里声明requires javafx.controls和requires javafx.fxml同时还要处理Spring的模块访问问题。我的建议是非必要不要用模块化直接打成普通jar包用Classpath方式运行会省掉很多烦恼。2.2 SpringBoot3与MyBatisPlus的版本对应关系MyBatisPlus从3.5.3开始正式支持SpringBoot3之前版本的mybatis-plus-boot-starter是基于javax包的在SpringBoot3下会直接启动失败报一些关于jakarta包的ClassNotFoundException。SpringBoot3把javax.servlet换成了jakarta.servlet这是个大变化。所以依赖坐标也要换成对应的starterdependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency注意artifactId里的spring-boot3这个不能写错。我见过有人直接把旧项目的mybatis-plus-boot-starter复制过来结果启动时一堆莫名其妙的报错排查半天才发现是依赖坐标不对。数据库驱动方面MySQL 8以上版本建议直接用mysql-connector-j用起来就是这样dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency如果用了Druid连接池也注意要引入适配SpringBoot3的druid-spring-boot-3-starter与旧的druid-spring-boot-starter是两个不同坐标。3. 启动入口设计把JavaFx Application线程与Spring IoC容器桥接起来3.1 最常见的错误写法我见过很多人在JavaFx里用Spring方式是先手动创建Spring上下文然后在Controller里通过ApplicationContext.getBean()去拿Service。这种方式能跑但问题很大Controller和Spring容器耦合严重任何脱离容器的操作都会变成定时炸弹而且你根本没法使用Autowired界面类完全游离在Spring管理之外。如果你只是想“能用”那没问题。但如果想让整个项目结构够干净Controller、Service、Mapper都脱离手动管理标准做法是把JavaFx的Application启动入口做成一个独立的启动类让Spring在Application的init()阶段初始化然后在start()阶段从Spring容器里拿已经管理好的对象。3.2 正确的启动方式SpringBootApplication public class FxLauncher { public static void main(String[] args) { // JavaFx入口不启动Spring容器 Application.launch(FxApplication.class, args); } }public class FxApplication extends Application { private ConfigurableApplicationContext springContext; Override public void init() { // 在JavaFx初始化阶段启动Spring容器 springContext new SpringApplicationBuilder(FxLauncher.class) .headless(false) .run(getParameters().getRaw().toArray(new String[0])); } Override public void start(Stage primaryStage) throws Exception { // 从Spring容器中获取FxmlLoader这样FXML中的Controller才能被Spring管理 SpringFxmlLoader loader springContext.getBean(SpringFxmlLoader.class); Parent root loader.load(/view/MainView.fxml); Scene scene new Scene(root, 1280, 800); primaryStage.setTitle(SpringBoot3 JavaFx MyBatisPlus); primaryStage.setScene(scene); primaryStage.show(); } Override public void stop() { springContext.close(); Platform.exit(); } }这里有个非常重要的细节SpringApplicationBuilder.run()默认会把非Web应用当作无界面应用如果你在JavaFx里启动SpringBoot会有概率出现“HeadlessException”因为你没有图形环境。所以必须加.headless(false)告诉SpringBoot这个世界是有屏幕的。3.3 让FXML的Controller被Spring管理FXML文件里Controller的实例化默认是由JavaFx的FXMLLoader完成的它不认识Spring容器。解决办法是自定义一个loader让FXMLLoader在加载Controller时从Spring容器里查找BeanComponent public class SpringFxmlLoader { Autowired private ApplicationContext context; public Parent load(String fxmlPath) throws IOException { FXMLLoader loader new FXMLLoader(getClass().getResource(fxmlPath)); loader.setControllerFactory(context::getBean); return loader.load(); } }这样写之后FXML里的Controller就可以放心使用Autowired注入Service、Mapper等Spring Bean了。这个ControllerFactory机制是JavaFx专门留出来给外部IoC框架做集成的口子把它接上JavaFx和Spring的整合就算真正完成了。4. MyBatisPlus分页配置与500条限制的真实成因4.1 分页插件的配置方式MyBatisPlus的分页不是默认开启的不配置分页拦截器的话selectPage方法会直接查全表再内存分页这在小数据量下看不出问题一旦表里几万条数据就炸了。配置分页拦截器的方式如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); interceptor.addInnerInterceptor(pagination); return interceptor; } }4.2 单页500条限制到底是怎么回事很多人在线上遇到“MyBatisPlus单页只能查500条”的问题查下一页就报错或者在日志里看到类似“Illegal SQL, the limit is 500”这样的提示。首先要说明MyBatisPlus本身并没有写死500条。这个限制来自PaginationInnerInterceptor的maxLimit属性。当你显式设置这个值或者所在团队的基础架构配置里设置了它那么任何单次查询的pageSize超过这个值都会被拦截器直接拦截。PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L);设置了500之后如果你调用selectPage(new Page(1, 1000), queryWrapper)MyBatisPlus会在SQL执行前把limit改掉或者直接抛异常这是官方给的一种防止手滑查爆数据库的保护机制。所以“500条限制”本质上不是框架缺陷而是一个保护阀。网络热搜里提到的“接触mybatisplus单页500条限制”大概率是两种场景一是项目里有人配置了maxLimit500但后来忘了二是某次版本升级后框架默认行为变化导致分页被限制。如果确实需要放开有两种方式// 方式一Java配置中设为一个更大的值或null pagination.setMaxLimit(10000L); // 方式二YAML中统一配置 mybatis-plus: interceptor: pagination: max-limit: 10000我个人的建议是不要直接改成无限制而是根据业务合理调大。比如报表导出这种场景确实可能单次超过500条但普通列表页真正需要一次显示超过500条的几乎没有。保留这个保护阀至少能让那些写错参数的队友得到一次明确的报错而不是超时。4.3 MyBatisPlus核心配置的YAML写法mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0分页相关的YAML配置注意SpringBoot3 MyBatisPlus 3.5.x版本里分页拦截器还是推荐用Java配置类创建BeanYAML里的interceptor配置在不同版本中支持情况不一致。你在网上搜到的mybatis-plus.interceptor.pagination.max-limit这种配置在3.5.5之前的版本不一定生效。最稳的方式是把分页拦截器写成Bean不要依赖YAML去隐式配置。5. 字段关键字与保留字冲突MyBatisPlus最隐蔽的坑5.1 一个例子引发的血案有一张订单表设计的时候有个字段叫order用来存排序权重。结果用MyBatisPlus查询时生成的SQL是SELECT id, order FROM ...在MySQL里直接报语法错误因为order是保留字。类似的字段还有desc、group、index、key、lock、rank这些都是SQL保留字或MySQL函数名。问题在MyBatisPlus里尤其隐蔽因为实体类字段叫order生成的Mapper方法根本看不出任何问题直到运行时SQL解析报错。5.2 解决方案TableField注解与反引号Data TableName(t_order) public class OrderEntity { TableId(type IdType.AUTO) private Long id; TableField(value order) private Integer order; }关键是TableField(value order)手动给字段加上反引号这样MyBatisPlus生成SQL时会保留这个反引号MySQL就能正确识别列名。5.3 从源头避免关键字问题我踩过这个坑之后的经验是建表时就避开保留字字段命名尽量用业务语义更完整的词。比如order改成sort_orderdesc改成descriptiongroup改成group_code从源头就切断冲突的可能。如果你的项目已经大量存在这种字段还有一种办法在MyBatisPlus全局配置中开启关键字自动转义但这需要手动维护一个关键字列表而且不同数据库的保留字还不完全一样维护成本不低。最推荐的还是改表字段名和实体字段名一劳永逸。5.4 复习一下怎么快速排查是不是关键字问题遇到SQL语法错误时第一步把MyBatisPlus的SQL日志打开mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl然后把打印出来的SQL复制到数据库客户端里直接执行。如果直接在客户端报错就复制SQL字段名看哪些是保留字。这里有个技巧把字段逐个用反引号包起来再执行如果包了某个字段后SQL就正常了那问题就锁定了。6. 整合项目中的其它实战细节6.1 事务管理SpringBoot管理事务在Service方法上加Transactional注解即可这一点在桌面应用里和在Web应用里完全一样。但要注意一个区别JavaFx的界面事件处理线程和Spring事务管理器的线程绑定机制。默认情况下Spring的事务是基于ThreadLocal的同一个事务必须在同一个线程内完成。JavaFx的按钮点击事件运行在JavaFx Application线程上如果在这个线程里直接调用一个带Transactional的Service方法事务是生效的因为Service方法在同一线程内执行。但如果你在事件回调里开了新线程去处理数据比如用CompletableFuture或ThreadPoolExecutor做异步处理那新线程里的事务是独立的不共享外层事务。这是一个非常容易踩的坑。典型场景是点击“批量导出”按钮界面线程提交任务到线程池线程池里的方法使用Transactional结果发现事务根本没生效因为Spring默认只拦截代理对象上的事务方法而从线程池调用的并不是被代理的对象入口。解决办法是让线程池内部的调用直接注入Service代理不要手动new Service。6.2 数据源与连接池配置桌面应用和Web应用的数据库连接模式很不相同。Web应用是高并发、短连接、连接数大桌面应用是单用户、低并发、连接时间短但频率高。所以我建议在桌面端把连接池的maximum-pool-size调小一点比如5到10就够用了。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 3000还有一个细节桌面应用经常需要手动关闭数据库连接。虽然HikariCP会自动管理连接池但如果你在Service里不小心持有Connection对象不释放连接池会被耗尽。MyBatisPlus的Mapper调用本身不需要手动管理连接但如果你混用了JdbcTemplate或原生JDBC务必用try-with-resources保证连接归还。6.3 界面线程与数据库操作的性能考量JavaFx的UI线程不能做耗时操作否则界面卡死。但很多人不知道MyBatisPlus的Mapper查询如果数据量大即使是正常查询也可能占用几百毫秒这在UI线程上就是明显的卡顿。解决方案很简单查询操作放到独立线程查询完成后通过Platform.runLater回到UI线程更新界面。TaskListOrderEntity task new Task() { Override protected ListOrderEntity call() { return orderService.list(new LambdaQueryWrapperOrderEntity().eq(OrderEntity::getStatus, 1)); } }; task.setOnSucceeded(event - { ListOrderEntity result task.getValue(); // 更新TableView }); new Thread(task).start();JavaFx的Task类可以和WritableValue完美配合这是官方推荐的线程方案比直接new Thread更安全它会自动处理异常传递和界面更新。集成SpringBoot之后Task内部可以直接调用注入好的Service完全不影响Spring的代理机制。6.4 打包发布的注意事项桌面应用最终要打成可执行jar或exe这里有个SpringBoot和JavaFx的经典矛盾SpringBoot默认把依赖打进fat jar而JavaFx的native library在fat jar里可能会因为路径问题加载失败。我实测可行的方案是用maven-shade-plugin或者jpackage打包成可执行文件。其中最简单有效的是用jlink和jpackage做自定义运行时这样JavaFx的native库会以模块方式安装不会出现路径问题。如果是团队内部工具不追求一键安装直接用maven打包成fat jar配合java --module-path指定JavaFx模块路径运行也行。注意启动命令要带JavaFx的模块java --module-path /path/to/javafx-sdk-17/lib --add-modules javafx.controls,javafx.fxml -jar app.jar如果打了真机运行的exe用jpackage是正式方案jpackage --name MyApp --input target/ --main-jar app.jar --main-class com.example.FxLauncher --type app-image打包过程中最常遇到的问题是SpringBoot的反射机制无法识别JavaFx的Controller类导致某些场景下Controller实例化失败。解决办法是在SpringBoot启动类里手动扫描或者用ComponentScan显式指定controller包。7. 这套架构后续还能怎么扩展整合完成之后你会发现项目突然有了很多可能性。因为Service层已经完全独立于界面层你可以把同样的Service抽出来作为本地RPC服务或者直接加一个SpringBootWeb模块让同一个服务同时支持桌面端和Web端调用。数据访问层也更规范了代码生成器一键生成Mapper和Entity分页、逻辑删除、自动填充这些需求全部通过配置完成不再需要手写SQL。我在实际开发中体会最深的一点是把SpringBoot引入桌面应用这件事表面上是加了一个框架实际上是改变了自己对桌面项目结构的思考方式。以前写JavaFx打开一个窗口就要想着怎么初始化数据库、怎么管理连接、怎么在多个窗口间共享数据现在这些都变成了Spring的配置问题界面代码只需要关心渲染和事件。如果你正在考虑给自己的JavaFx项目做一次架构升级我建议先拿一个非核心模块试点把启动入口和分页配置跑通再逐步迁移业务代码。这套整合方案的收益不是立竿见影的但一旦跑顺了后续加功能、改表结构、加报表效率提升会非常明显。本文还有配套的精品资源点击获取