十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot 3 与 Mybatis-Plus 兼容升级实战:版本组合与避坑指南

SpringBoot 3 与 Mybatis-Plus 兼容升级实战:版本组合与避坑指南 SpringBoot 3 正式版发布之后我手里好几个原本跑在 SpringBoot 2.7 上的项目都陆续启动了升级计划。本来以为只是改改依赖版本号的事结果第一个项目就卡了整整两天——启动直接报NoClassDefFoundError日志翻到最底下才发现是 Mybatis-Plus 的某个自动配置类引用了已经被移除的javax.*包。那两天我几乎把 Mybatis-Plus 的 GitHub issue 翻了个遍也试了好几个版本的组合最后才把一套稳定的搭配方案跑通。这篇文章就把我踩过的坑、验证过的版本组合、以及升级过程中那些文档里不会写的细节完整地梳理一遍。如果你也正准备把项目迁到 SpringBoot 3或者新项目想直接用 SpringBoot 3 Mybatis-Plus 这套组合那这篇内容应该能帮你省下不少折腾的时间。1. 为什么 SpringBoot 3 会让 Mybatis-Plus 突然水土不服1.1 根因不在 Mybatis-Plus而在 javax 到 jakarta 的整体迁移很多人第一次遇到兼容性问题时第一反应是Mybatis-Plus 是不是不支持 SpringBoot 3其实这个理解是偏的。真正的原因要追溯到 SpringBoot 3 的一个底层决策它把整个生态从 Java EE 的javax.*命名空间迁移到了 Jakarta EE 的jakarta.*命名空间。这个迁移不是 Spring 一家的事而是整个 Java 生态在 Jakarta EE 9 之后的一次大搬家。具体到代码层面以前你写import javax.servlet.http.HttpServletRequest;在 SpringBoot 3 里必须改成import jakarta.servlet.http.HttpServletRequest;。Spring 框架自己完成了这个迁移Servlet 容器Tomcat 10也完成了但第三方库如果没跟上就会在编译期或者运行期直接炸掉。Mybatis-Plus 作为一个重度依赖 Spring 生态的 ORM 增强工具它的自动配置模块、starter 模块里都有对 Servlet API 和 Spring 内部类的引用所以它必须专门发一个适配 SpringBoot 3 的版本。这里有个很容易被忽略的点不是所有 Mybatis-Plus 的 3.5.x 版本都支持 SpringBoot 3。早期的一些 3.5.x 版本虽然版本号看着挺新但内部还是基于javax编译的你把它塞进 SpringBoot 3 项目里编译能过启动就挂。这就是为什么很多人明明升级了版本还是报错。1.2 报错信息背后的真实含义我整理了一下升级过程中最常见的几类报错以及它们各自对应的真实问题报错信息关键词真实原因解决方向NoClassDefFoundError: javax/servlet/...依赖库里还在引用 javax 包换用适配 SpringBoot 3 的 starterClassNotFoundException: javax.annotation.PostConstruct注解包未迁移升级 JDK 到 17 并换 starterNoSuchMethodError: ...SqlSessionFactoryBeanMybatis 核心版本与 MP 不匹配对齐 mybatis-spring 版本Invalid value type for attribute factoryBeanObjectTypeMybatis-spring 版本过低升级 mybatis-spring 到 3.0.x启动卡在DataSource初始化数据源自动配置冲突检查是否重复引入 druid 等其中Invalid value type for attribute factoryBeanObjectType这个报错特别典型我第一次遇到时完全摸不着头脑后来查了半天才知道是mybatis-spring的版本问题。SpringBoot 3 对FactoryBean的处理逻辑变了老版本的mybatis-spring返回的类型不符合新规范所以 Spring 在解析 Bean 定义时直接抛异常。这个坑的隐蔽之处在于报错信息里根本没提 Mybatis只说了factoryBeanObjectType新手很容易往 Spring 本身的方向去查。1.3 版本兼容的本质是一张依赖关系网要彻底搞明白这个问题得先理清这几个组件之间的依赖关系。SpringBoot 3 依赖 Spring Framework 6Spring Framework 6 依赖 JDK 17 和 Jakarta EE 9。Mybatis-Plus 依赖 Mybatis 核心和 mybatis-spring而 mybatis-spring 又依赖 Spring 的SqlSessionFactoryBean等类。所以整条链路是SpringBoot 3 → Spring Framework 6 → mybatis-spring 3.0.x → Mybatis 3.5.x → Mybatis-Plus 3.5.3这条链上任何一环版本不对都会出问题。而且 Mybatis-Plus 自己还分mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter两个 starter前者是给 SpringBoot 2 用的后者才是给 SpringBoot 3 用的。这个区分是很多人踩坑的起点——直接复制老项目的依赖把版本号一改就以为完事了结果引入的还是老 starter。提示判断一个 starter 是否适配 SpringBoot 3最直接的方法是看它的 artifactId 里有没有spring-boot3字样或者去 Maven 中央仓库看它的依赖树里是不是引用了jakarta.*。2. 一套经过实测的稳定版本组合2.1 核心依赖的版本对照表我把几个项目里验证过的版本组合整理成了下面这张表可以直接照着用。这套组合在 JDK 17、JDK 21 上都跑过SpringBoot 3.2.x 和 3.3.x 也都兼容。组件推荐版本说明JDK17 或 21SpringBoot 3 最低要求 17SpringBoot3.2.5 / 3.3.23.2 以上都稳定Mybatis-Plus3.5.5 / 3.5.73.5.3 起正式支持 SB3mybatis-plus-spring-boot3-starter与 MP 同版本注意 artifactId 带 boot3Mybatis3.5.15由 starter 传递引入mybatis-spring3.0.3关键必须 3.0.xDruid可选1.2.20用 druid-spring-boot-3-starter这里要特别强调mybatis-spring的版本。虽然你通常不需要手动声明它因为它会被 starter 传递引入但如果你项目里因为其他依赖比如某些分页插件、多数据源框架间接引入了老版本的mybatis-spring就会覆盖掉 starter 里的版本导致启动报错。这种情况在 Maven 里叫依赖仲裁处理起来需要显式排除老版本。2.2 Maven 依赖该怎么写下面是我现在新项目里用的依赖配置直接贴出来properties java.version17/java.version spring-boot.version3.3.2/spring-boot.version mybatis-plus.version3.5.7/mybatis-plus.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 注意这里是 spring-boot3-starter不是 boot-starter -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies注意 MySQL 驱动这里用的是mysql-connector-j不是老的mysql-connector-java。SpringBoot 3 的依赖管理里已经把驱动换成了新的 groupId 和 artifactId如果你还写老的那个虽然能编译但版本可能对不上运行时会有警告甚至连接失败。2.3 Gradle 项目的写法差异如果你用的是 Gradle配置逻辑一样但要注意 Gradle 的依赖解析机制和 Maven 略有不同。Gradle 默认会选最高版本所以有时候反而不会出现 Maven 那种被老版本覆盖的问题。但反过来如果某个传递依赖被强制指定了低版本Gradle 也可能不报错但运行时出问题。我的建议是在 Gradle 里显式声明mybatis-spring的版本避免不确定性dependencies { implementation org.springframework.boot:spring-boot-starter-web implementation com.baomidou:mybatis-plus-spring-boot3-starter:3.5.7 // 显式锁定避免被其他依赖拉低 implementation org.mybatis:mybatis-spring:3.0.3 runtimeOnly com.mysql:mysql-connector-j }2.4 怎么验证版本组合是否真的生效光看配置文件不够启动起来才知道行不行。我一般会做三步验证看启动日志里 Mybatis-Plus 的 banner。MP 启动时会打印一个版本信息确认它加载的是你期望的版本。写一个最简单的 Mapper 查询比如selectById确认能正常执行。检查依赖树用mvn dependency:tree | grep mybatis看看实际引入的版本确认没有老版本混进来。第三步最容易被跳过但恰恰是最关键的。我遇到过一次配置文件里写的是 3.5.7但依赖树里mybatis-spring是 2.0.7原因是某个分页插件传递引入了老版本。这种情况不查依赖树根本发现不了。3. 升级过程中那些文档不会告诉你的坑3.1 分页插件在 SpringBoot 3 下的配置变化Mybatis-Plus 的分页功能依赖PaginationInnerInterceptor这个类在 SpringBoot 3 下的配置方式和以前基本一样但有一个细节变了拦截器的注册顺序。在 SpringBoot 2 时代很多人习惯把分页拦截器放在最后注册因为 MP 官方文档说多个插件时分页插件要放最后。但在 SpringBoot 3 MP 3.5.5 之后如果你同时用了多租户插件和分页插件顺序不对会导致分页失效而且不报错只是查出来的数据没有分页效果。我当时的配置是这样的Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 多租户插件先加 interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { Override public Expression getTenantId() { return new LongValue(1L); } Override public String getTenantIdColumn() { return tenant_id; } Override public boolean ignoreTable(String tableName) { return sys_user.equals(tableName); } })); // 分页插件后加 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这个顺序问题在文档里其实有提但写得很隐蔽很多人不会注意到。我的经验是只要用了多个 InnerInterceptor就一定要确认分页插件是最后一个否则分页参数会被前面的插件吃掉。3.2 XML 映射文件与 Mapper 接口同目录的配置陷阱热词里提到了xml 与 mapper 在同一个文件夹下应该如何配置这个问题在 SpringBoot 3 下确实有变化。以前在 SpringBoot 2 里很多人习惯把 XML 放在src/main/java下和 Mapper 接口同目录然后通过mybatis-plus.mapper-locations配置扫描路径。但 SpringBoot 3 默认不把src/main/java下的非 Java 文件打包进 classpath所以运行时会报Invalid bound statement (not found)。解决方式有两种。第一种是把 XML 放到src/main/resources下按包路径建目录这是最推荐的做法mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.demo.entity第二种是如果你坚持要放在src/main/java下需要在pom.xml的build里显式配置资源目录build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources /build我个人的建议是直接用第一种把 XML 统一放到resources/mapper下。这样配置简单也不会因为构建工具的差异出问题。同目录的方案虽然看起来整洁但跨构建工具Maven 和 Gradle的行为不一致团队协作时容易出幺蛾子。3.3 逻辑删除字段在 SpringBoot 3 下的行为确认热词里还有一个mybatis-plus 查询 deleted这个涉及逻辑删除的配置。MP 的逻辑删除功能在 SpringBoot 3 下行为没变但有一个细节需要注意如果你在实体类里用了TableLogic注解同时在全局配置里也配了logic-delete-field两者会冲突。SpringBoot 3 下这个冲突的表现是逻辑删除不生效查询时仍然会带出已删除的数据。正确的做法是二选一。要么全局配置mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0要么在实体字段上加注解TableLogic private Integer deleted;不要两个都配。我见过有项目两个都配了结果查询条件里出现了两个deleted 0虽然不影响结果但 SQL 看起来很怪而且一旦两边的值配得不一样就会出现数据错乱。3.4 多数据源场景下的额外注意事项如果你的项目用了多数据源比如 dynamic-datasource升级到 SpringBoot 3 时还要额外注意。dynamic-datasource-spring-boot-starter也需要用适配 SpringBoot 3 的版本目前是 4.3.0 以上。而且多数据源和 MP 的拦截器配合时分页插件要注册在正确的SqlSessionFactory上否则会出现主库分页正常、从库分页失效的诡异现象。这个问题的排查思路是先确认每个数据源对应的SqlSessionFactory是否都注册了拦截器。如果用的是DS注解切换数据源拦截器是全局生效的一般不会有问题但如果是手动配置了多个SqlSessionFactory就需要在每个 factory 上都注册一遍。4. 从 SpringBoot 2 迁移的完整操作清单4.1 迁移前的依赖梳理迁移之前先把现有项目的依赖树导出来重点看这几个mvn dependency:tree -Dincludesorg.mybatis,com.baomidou,org.mybatis.spring.boot把结果里所有和 Mybatis 相关的依赖列出来标记出版本。然后对照前面那张版本表逐个确认是否需要升级。特别要注意那些间接依赖比如某些代码生成器、分页插件、多数据源框架它们可能自己带了老版本的 Mybatis。4.2 代码层面的改动点依赖改完之后代码层面主要有这几处需要动javax 改 jakarta所有import javax.servlet.*、import javax.annotation.*都要改成jakarta.*。IDEA 有批量替换功能但要注意别把javax.sql.DataSource也改了那个还是 javax 的。配置文件里的属性名SpringBoot 3 里有些配置属性改了名比如spring.datasource.url没变但spring.redis.*变成了spring.data.redis.*。MP 相关的配置基本没变。实体类的主键策略MP 3.5.x 里TableId的默认策略变了以前默认是ASSIGN_ID雪花算法现在还是但如果你之前依赖的是数据库自增需要显式写TableId(type IdType.AUTO)。4.3 启动后的验证步骤迁移完成后不要急着上生产按这个顺序验证一遍启动项目确认没有NoClassDefFoundError和NoSuchMethodError。调用一个简单的查询接口确认 MP 能正常执行 SQL。调用一个分页接口确认分页参数生效。调用一个逻辑删除接口确认删除后查询不到数据。如果有 XML 映射确认所有 XML 都被加载看启动日志里有没有Invalid bound statement。这五步走完基本能覆盖 90% 的兼容性问题。剩下的 10% 通常是特定业务场景下的边界问题需要结合具体代码排查。4.4 回滚方案要提前准备升级这种事一定要留后路。我的做法是在 Git 上开一个独立分支做升级主分支保持不动。如果升级过程中遇到短期内解决不了的问题可以随时切回主分支继续开发。另外数据库层面如果有 MP 自动建表或者字段变更的逻辑升级前先备份一份数据避免因为版本差异导致数据被意外修改。5. 几个高频问题的快速排查思路5.1 启动报 factoryBeanObjectType 错误这个错误的根因是mybatis-spring版本低于 3.0.0。排查步骤执行mvn dependency:tree | grep mybatis-spring看实际版本。如果低于 3.0.0找到是哪个依赖引入的用exclusions排除掉。显式声明mybatis-spring3.0.3 或更高版本。5.2 查询报 Invalid bound statement这个错误说明 Mapper 接口和 XML 没有对应上。排查步骤确认mapper-locations配置的路径能匹配到 XML 文件。确认 XML 里的namespace和 Mapper 接口的全限定名一致。确认 XML 里的id和接口方法名一致。如果是 SpringBoot 3确认 XML 在 classpath 里用src/main/resources方案。5.3 分页不生效排查步骤确认PaginationInnerInterceptor已经注册。确认它是最后一个注册的 InnerInterceptor。确认查询方法返回的是IPage类型且传入了Page对象。如果用了多数据源确认每个数据源都注册了拦截器。5.4 逻辑删除失效排查步骤确认TableLogic和全局配置没有同时使用。确认逻辑删除字段的类型和配置的值匹配比如Integer配 0/1Boolean配 true/false。确认查询时没有手动写deleted 0的条件否则会和 MP 自动加的条件冲突。6. 关于版本选择的一点个人经验折腾了这么多项目之后我现在的策略是新项目直接用 SpringBoot 3.3.x Mybatis-Plus 3.5.7 这套组合老项目升级时先升到 3.5.5 验证稳定后再考虑要不要跟到最新版。为什么不直接上最新版因为 MP 的小版本之间偶尔会有行为变化比如某个版本改了分页的默认行为或者改了逻辑删除的 SQL 生成逻辑。对于已经在生产跑的项目稳定比新功能重要。另外我强烈建议在项目里加一个dependencyManagement或者 Gradle 的platform把 Mybatis 相关的版本统一管理起来。这样即使某个传递依赖想引入老版本也会被强制对齐到你指定的版本。这个做法在多人协作的项目里尤其有用能避免我本地能跑你本地报错这种问题。还有一个细节如果你用的是 IDEA升级完依赖后记得刷新 Maven/Gradle并且清一下 IDEA 的缓存File → Invalidate Caches。我有一次改完依赖死活不生效最后发现是 IDEA 缓存了老的依赖树清了缓存才正常。这种问题不常见但遇到了很浪费时间。最后说一个我踩过的坑SpringBoot 3 的spring-boot-maven-plugin在打包时对jakarta相关的类处理更严格如果你项目里有自己写的Filter或者Servlet记得把javax.servlet的 import 全部改掉否则打包能过运行时报ClassNotFoundException。这个坑我在一个老项目上遇到过当时排查了半天才定位到是一个不起眼的Filter类没改干净。
返回列表