解决Spring Boot 3下MyBatis-Plus的ddlApplicationRunner Bean类型报错

发布时间:2026/10/5 15:52:57
解决Spring Boot 3下MyBatis-Plus的ddlApplicationRunner Bean类型报错 Spring Boot 3整合MyBatis-Plus时如果启动日志里出现Bean named ddlApplicationRunner is expected to be of type ...这种Bean类型报错恭喜你遇到了老项目升级时最经典的一个坑。我刚把项目从Spring Boot 2.7升到3.2那会儿也在这上面卡了小半天网上搜到的回答要么让你换starter要么让你删配置但很少有人讲清楚这个ddlApplicationRunner到底是谁注册的、为什么Spring Boot 3才会炸、以及换完依赖之后还会不会有连锁问题。这篇就把我的完整排查过程和解决方案整理出来给正在踩坑的朋友一个能直接落地的参考。1. 报错的完整形态与触发场景1.1 先看清错误日志到底说了什么这类报错通常长这样*************************** APPLICATION FAILED TO START *************************** Description: Bean named ddlApplicationRunner is expected to be of type org.springframework.boot.ApplicationRunner but was actually of type org.springframework.boot.Runner Action: Consider injecting the bean as one of its interfaces or forcing the beans declaring beanFactory to treat them as ApplicationRunner.如果你的日志和我当时看到的基本一致说明项目里至少有两个版本的Runner类同时存在或者某个自动配置类注册ddlApplicationRunner这个Bean时声明的返回类型和实际实例类型对不上。Spring Boot 3的容器会在启动阶段做严格类型校验发现类型不符合预期就直接抛异常阻止启动不像Spring Boot 2.x很多场景下睁一只眼闭一只眼Bean类型不对也只是启动后行为异常不会爆得这么明显。1.2 这个错误通常在什么阶段出现它不是在最开始解析配置文件就报出来的而是在SpringApplication.run()执行到Runner收集阶段才会触发此时Spring容器已经完成了大部分Bean的创建和注入眼看着就要执行CommandLineRunner和ApplicationRunner了结果发现ddlApplicationRunner这个Bean的声明类型和实际类型对不上整个启动流程就崩在这里。我当时项目的情况是客户现场急着要Spring Boot 3.2的适配版本服务里用到了MyBatis-Plus的分页插件和自动填充功能。从2.7升到3.2之后第一轮启动直接撞上这个报错根本没机会验证其他功能。1.3 为什么这个报错容易让人走弯路因为ddlApplicationRunner这个名字太有迷惑性大部分人会第一反应去搜ddl是什么配置然后被带到MyBatis-Plus的DDL自动建表功能里去。这条路不是完全无关但真正的病根往往不在这一层而是在MyBatis-Plus老版starter对Spring Boot 3的兼容问题。另一个容易让人绕路的原因是这个报错在不同版本里的文本细节不一样我看到过有人报expected to be of type org.springframework.boot.ApplicationRunner也有人报反过来的expected to be of type org.springframework.boot.Runner。这两种表述本质是同一类问题容器里某个Bean的名称是ddlApplicationRunner但Spring Boot 3要求的Runner接口类型和你当前实际加载到的接口类型不是同一个或者声明的返回类型与实例类型不一致。2. 根因拆解Runner接口的变化与依赖坐标冲突2.1 ddlApplicationRunner这个Bean是谁注册的这个Bean来自MyBatis-Plus的DDL模块准确说是MybatisPlusDdlAutoConfiguration这类自动配置类注册的。它内部会创建DdlApplicationRunner作用是在应用启动完成后扫描并执行DDL脚本服务于MyBatis-Plus的自动建表、自动加字段功能。你在application.yml里配置了类似mybatis-plus.global-config.db-config.ddl-auto之类的开关时这个Runner就会被激活。问题在于旧版本MyBatis-Plus的starter在注册这个Bean时Runner相关的类型引用写的是老版本形态或者引用的Spring Boot包路径在新版本里已经被调整。Spring Boot 3.0之后整个基础设施做了大改尤其是Spring Framework 6引入了jakarta.*命名空间很多为Spring Boot 2.x设计的自动配置类在新容器下注册Runner Bean时就会出现类型层面的错位。2.2 Spring Boot 3对Runner机制做了什么调整Spring Boot应用启动到最后阶段会从容器里找出所有实现ApplicationRunner和CommandLineRunner接口的Bean逐个执行它们的run方法。这个机制从Spring Boot 1.x就有但Spring Boot 3.2之后对Runner的收集和排序逻辑做了一次重构对Bean声明的类型和它的实际类型之间的一致性检查变得更严格。打个比方Spring Boot 2.x像是学校门口签到的保安你说你是学生他就让你进去哪怕你其实是老师。Spring Boot 3.2之后换了个较真的保安不仅要看你说自己是谁还要掏出证件核对你的实际身份。ddlApplicationRunner这个Bean声明自己是个ApplicationRunner实际拿出来的类却仍然是旧接口形态于是就被拦在了门外。2.3 更深层的原因是依赖坐标用错了绝大多数触发这个报错的项目pom里用的还是这个坐标dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency这个starter是为Spring Boot 2.x设计的虽然也能勉强在Spring Boot 3下被加载但内部的自动配置类沿用旧逻辑Runner类型处理就暴露问题了。Spring Boot 3的项目应该使用的是官方专门适配的坐标dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency如果你把mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter同时引进了pom那更是雪上加霜容器里会出现两套MyBatis-Plus自动配置逻辑Runner Bean可能被重复注册类型冲突直接升级成BeanDefinitionStoreException报错信息就更难看了。我当时检查依赖树第一眼瞄到的就是这个双坐标同时存在的问题直接先干掉了一个。3. 方案一升级到官方适配Spring Boot 3的starter坐标3.1 正确姿势先清理pom里的旧坐标如果你的项目还不需要保留特殊旧依赖最省事的解法就是换坐标加升级版本。先通过Maven的依赖树看一眼当前MyBatis-Plus相关组件都有哪些mvn dependency:tree -Dincludescom.baomidou:*建议把输出结果对一下重点确认以下几点mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter不能同时出现只能留后者mybatis-plus-extension不能被低版本的其它传递依赖拉进来否则还是会出现旧Runner类如果项目里用到分页插件还需要检查jsqlparser的版本是否正常解析到了。3.2 推荐的最低版本组合我这里直接给一套经过实测的组合JDK 17、Spring Boot 3.2.x环境跑通依赖只保留mybatis-plus-spring-boot3-starter版本建议选3.5.5以上我最后用的是3.5.7完全稳定。如果你愿意追一点新版本3.5.9之后的版本也可以但要注意3.5.9把SQL解析相关的依赖做了一次拆分分页插件单独依赖了jsqlparser需要额外引入dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-jsqlparser/artifactId version3.5.9/version /dependency如果你是3.5.9之前的版本不需要第二个依赖jsqlparser会由starter自动传递进来。3.3 替换坐标之后还要检查MapperScan和插件配置换掉starter之后很多项目会发现MapperScan的路径如果没写对Mapper Bean根本扫不进来表现就是启动不报错但一调用Mapper方法就报Invalid bound statement。这不是新问题但如果项目之前是靠自动配置扫描包的升级后建议显式检查MapperScan指向的包路径是否仍然和Mapper接口所在包一致。分页插件那个配置我顺便多说一句Spring Boot 3下要重新注入MybatisPlusInterceptor这个Bean并且在里面加入PaginationInnerInterceptor注意指定数据库类型Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(-1L); interceptor.addInnerInterceptor(pagination); return interceptor; } }这里setMaxLimit(-1L)是解除单页500条限制用的。网上经常看到有人问MyBatis-Plus分页到了第500条之后就查不出来多半就是没关这个默认限制。很多项目把接口升到Boot 3后发现分页行为也变了有一部分原因就在这里。4. 方案二不想换坐标的临时兜底方案4.1 排除掉MyBatis-Plus的自动建表功能如果项目对MyBatis-Plus的DDL自动建表功能没有依赖或者本来就只用了单表CRUD和分页那可以先通过排除自动配置的方式把这个麻烦绕过去。核心是让MybatisPlusDdlAutoConfiguration这个自动配置类不生效ddlApplicationRunner就不会被注册了。一种排除方式是启动类上排除SpringBootApplication(exclude { com.baomidou.mybatisplus.autoconfigure.MybatisPlusDdlAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }另一种是配置文件里排除适合在多个启动类里共用一套配置的情况spring: autoconfigure: exclude: - com.baomidou.mybatisplus.autoconfigure.MybatisPlusDdlAutoConfiguration这样处理的代价是MyBatis-Plus自动建表、字段补齐这类能力就没了。如果你公司项目的建表流程本来就走Flyway或者Liquibase那这个排除没什么损失甚至更符合生产规范。真正依赖MyBatis-Plus自动建表的小型项目我不建议长期停留在这个方案上。4.2 手动注册一个类型正确的同名列覆盖旧Bean还有一个偏hack的做法就是通过自定义配置类注册一个类型完全正确的同名Bean让它把旧自动配置的定义覆盖掉。Spring Boot同一个容器里Bean同名时按后加载的配置为准我们可以利用这一点兜底Configuration public class DdlApplicationRunnerOverrideConfig { Bean public ApplicationRunner ddlApplicationRunner() { return args - { // 这里可以什么都不做或者将旧DdlApplicationRunner的核心逻辑手动执行一遍 log.warn(ddlApplicationRunner overridden by custom ApplicationRunner); }; } }这种做法能让项目先启动起来表面看问题消失了实际隐患也埋下了MyBatis-Plus DDL模块原本的初始化逻辑被完全跳过。如果你还需要TableName标注的实体类自动建表这个方案会让你的表永远建不出来。所以我的判断是只适合临时救火不适合当长期方案。4.3 两种方案怎么取舍我自己的倾向很明确能升级starter就升级starter。临时的绕行方案只能解决眼前启动失败的问题不能解决Boot 3下的兼容性隐患。尤其项目组如果后续还想上MyBatis-Plus的新功能旧坐标迟早要换。如果你在客户现场被压得比较急可以先排除DDL配置让服务跑起来夜里再找窗口处理依赖升级。这种级联问题在Spring Boot升级过程中其实特别常见根因往往不在最表面的报错。所以不管用哪个方案我建议都先留出半天窗口做回归测试重点测分页、多租户、逻辑删除、审计填充这几个高度依赖SQL解析的功能。5. 验证方式与常见的衍生坑5.1 启动验证不要只看能起来换了starter之后启动不报错只是第一步。我建议在项目里临时加一个接口或者测试用例显式输出容器中所有ApplicationRunner和CommandLineRunner的Bean列表确认ddlApplicationRunner的类型已经正确Autowired ApplicationContext context; PostConstruct public void printRunners() { for (String name : context.getBeanNamesForType(ApplicationRunner.class)) { System.out.println(ApplicationRunner: name - context.getBean(name).getClass().getName()); } for (String name : context.getBeanNamesForType(CommandLineRunner.class)) { System.out.println(CommandLineRunner: name - context.getBean(name).getClass().getName()); } }正常情况下列表里不会有ddlApplicationRunner这个名字因为新版本starter已经不再用旧的Runner类型或者干脆把DDL Runner合并到了别的执行链路里。如果这个名字还在且类型是ApplicationRunner也不算错误但你要确认它的实际行为可接受。5.2 分页失效是升级后最隐蔽的坑报错解决后很多项目会踩进下一个坑分页不生效。表现通常是Page对象返回的total是0或者records集合里塞进了全部数据但总条数不对。原因大部分是在Spring Boot 3的环境下MybatisPlusInterceptor没有被正确注册为Bean或者注册了Interceptor但缺少jsqlparser依赖。还有一种情况需要注意3.5.9以后官方做了模块拆分如果项目从3.5.7直接升到3.5.9没补mybatis-plus-jsqlparser那么PaginationInnerInterceptor启动时不会直接报错但运行时无法完成SQL的分页解析表现出的症状非常像分页失效。这种坑不看依赖树是找不到原因的所以我强烈建议升级后第一时间跑一条分页SQL确认。5.3 MySQL 1064等SQL相关报错的关联顺带提一下另一个高频搜索词“mysql1064报错怎么解决”。如果你在分页插件场景下看到1064语法错误先别急着检查SQL语句本身确认一下PaginationInnerInterceptor配置的DbType是否和实际数据库一致。我见过有人连接的是MySQL结果配置里写成了DbType.OTHER分页SQL拼出来之后语法完全不对报错信息像极了手写SQL写错了。这个和当前报错虽然不直接相关但在Spring Boot 3重塑依赖的过程中非常容易一起出现建议排查顺序是先确认分页插件注册再确认DbType最后才去抠具体SQL。5.4 依赖树检查的实操细节用一个命令把MyBatis-Plus家族的全部依赖拉出来看mvn dependency:tree -Dincludescom.baomidou:mybatis-plus*如果输出里同时出现了mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter两兄弟请务必删掉前者。也可以查org.springframework.boot:spring-boot-autoconfigure的版本确认Spring Boot的Runner机制是3.2以前还是以后这对预判是否还会触发类型强校验有帮助。6. 我的实战体会与避坑清单6.1 不同版本组合的横向对照组合启动结果建议Spring Boot 2.7 mybatis-plus-boot-starter 3.5.1正常老项目可以先不动别主动升BootSpring Boot 3.2 mybatis-plus-boot-starter 3.5.3.1大概率触发ddlApplicationRunner类型报错尽快切换到boot3 starterSpring Boot 3.2 mybatis-plus-spring-boot3-starter 3.5.7正常当前最稳的搭配Spring Boot 3.2 mybatis-plus-spring-boot3-starter 3.5.9 mybatis-plus-jsqlparser正常新项目可优先采用这个对照表是我自己项目里实践过的结论不是从某个文档抄来的。Spring Boot 3.3和3.4我也在别的分支上试过只要starter版本够新ddlApplicationRunner这个类型冲突基本不会再出现。6.2 排查顺序建议总结这里我把自己的排查顺序整理了一遍方便你已经改了一半又重新看这篇文章时快速对照先看依赖树确认是不是同时存在两个starter先做减法确认项目Spring Boot版本是不是3.2以上如果是优先考虑换mybatis-plus-spring-boot3-starter如果项目里还有老代码手动注册了Runner类型的Bean检查有没有和ddlApplicationRunner重名启动验证通过后马上测分页、逻辑删除、自动填充三个功能最后再做一轮全量回归重点关注涉及SQL解析的接口。6.3 最后分享两个小技巧第一个是不要一上来就排除SpringAutoConfiguration这种操作虽然能让服务跑起来但随着后续组件增多排除列表会越滚越大最后变成一团乱麻。第二个是我个人比较推荐的一段配置放在application.yml里作为新项目的基线mybatis-plus: global-config: db-config: ddl-auto: none configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplddl-auto设置成none的意义在于让DDL Runner即使存在也不执行建表动作建表的事情交给专门的迁移脚本去管。这样即便下次再出Runner相关的幺蛾子也仅仅是启动日志里多一个无害Bean而不会暗地里把生产库的表结构改了。日志级别的StdOutImpl只在联调阶段开上线前记得换回Slf4jImpl或者直接关掉。这个报错说到底还是给Spring Boot 3适配过程中交的一笔学费。把依赖坐标理顺、理解Runner机制的校验逻辑、顺手确认一下分页插件和新版SQL解析依赖的关系基本上就能把这一串问题从根上解决后面再升级版本也不会慌。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询