"Error creating bean with name 'sqlSessionFactory' defined in class path resource" 这行报错,搞 Java 后端的朋友应该都不陌生。项目一启动,控制台刷出一大片红,顶在最上面的就是这句话,后面还跟着一串 Caused by。很多人第一反应是去搜"sqlSessionFactory 怎么创建失败",但说实话,这行报错的真正答案几乎从来不在这句话本身里,而在它下面那几行 Caused by 里。
我见过不少同事抱着这行报错改了半天配置,结果方向完全跑偏。这篇就把我自己的排查经验完整整理一遍,从这个报错到底是怎么来的讲起,到最常见的几个根因,再到我实际踩过的坑,一步步说清楚。适合正在被这个报错卡住的人,也适合想彻底搞懂 Spring 和 MyBatis 集成原理的人,看完至少能少走半天弯路。
1. 错误定级:先看 Caused by,别盯着第一行
先说一个最基本的判断:Error creating bean with name 'sqlSessionFactory'只是 Spring 容器在创建 Bean 失败时抛出的外层包装。你可以把它理解为"快递包裹外面的破损标签",真正的损坏原因在包裹里面的货物——也就是 Caused by 那部分。
这段报错常见的完整形态是这样:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(...) ... Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/example/demo/mapper/UserMapper.xml at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(...) ...看见没有,BeanCreationException在上,BuilderException和IOException在下。如果只盯着第一行看,你永远不知道为什么失败。真正要定位的,是最底层那个Caused by指向的异常类型和消息。
这一步非常关键,我见过太多人在application.yml里瞎改一个下午,结果问题其实是依赖版本冲突。所以拿到报错的第一步,永远是往下翻,找到最深层那个Caused by,然后对着它去分析。
另外要留意,控制台报错颜色多、日志长,很多人截图只截前几行。如果你是自己排查,最好直接把日志拉到最下面,或者搜索Caused by关键字,从最底部的Caused by开始往上看。这个习惯我保持了快十年,能省掉无数无效排查时间。
2. sqlSessionFactory 到底是什么:理解它为什么在启动阶段就挂
要真正理解这个报错,得先知道sqlSessionFactory这个 Bean 在 Spring 容器里承担什么角色。
在 MyBatis 里,最核心的对象就是SqlSessionFactory,它是一个重量级对象,一个数据库环境对应一个实例,负责创建SqlSession、持有全局配置、维护 Mapper 映射关系。正常情况下,一个应用只创建一个SqlSessionFactory,所以它的初始化质量直接决定整个数据访问层能不能用。
在 Spring 集成 MyBatis 时,SqlSessionFactoryBean实现了 Spring 的FactoryBean和InitializingBean接口。InitializingBean里的afterPropertiesSet()方法会在 Bean 实例化后被容器自动调用,而SqlSessionFactoryBean正是在这个方法里执行了 MyBatis 配置加载的核心逻辑。
换句话说,sqlSessionFactory这个 Bean 一旦被创建,Spring 会立刻去读取 MyBatis 全局配置、解析 Mapper XML、注册类型别名、构建Configuration对象。这个阶段任何一步出错,比如 XML 路径不对、XML 语法有问题、配置项有冲突,都会直接导致afterPropertiesSet()抛出异常,最终被 Spring 包装成你看到的那句Error creating bean with name 'sqlSessionFactory'。
这也是为什么这个报错几乎都发生在项目启动阶段,而不是运行阶段。因为 MyBatis 把大量配置校验工作都放在了初始化时集中完成,越早暴露问题,其实越好排查。
那mybatis-spring-boot-starter的自动配置又做了什么?它会自动注册一个SqlSessionFactoryBean,自动扫描@Mapper注解的接口,并把application.yml里mybatis.*开头的配置项绑定到MybatisProperties,在SqlSessionFactoryBean里统一处理。这个过程中涉及到的配置项非常多,任何一个没配对,都会在创建 Bean 时炸出来。
理解了这一层,你再看报错就不会慌了。它不是某个神秘故障,而是初始化阶段某个配置没满足,Spring 把这个失败告诉了你而已。
3. 六大高频根因:照着这个清单排查
结合我这些年遇到的实际情况,sqlSessionFactory创建失败的原因基本可以归结为六大类。下面逐个拆开讲,每个都给出典型的报错特征和解决思路。
3.1 XML 映射文件路径配置错误(出现率最高)
这绝对是最常见的根因,没有之一。Mapper XML 文件写了,但配置的路径跟实际存放路径对不上,启动时 MyBatis 去指定路径找文件,找不到就直接抛IOException。
典型报错长这样:
Caused by: java.io.IOException: Could not find resource com/example/demo/mapper/UserMapper.xml这里的com/example/demo/mapper/UserMapper.xml是mapper-locations配置里写的路径。如果你的 Mapper XML 放在src/main/resources/mapper/下,但配置写成了classpath:com/example/demo/mapper/*.xml,那肯定找不到。
正确的application.yml配置有两种常见写法:
# 写法一:classpath 前缀 mybatis: mapper-locations: classpath:mapper/*.xml # 写法二:classpath* 前缀,用于多模块场景 mybatis: mapper-locations: classpath*:mapper/**/*.xml这两种写法有什么区别?classpath:只会在当前 classpath 根路径下找一次,适合单模块项目,性能好、逻辑简单;classpath*:会扫描所有 jar 和 classpath 路径下的同名资源,适合多模块项目,尤其是 Mapper XML 放在公共模块或依赖 jar 里的情况。
另外要注意 Ant 风格的通配符。mapper/*.xml只匹配一级目录下的 XML,mapper/**/*.xml会匹配所有子目录下的 XML。如果你的 XML 放在mapper/user/UserMapper.xml,但配置是mapper/*.xml,同样会找不到。
这里有个反直觉的坑:即使 XML 确实存在于 resources 目录下,IDEA 或 Maven 构建时如果没有把 XML 文件复制到 target/classes 里,同样会报找不到资源。因为 Maven 默认只把src/main/resources下的文件复制到 classpath,如果 XML 放在了src/main/java下,就需要在pom.xml里额外配置资源目录。我的建议是 XML 一律放 resources,别跟 Java 源码混在一起,省得踩这种低级坑。
3.2 MyBatis 全局配置文件缺失或路径指错
这个场景出现的频率比第一个低一些,但也不少见。有人在application.yml里配置了mybatis.config-location: classpath:mybatis-config.xml,但 resources 目录下根本没有这个文件,或者文件名拼错了,启动时就会报找不到资源。
典型报错:
Caused by: java.io.IOException: Could not find resource mybatis-config.xml还有另一种情况:同时配置了mybatis.config-location和mybatis.configuration.*属性。在 Spring Boot 2.x 中,这两者是不能同时使用的,会直接抛异常:
Caused by: java.lang.IllegalStateException: Configuration location must be specified or configuration properties but not both.这个错误我印象很深,因为它的提示已经说得很直白了——config-location和configuration属性只能二选一。如果你用config-location指定了独立的mybatis-config.xml,那application.yml里mybatis.configuration.*下的所有配置项都会被忽略,反之亦然。
我个人的习惯是:简单项目直接用mybatis.configuration.*在application.yml里配,不单独建mybatis-config.xml。只有在需要配置一些 YAML 不直接支持的复杂对象,比如自定义ObjectFactory、Interceptor插件时,才用独立的mybatis-config.xml。
3.3 数据源配置缺失或不正确
SqlSessionFactory构建时必须拿到一个DataSource。如果DataSource本身都没配置好,创建SqlSessionFactory时自然失败。
常见两种表现:
第一种,完全没有配置数据源,引入spring-boot-starter-jdbc或 MyBatis Starter 后,Spring Boot 自动配置找不到spring.datasource.url,会报:
Caused by: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.或者 Spring Boot 特有的:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.第二种,数据源配置了但有问题,比如 URL 写错、驱动类加载不到、数据库连不上。这种报错往往更底层,直接暴露数据库连接异常。
排查这类问题时,先把 MyBatis 放一边,单独验证数据源能不能正常获取连接。一种很实用的方式是用 IDEA 的 Database 面板直接填同一套配置测试连通性。如果 IDEA 能连上,Spring Boot 这边还报错,那就是代码配置层面的问题,比如 URL 里的库名错了,或者用户名密码里隐藏了看不见的特殊字符。
另外提醒一个细节:如果是多数据源场景,需要手动定义多个DataSource、SqlSessionFactory和MapperScan。这个时候如果某个SqlSessionFactory依赖于一个没有被正确注入的DataSource,也会出现类似报错。后面我会单独讲多数据源的坑。
3.4 依赖版本冲突(Spring Boot 2.x 和 3.x 最容易踩)
版本问题比较隐蔽,报错信息也五花八门,但有一个共同特征:Caused by指向NoSuchMethodError、ClassNotFoundException、NoClassDefFoundError这类异常。
最常见的就是 Spring Boot 3.x 项目用了mybatis-spring-boot-starter2.x 版本。因为 Spring Boot 3.x 基于 Jakarta EE,包名从javax.*变成了jakarta.*。MyBatis 官方 Starter 从 3.0.0 才开始支持 Spring Boot 3.x。如果用错版本,很可能在启动时直接报:
Caused by: java.lang.NoClassDefFoundError: javax/sql/DataSource或者各种ClassNotFoundException: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration这类诡异错误。
版本对应关系大体如下:
| Spring Boot 版本 | 对应 mybatis-spring-boot-starter 版本 |
|---|---|
| 2.0.x | 2.0.x |
| 2.1.x ~ 2.6.x | 2.1.x ~ 2.2.x |
| 2.7.x | 2.2.x 或 2.3.x |
| 3.0.x | 3.0.x |
| 3.1.x / 3.2.x | 3.0.x 及以上 |
建议直接去 MyBatis 官方 GitHub 页面查看最新版本对应关系。我踩过一次最无语的坑是:Spring Boot 2.7 项目里,同事手滑引入了mybatis-spring-boot-starter3.0.3,结果启动时一直报ClassNotFoundException: org.mybatis.spring.SqlSessionTemplate,排查了大半天才发现是 starter 版本跨了主版本。
排查依赖冲突的标准操作是看依赖树:
mvn dependency:tree -Dincludes=org.mybatis:mybatis,org.mybatis.spring.boot:mybatis-spring-boot-starterGradle 项目用:
gradle dependencies --configuration compileClasspath | grep mybatis重点看有没有重复的 mybatis 或者 mybatis-spring 版本,如果有不同版本共存,大概率就是版本冲突导致的初始化异常。
3.5 Mapper 扫描与注册配置问题
这一类问题要分两种情况看:一种是启动阶段直接报错,另一种是配置不完整导致运行阶段才出问题。
启动阶段最常见的是@MapperScan和@Mapper注解的路径写错,导致 Mapper 接口没有被注册。但说实话,这种问题通常不会直接导致sqlSessionFactory创建失败,更多是项目能启动,但运行时报Invalid bound statement (not found)。
不过有一种情况会在初始化时就挂:@MapperScan指定的包路径下存在无法被加载的类,或者其他框架的类被误扫进来。这时 MyBatis 在自动扫描 Mapper 接口时可能会抛出异常,间接导致sqlSessionFactory创建失败。
还有一个隐藏比较深的点:@MapperScan扫描到的接口,如果 XML 映射文件里的namespace跟接口全限定名不一致,启动阶段一般不会报错,但运行时报BindingException。这个问题我在第四节里单独讲。
如果你用的是@Mapper注解而不是@MapperScan,要确保@Mapper注解所在的接口被 Spring 的组件扫描覆盖到。Spring Boot 的自动扫描默认从启动类所在包开始向下扫描,如果@Mapper接口放在了启动类所在包之外,就不会被扫到,MyBatis 自动配置中的AutoConfiguredMapperScannerRegistrar也扫不到,最终表现为 Mapper Bean 缺失。
3.6 类型别名、实体类与配置属性冲突
这一类问题比前几个少见,但一旦遇到,排查起来更费劲。
typeAliasesPackage配置了不存在的包路径,或者配置的包下面没有实体类,通常会静默处理或者扫出一个空集合,一般不会报错。但如果配置的包路径写成了类似com.example.entity,com.example.model这种带逗号的形式,在某些版本下有概率直接解析失败。
还有一种情况是typeHandlersPackage配置错误,或者是自定义 TypeHandler 类里引用了不存在的依赖,初始化时抛TypeException或ClassNotFoundException。这类报错通常也是通过Caused by里的具体异常信息暴露出来的。
另外,mybatis.configuration.map-underscore-to-camel-case、mybatis.configuration.jdbc-type-for-null这类配置项如果写错了枚举值或类型,也可能会在构建 Configuration 对象时抛异常。比如jdbc-type-for-null配置成了一个不存在的 JDBC 类型,MyBatis 解析时会直接报IllegalArgumentException。
这类问题不好归成固定套路,我的建议是:看到Caused by是 MyBatis 自身的BuilderException或ConfigurationException时,优先回看application.yml里所有mybatis.*配置项有没有明显不合法的值,再用逐个注释配置项的方式做二分定位。
4. 一步步入手的定位实操:从启动报错到修复完成
理论知识讲完,接下来是我在实际排障时用的一套固定流程。这套流程看起来不复杂,但每步都能有效缩小排查范围,比瞎改配置靠谱得多。
4.1 第一步:拿到完整的异常链
先把启动时的异常日志完整复制下来,重点是找Caused by。如果用的是 IDEA,控制台会直接把异常链折叠成树状结构,点开就能看到完整堆栈。
如果日志太长不好定位,可以直接在日志文件里搜索:
grep -n "Caused by" spring.log然后把最后一个Caused by对应的异常信息单独拎出来看。
我给自己定的排查标准是:只认最后一个 Caused by,前面的报错信息只看链路背景,不动手改配置。这样可以避免被多个异常信息干扰,集中精力解决根因。
4.2 第二步:对照检查配置文件
拿到根因后,打开application.yml或application.properties,着重检查四类配置:
第一,数据源配置。看spring.datasource.url、username、password、driver-class-name是否完整、正确。
第二,MyBatis 配置。看mybatis.mapper-locations、mybatis.type-aliases-package、mybatis.config-location、mybatis.configuration.*这几个关键项是否合理。
第三,Mapper 扫描配置。看启动类或配置类上有没有@MapperScan,指定的包路径是否真实存在且包含 Mapper 接口。
第四,检查pom.xml或build.gradle,确认 mybatis 相关依赖版本和 Spring Boot 版本匹配。
这个阶段不需要逐行细看,先做"配置是否存在、格式是否合法、路径是否真实存在"这三个判断,能过滤掉一半以上问题。
4.3 第三步:二分法隔离问题
如果配置看起来都正常,但报错依旧,就需要用二分法缩小出问题的模块。
我的做法是:先注释掉mybatis相关配置,启动一次。如果能启动,说明问题出在 MyBatis 配置;如果还是报错,说明数据源或依赖层面的问题。
把问题范围缩小到 MyBatis 后,再进一步二分:
- 先注释掉
mapper-locations,启动。如果能启动,说明 Mapper XML 路径或 XML 内容有问题。 - 再注释掉
type-aliases-package,启动。如果能启动,说明别名扫描路径有问题。 - 再检查
config-location和configuration.*是否同时配置了,这俩冲突是启动阶段直接报错的,很好验证。
这种二分法配合 IDE 热重启,通常半小时内能锁定问题范围。
4.4 第四步:单独验证 Mapper XML
如果定位到 Mapper XML 有问题,但路径看起来没错,那就要检查 XML 内容本身。
常见问题有:
第一,XML 文件头缺失或者 DOCTYPE 写错,导致解析失败。
第二,XML 映射文件里的mapper标签namespace跟 Mapper 接口全限定名不一致。比如接口是com.example.demo.mapper.UserMapper,但 XML 里写成了com.example.demo.mapper.UserDao,启动阶段可能不报错,但一旦调用就会抛BindingException。
第三,XML 文件编码问题,比如文件是 GBK 编码但项目统一用 UTF-8,解析时会出现乱码导致XMLParserException。
验证单个 XML 文件是否有效,可以直接用浏览器的 XML 查看器打开,或者用 IDEA 的 XML 校验功能。如果是 namespace 对不上,我在 IDEA 里装了 MyBatisX 插件,它能自动检查 Mapper 接口和 XML 的对应关系,很实用。
4.5 第五步:清理本地构建缓存
这个步骤属于压箱底的土办法,但确实解决过我遇到的问题。有时候代码和配置都检查不出问题,但项目里残留了旧的构建产物。比如target/classes里有旧的 XML 文件,或者本地 Maven 仓库里的 MyBatis 依赖包损坏了。
操作很简单:
mvn clean package如果还不行,把本地仓库里对应的 mybatis 依赖目录删掉,重新拉取:
rm -rf ~/.m2/repository/org/mybatis mvn clean package这种行为模式我已经形成习惯了:代码排查超过半小时没结果,先做一次mvn clean package。看起来毫无技术含量,但确实能排除很多"缓存型幻觉问题"。
5. 常见问题速查表:对着症状找方案
把前面涉及的问题整理成一个速查表,排查时可以先对号入座,再决定从哪里下手。
| 报错特征(看 Caused by) | 大概率原因 | 快速解决方案 |
|---|---|---|
| IOException: Could not find resource xxx.xml | mapper-locations 路径错误或 XML 未打包 | 检查 mapper-locations 路径;把 XML 放 resources,重新mvn clean package |
| IllegalStateException: Configuration location must be specified or configuration properties but not both | config-location 与 configuration.* 冲突 | 二选一,精简配置 |
| IllegalArgumentException: jdbcUrl is required | 数据源 URL 缺失 | 检查 spring.datasource.url 配置 |
| Failed to configure a DataSource | 数据源自动配置失败 | 检查数据源依赖和配置,确认引入了 JDBC Starter |
| NoClassDefFoundError: javax/sql/DataSource | 版本是 Spring Boot 3.x + 旧版 mybatis-starter | 升级 mybatis-spring-boot-starter 到 3.x |
| ClassNotFoundException: org.mybatis.spring.SqlSessionTemplate | mybatis-spring 版本缺失或版本冲突 | 用依赖树检查 mybatis 相关版本 |
| XMLParserException: Error resolving JAXB | JDK 版本过高或缺少 XML 解析器 | 检查 mapper XML 的 DOCTYPE 声明 |
| BindingException: Mapper method not found | XML 的 namespace 与接口不匹配 | 修正 namespace 或 Mapper 接口路径 |
| BuilderException: Error parsing Mapper XML | XML 内容语法错误 | 用 IDEA XML 校验功能检查文件 |
注意,这张表里的每种情况,都需要结合你自己的项目环境做二次确认,不能拿着对应关系就直接套。毕竟不同 Spring Boot 版本、不同 MyBatis 版本,报错细节会有差异,但根因分析的方向是一致的。
6. 我踩过的一些特殊坑:多数据源与自定义插件场景
接下来这部分是我额外想补充的内容,虽然不算是sqlSessionFactory创建失败的"最常见"原因,但一旦遇到,排查起来特别痛苦,所以专门拿出来说一说。
6.1 多数据源场景下的 SqlSessionFactory 重复注册
在一次实际项目里,我需要同时连接两个数据库,于是手动配置了两个DataSource和两个SqlSessionFactory。启动时报的第一个错就是:
Error creating bean with name 'sqlSessionFactory' defined in class path resource ...排查过程比较曲折,最后发现是@Primary和@Qualifier没有配合好,或者两个MapperScan扫描到的 Mapper 接口出现了交叉导致注入冲突。
多数据源的正确配置套路大体是:
@Configuration @MapperScan(basePackages = "com.example.demo.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory") public class Db1Config { @Bean @Primary public DataSource db1DataSource() { // 数据源1 } @Bean @Primary public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); // ... return factoryBean.getObject(); } }另一个Db2Config类似,但不能加 @Primary,并且sqlSessionFactoryRef要指定对应的SqlSessionFactoryBean 名称。
多数据源还有一个容易被忽略的问题:事务管理器PlatformTransactionManager也需要对应多个,并明确指定主事务管理器。如果事务管理器的DataSource跟SqlSessionFactory的DataSource不一致,启动阶段可能不报错,但运行时事务会失效,数据源会用错。
6.2 自定义 Interceptor 或 TypeHandler 导致初始化失败
还有一次,我在SqlSessionFactoryBean上注册了一个自定义的 MyBatis 拦截器做 SQL 日志打印。因为拦截器类里用到了 Spring 注入的组件,但SqlSessionFactoryBean的初始化顺序比那个组件晚,导致拦截器初始化时拿到 null,最终抛NullPointerException被包装成sqlSessionFactory创建失败。
这种问题最坑的地方在于:Caused by是NullPointerException,很难直接联想到是拦截器的问题。排查思路是看堆栈里有没有你自己的业务类,如果有,优先检查这些类里的依赖注入和初始化逻辑。
解决方案通常是把拦截器设计成无状态类,内部不依赖 Spring 容器;或者用ApplicationContextAware在上下文刷新后再获取依赖,避免在afterPropertiesSet()阶段提前使用未初始化的 Bean。
6.3 配置项缩进错误与 YAML 解析异常
说出来有点不好意思,但这类低级错误我真的踩过,而且不止一次。
YAML 配置对缩进非常敏感,mybatis:下面如果少缩进两个空格,或者mapper-locations:后面的值没有空格,Spring Boot 解析时可能直接把配置忽略了,或者直接抛解析异常。
比如:
mybatis: mapper-locations:classpath:mapper/*.xml这种写法在 IDEA 里会被标红,因为mapper-locations:和classpath:之间要有空格。如果 IDE 没提示,或者你用的是线上环境临时改配置,那问题就很隐蔽了。
我的检查习惯是:修改完 YAML 后,先在 IDEA 里看缩进标线是否对齐;如果 IDE 对.yml文件没有识别成 YAML 格式,先确认文件后缀正确;然后看配置项前面有没有多余的空格或 Tab。这个习惯帮我避免了至少三次无意义的排查。
6.4 多模块 Maven 项目中的资源文件遗漏
还有一种场景,Maven 多模块项目,公共模块里定义了 Mapper 接口和 XML 文件,但 XML 放在公共模块的src/main/java目录里,没有单独放 resources,或者没有在公共模块的pom.xml中配置资源插件。
启动报错通常是:
IOException: Could not find resource mapper/UserMapper.xml但这个路径在单独看的时候明明存在,只因为在启动的 Web 模块里,公共模块的 XML 没有被打进它自己的 jar 包里。
这个问题的检查方法是看打出来的 jar 里有没有对应 XML:
unzip -l common-module.jar | grep xml如果没找到,就要在公共模块的pom.xml里配置资源目录,把 XML 也打包进去:
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build>这种问题一旦遇到,靠改mapper-locations前缀(从 classpath 改成 classpath*)有时候能绕过去,但如果 XML 压根没打进去,改什么都没用。核心解法还是把 XML 的打包问题解决掉。
7. 我的最后建议:建立配置检查清单
说实话,sqlSessionFactory创建失败这个问题,90% 的情况都能靠"看 Caused by"解决。真正让人头疼的是那种 Caused by 指向的异常信息不明确、或者被中间层二次包装的情况。这种情况下,扎实的配置基本功比所谓的"经验技巧"更管用。
我给自己整理过一份启动期数据访问层配置检查清单,每次新建项目或者搬迁项目时都会过一遍:
第一,spring.datasource.url、username、password三件套是否完整,URL 是否带上了正确的驱动参数和时区参数。
第二,mybatis.mapper-locations是否为classpath:mapper/*.xml或classpath*:mapper/**/*.xml,并且实际的 XML 目录结构与配置匹配。
第三,mybatis.configuration.*里的每个子项是否拼写正确,布尔类型是否写成了字符串。
第四,依赖版本是否匹配,尤其注意 Spring Boot 大版本和 mybatis-spring-boot-starter 的版本要不要对齐。
第五,是否有多个SqlSessionFactory或DataSource同时被定义,如果没有特殊需求,千万不要手动重复定义。
这份清单看起来平平无奇,但每一次项目从零搭建,我就是靠它避开了绝大多数启动期坑。作为一个常年跟 Spring 生态打交道的人,我最大的体会就是:这类报错其实是在帮你做配置自检。它把问题暴露在启动阶段,而不是等你跑到线上才出岔子。从这个角度看,看到这行报错不完全是坏事——至少你还有机会在用户面前把它修好。