ARTICLE DETAIL

资讯详情

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

Spring Boot 3 整合 MyBatis Plus 报错:ddlApplicationRunner 类型不匹配排查与解决

Spring Boot 3 整合 MyBatis Plus 报错:ddlApplicationRunner 类型不匹配排查与解决 Spring Boot 3 整合 MyBatis Plus 时遇到ddlApplicationRunner类型不匹配的报错这几年几乎成了项目升级路上的固定节目。最近我这边连续好几个项目组的同事都跑来问同一个截图代码看着没毛病启动却直接失败异常信息全在 Bean 类型上打转。这篇文章就专门把这个报错讲透——它到底怎么冒出来的背后是哪两个框架在“抢地盘”以及我实际用过的几种处理方案适合正在做 Spring Boot 3 升级、或者已经升级完但启动失败的 Java 后端开发。先把报错完整写出来方便你对着排查。完整信息一般是下面这种样子Bean named ddlApplicationRunner is expected to be of type org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDatabaseInitializerDetector but was actually of type com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration$DdlApplicationRunner不同 Spring Boot 小版本里expected type 那一串可能略有差异但结构完全一样。核心就三句话容器里有一个叫ddlApplicationRunner的 BeanSpring Boot 启动时按类型找它找到的类型不对两边都有话要说干脆把启动流程拦住。下面按照我实际排查的顺序一层层拆开讲。1. 先处理一次完整的报错现场1.1 报错什么时候出现我这边遇到的情况是在 Spring Boot 3.1.0 MyBatis Plus 3.5.3.1 的组合下出现的也有同事在 Spring Boot 3.0.x 上碰到过。报错发生的时间点很统一应用启动阶段Spring 容器开始实例化 Bean、填充属性、执行ApplicationRunner前后。控制台会刷一大片红色异常应用进程直接退出连端口都来不及监听。这个报错的触发条件有三个项目确实用了 Spring Boot 3.x项目里引入了mybatis-plus-boot-starter或者mybatis-plus的旧版本3.5.3.1 及以下没有手动排除相关自动配置。只要三条同时满足基本百分百复现。我一开始还以为是团队里有人动了公共依赖后来查了 Git 历史才发现只是某次升级 Spring Boot 主版本的时候顺手把 MyBatis Plus 也升级到了当时最新的 3.5.3.1结果两个框架的自动配置类就撞上了。1.2 报错信息里藏着的两条关键线索这条报错看起来长拆开就两句重点。线索一Bean named ddlApplicationRunner。这说明冲突的核心是一个叫ddlApplicationRunner的 Bean。在 Spring IoC 容器中同一时刻同一容器里 Bean 名字是唯一的出现同名 Bean 就意味着至少有两位“选手”都注册了这个名字。线索二expected to be of type ...和but was actually of type ...。expected 类型来自 Spring Boot 自己的org.springframework.boot.sql.init包actual 类型来自com.baomidou.mybatisplus.autoconfigure包。这两个包名已经说明一切Spring Boot 和 MyBatis Plus 各自实现了一个功能相似、名字相同的 Runner注册到容器后互相覆盖最后 Spring Boot 按自己的类型去查发现捡到的是 MyBatis Plus 的类类型不匹配直接抛错。用一个不严谨但好懂的类比小区里两家人给孩子起名都叫“阿强”物业系统里只允许保留一个“阿强”。后来物业按身份证号找人发现登记的“阿强”是隔壁老王家的孩子不是自己预期的那位于是把整个寻人流程叫停了。提示报错开头如果还跟着Consider marking one of the beans as Primary这类建议那是 Spring 在告诉你“有多个同类 Bean 我不知道怎么选”。这里的场景虽然有点像但根因不是多个候选 Bean而是同名 Bean 的类型被覆盖别被引导词带到沟里去。2. ddlApplicationRunner 类型不匹配根因到底在哪2.1 Spring Boot 3 里冒出来的新“玩家”Spring Boot 3 在数据源初始化上做了一件正事把schema.sql、data.sql这类 SQL 脚本的加载逻辑统一收口到spring.sql.init机制里。对应的自动配置类是SqlInitializationAutoConfiguration它里面会注册一个内部类DdlApplicationRunner作用是等数据源初始化器准备好之后触发执行数据库 DDL 脚本比如CREATE TABLE、ALTER TABLE。这个 Runner 的职责很简单启动时检查项目里有没有配置数据库初始化脚本有就执行。在 Spring Boot 2.x 时期这套机制也存在但命名和实现方式没有跟第三方框架产生冲突。到了 Spring Boot 3官方把数据库初始化这块重构了一遍并且加入了一些类型探测组件专门用来嗅探容器里到底有没有对应类型的初始化执行器。于是ddlApplicationRunner这个名字正式成为 Spring Boot 官方“注册商标”。2.2 MyBatis Plus 为什么也要注册同名 BeanMyBatis Plus 作为一个增强 ORM 框架它也有一套自己的 DDL 功能比如在启动时根据实体类信息或指定脚本维护表结构。这套功能内部需要一个执行器而 MyBatis Plus 的自动配置类MybatisPlusAutoConfiguration里恰好也有一个内部类叫DdlApplicationRunner并且它的Bean方法返回的 Bean 名也叫ddlApplicationRunner。在 Spring Boot 2.x 时代官方没有同名的 RunnerMyBatis Plus 内部想怎么起名都没人管。但到了 Spring Boot 3尤其是 3.1 之后官方也用了这个名字两边就在同一个容器里打起来了。2.3 为什么 Spring 会“直接判死刑”这里要多说几句 Spring 容器的工作机制理解了这个以后再遇到类似类型不匹配心里就有底。Spring 启动时每个Bean方法最终都会生成一条BeanDefinition里面带着 beanName、Bean 的类型、初始化方式等信息。当两条BeanDefinition的 beanName 相同Spring 默认会利用后加载的定义覆盖先加载的定义——前提是配置允许覆盖也就是spring.main.allow-bean-definition-overriding为true。Spring Boot 3 里这个开关默认是false所以在很多场景下同名 Bean 会直接报BeanDefinitionOverrideException。但 MyBatis Plus 这个自动配置在注册方式上更“激进”一些有些版本是通过BeanDefinitionRegistryPostProcessor的方式提前往容器里塞了定义绕过了普通的覆盖检查最后还是形成“覆盖后的类型不是预期类型”的尴尬局面。更关键的是Spring Boot 3.1 在数据库初始化上引入了DatabaseInitializationDependencyConfigurer这类“类型探测器”。它会遍历容器里的ApplicationRunner按类型判断哪些是数据库初始化执行器。一旦它发现ddlApplicationRunner的实际类型是 MyBatis Plus 的类而不是 Spring Boot 自己定义的类就直接判定类型不匹配。Spring Boot 3 的启动流程对这类问题非常敏感因为它担心后续数据库脚本初始化顺序被第三方框架破坏所以宁可直接失败也不蒙混过关。我个人觉得这本质上不是“谁对谁错”的问题而是版本演进的时间差问题。Spring Boot 3 先改了命名和类型检测机制MyBatis Plus 旧版本没有同步跟上等新版本 3.5.3.2 出来之后才补上了这块兼容逻辑。3. 标准修复升级 MyBatis Plus 并从 starter 层面换血3.1 版本对照与升级操作官方在 MyBatis Plus 3.5.3.2 版本里专门修复了 Spring Boot 3 的兼容问题并且发布了独立的 startermybatis-plus-spring-boot3-starter。如果你用的是老一套mybatis-plus-boot-starter在 Spring Boot 3 项目里最好直接换成新的 starter别继续混着用。先说 Maven 项目。老配置长这样dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency改成这样dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.3.2/version /dependency注意 artifactId 变长了一截从mybatis-plus-boot-starter变成了mybatis-plus-spring-boot3-starter。如果你暂时只能用artifactIdmybatis-plus-boot-starter/artifactId那也请至少把版本升到3.5.3.2及以上。两个方向都验证过都能让那条ddlApplicationRunner报错消失。Gradle 项目对应改成implementation com.baomidou:mybatis-plus-spring-boot3-starter:3.5.3.2我这里按 3.5.3.2 写是因为这是验证过最稳的起点。如果你对版本没有洁癖直接上 3.5.4、3.5.5、3.5.6 或更新版本也没问题。只是要提醒一点不要一下跳到太新的版本而不看 release notes因为新版本有概率调整某些 API 的包路径或默认行为跟前任代码不兼容。版本对照表我放在下面方便按自己的 Spring Boot 版本选Spring Boot 版本MyBatis Plus 推荐版本备注3.0.x3.5.3.2 及以上旧版 3.5.3.1 及以下会触发 ddlApplicationRunner 冲突3.1.x3.5.3.2 及以上建议 3.5.43.1 和 3.0 在数据库初始化逻辑上有调整3.2.x3.5.5 及以上用新 starter老 starter 大概率还会牵出其它兼容问题3.3.x3.5.9 及以上版本越新对框架内部重构适配越完善提示这个表不是官方强制契约而是我在多个项目里实测得出来的经验值。如果你项目里有自定义的 MyBatis Plus 扩展插件升级前一定要先看一眼插件作者是否跟进了 Boot 3 的兼容别让自定义拦截器变成新的“雷”。3.2 升级后应该如何验证改完依赖、刷新 Maven/Gradle、重启应用这是最基本的动作。但“不报错”不等于“完全正常”我建议按下面三步走一遍第一步确认启动日志里不再有Bean named ddlApplicationRunner相关异常同时观察Started Application正常出现。第二步调用一个简单的 Mapper 方法比如执行selectById或者selectList确认 MyBatis Plus 的 SQL 会话和 Mapper 代理都正常这一步能发现 starter 替换后是否漏了自动配置。第三步如果你之前临时加过排除自动配置的代码例如SpringBootApplication(exclude SqlInitializationAutoConfiguration.class)升级后要记得还原。否则你会发现报错虽然没了但 Spring Boot 的schema.sql初始化功能也被你自己关掉了属于典型的“治标不治本”。3.3 升级 MyBatis Plus 版本后还需要检查什么版本升级不只是删掉一个报错可能顺带调整一些行为尤其要检查这三块。分页插件。Spring Boot 3 项目里分页插件仍需要自己配置MybatisPlusInterceptor代码本身没变化但PaginationInnerInterceptor里的DbType要跟实际数据库对上。比如 MySQL 写DbType.MYSQLPostgreSQL 写DbType.POSTGRE_SQL。写错的情况下分页不会炸但生成的 count 语句和 limit 语法可能不对表现为“总数对但数据不对”或者“第一页正常第二页重复”。公共字段填充和逻辑删除。MetaObjectHandler、TableLogic这些注解和接口在 Boot 3 下依旧可用但如果你升级跨度很大比如从 3.3.x 直接升到 3.5.x注意看MybatisPlusProperties的配置前缀是否变化避免application.yml里写了一堆配置却不生效。代码生成器。很多人用mybatis-plus-generator生成实体和 Mapper升级后生成器的包名和 API 可能调整网上很多教程还停留在旧写法。如果生成结果异常优先去官方文档看对应版本的新 API别拿旧代码硬编。4. 临时降火两种不升级也能跑起来的绕行方案有些项目升级节奏没那么快或者 MyBatis Plus 版本受制于公司中间件团队统一管控短期内升不了。这种时候我试过两种绕行方案先说结论能用但都是“临时降火”不是长久之计。4.1 排除 Spring Boot 的 SqlInitializationAutoConfiguration如果你们项目压根没在用schema.sql、data.sql的启动自动初始化那最常见的做法是直接把 Spring Boot 这边的自动配置排除掉。两边的 Runner 少了一个冲突自然消失。用application.yml配置spring: autoconfigure: exclude: - org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration或者用application.propertiesspring.autoconfigure.excludeorg.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration也可以直接在启动类上排除SpringBootApplication(exclude { org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }这个方案逻辑上说得通Spring Boot 的 SQL 初始化功能对你没用排除掉不心疼。缺点也很明显一旦项目后续需要schema.sql来自动建表这个功能就不会执行了。很多团队后来踩坑就是这里之前手动排除了自动配置过了半年有人加了schema.sql发现启动不建表排查老半天才想到是半年前埋的雷。4.2 保留 Spring Boot 自动配置排除 MyBatis Plus 的 DDL Runner如果你确实需要 Spring Boot 的schema.sql机制那就反过来把 MyBatis Plus 的 DDL Runner 干掉。具体做法是手动写一个配置类把 MyBatis Plus 自动配置里ddlApplicationRunner的存在抹掉同时保留它的其它自动配置能力。不过这个方案在实现细节上比较绕没有spring.autoconfigure.exclude那么好操作。因为你没法单独排除MybatisPlusAutoConfiguration里的某一个Bean方法只能整个自动配置类排除然后手动组装 MyBatis Plus 的基础设施。手写SqlSessionFactory、MapperScannerConfigurer的代码量不小普通项目不划算。我的建议是如果你真的需要 Spring Boot 的数据库脚本初始化能力那就认真把 MyBatis Plus 升级到 3.5.3.2如果没有这个需求直接排除SqlInitializationAutoConfiguration是最省事的临时方案。4.3 千万别靠 allow-bean-definition-overriding 硬扛网上有些帖子会让你把spring.main.allow-bean-definition-overriding设为true说这样就能让 Bean 覆盖正常发生绕过启动报错。我试过在特定版本组合下确实能启动但我不推荐。原因很简单这个开关只是允许 Bean 定义覆盖它不保证覆盖后的类型符合预期。报错虽然没了但容器里最终可能留下的是 MyBatis Plus 的 RunnerSpring Boot 的类型探测组件在后续流程里可能仍然拿不到想要的类型表现为启动成功但数据库脚本初始化不执行、或者某些依赖顺序的初始化器没有触发。这种“半启动”状态比直接报错更难排查因为它不给你一个明确的失败信号所有问题都变成隐性的。提示我见过一个项目为了绕过这个报错开了 Bean 覆盖又加了各种Order注解调顺序最后把启动过程搞得像玄学。升级版本之后删掉这些配置世界立刻清净。所以遇到这类问题第一反应应该永远优先考虑版本兼容而不是调容器开关。5. Spring Boot 3 迁移中其它容易和 MyBatis Plus 一起炸的点5.1 看清楚 starter 的“血统”前面反复强调mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter的区别这里再展开说下为什么重要。Spring Boot 3 最大的底层变化是从javax.*命名空间迁移到jakarta.*命名空间。老版 MyBatis Plus 的 starter 里如果直接依赖了javax.sql、javax.servlet等旧包在 Boot 3 里就会出现“类找得到但类型不兼容”的情况。官方后来专门拆出mybatis-plus-spring-boot3-starter就是为了在依赖层面避开这些坑。很多项目是直接从 Spring Boot 2 的老项目升级过来的pom.xml里还留着旧 starter 的坐标。如果只改 Spring Boot 版本别的没动即使暂时没报ddlApplicationRunner的错大概率也会在 Mapper 扫描或者事务管理器初始化阶段冒出别的幺蛾子。所以升级 Spr 判类型的时候先把 MyBatis Plus 的依赖坐标换成新的。换完坐标以后最好全局搜一下还有没有别的依赖传递依赖到老的mybatis-plus-boot-starter。我曾经排查过一个项目pom.xml里明明只写了新的 starter结果一个公司内部脚手架模块里偷偷引了老的 starter导致依赖树上同时存在两个 MyBatis Plus starter。这种隐蔽冲突IDEA 的 Maven 窗口里一眼看不出来需要展开依赖树搜mybatis-plus关键词。5.2 同类报错速查表Spring Boot 3 MyBatis Plus 迁移过程中除了ddlApplicationRunner下面几个问题也是高频出现的。我整理了一张速查表方便你碰到时快速定位报错现象常见原因解决方向ddlApplicationRunner类型不匹配旧版 MyBatis Plus 与 Boot 3 的 SQL 初始化机制撞车升级到 3.5.3.2或排除SqlInitializationAutoConfiguration分页插件不生效Page对象没分页MybatisPlusInterceptor未注册或DbType配错检查分页拦截器注册、数据库类型配置、拦截器顺序Property sqlSessionFactory or sqlSessionTemplate are requiredstarter 不符MyBatis Plus 自动配置没加载换成mybatis-plus-spring-boot3-starter启动报循环依赖Boot 3 默认禁止循环依赖拦截器和数据源互相引用在拦截器或某个 Bean 上加Lazy打破循环logback-spring.xml里springProfile不生效Boot 3 升级了 Logback 版本检查 Logback 版本对应语法或改用其它方式按环境切配置Mapper 扫描不到Mapper Bean 注入失败启动类MapperScan路径不对或自动配置被排除检查扫描路径查自动配置是否被排除这张表不是全部答案但能覆盖大多数项目升级初期遇到的“启动即炸”问题。排查顺序我建议统一按“依赖坐标 - 自动配置 - 版本兼容”三步走先确认你用的确实是新 starter再看自动配置有没有被手动排除最后看版本是不是太老。绝大多数报错都能落到这三步里。5.3 快速定位“抢地盘”Bean 的实操技巧如果你遇到的报错不是ddlApplicationRunner而是另一个同名 Bean 的类型冲突也可以用这套排查方法。我教一个非常管用的手段在启动类里临时加一个ApplicationRunner把容器里所有 Bean 的名字打出来。Component public class BeanNamePrinter implements ApplicationRunner { Override public void run(ApplicationArguments args) { // 临时打印排查完记得删 String[] beanNames applicationContext.getBeanDefinitionNames(); Arrays.stream(beanNames) .filter(name - name.contains(ddl) || name.contains(Initializer)) .forEach(name - System.out.println( Bean: name)); } }这样你就能看到容器里到底有哪些跟ddl、Initializer相关的 Bean。配合actuator的/actuator/beans端点把同名 Bean 的类型、依赖、来源配置类都拉出来看比瞎猜快得多。另外一个更省事的办法是启动时加--debug参数。Spring Boot 会打印自动配置报告其中条件评估那一段能清楚看到哪些自动配置生效、哪些被排除。之前有同事非说自己没排除过SqlInitializationAutoConfiguration结果一开 debug 发现公司基础包里做了排除当场就定位到了。按照这种排查思路ddlApplicationRunner这类问题其实不难对付。我个人在实操作里的体会是遇到框架间的启动冲突先别急着写 workaround花五分钟看一眼版本矩阵和自动配置来源往往比绕来绕去省下好几个小时。升级 MyBatis Plus 到 3.5.3.2或者换成mybatis-plus-spring-boot3-starter是我验证过最干净、遗留问题最少的路如果因为外部原因暂时升不了再考虑排除自动配置或者调整启动逻辑但一定要在代码注释里标明“临时方案待升级后删除”免得半年后接手的人一头雾水。
返回列表