Spring Boot 项目启动的时候,控制台突然甩出一行Error creating bean with name 'sqlSessionFactory' defined in class path resource,这个场景我太熟了。不管是刚入行的新人,还是写了几年 Java 的老手,看到这行红字第一反应基本都一样:懵。更气人的是,这个报错有时候是偶发的,上午还能跑,下午就挂;有时候是换个环境就挂,本地好好的,一到测试环境就起不来。因为这类问题涉及 Spring 容器、MyBatis 自动配置、数据源初始化、XML 解析好几个层面,真正的原因往往被包在异常链的最里层,不仔细扒根本想不到。
这篇文章是我在 Spring Boot + MyBatis 项目里排查同类问题的一份完整记录,从报错信息本身开始拆,到常见的四类根因,再到一次完整的排查过程,最后整理成速查表。适合刚接触 Spring Boot 项目启动报错的新人,也适合被同样问题卡住、想系统梳理一遍排查思路的开发者。看完之后,你至少能做到:再遇到这个报错,不再乱改代码,而是先把日志中真正的Caused by翻出来,一步步定位。
1. 这个报错到底在说什么
1.1 逐行拆解报错信息
先看一个典型的完整异常长什么样:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' returned null这句话信息量很大,拆开看:
BeanCreationException是 Spring 容器在创建 bean 失败时抛出的统一异常类型。它就是一个包装壳,真正的问题通常不在这里。Error creating bean with name 'sqlSessionFactory'说的是容器里有个叫sqlSessionFactory的 bean 创建失败了。defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]说明这个 bean 不是我们手动配置的,而是 MyBatis 的自动化配置类MybatisAutoConfiguration注册的。看到这一行,基本可以确定项目里引入了mybatis-spring-boot-starter并且没禁用自动配置。Factory method 'sqlSessionFactory' returned null是这串异常里最有价值的一句,翻译成人话就是:sqlSessionFactory这个@Bean方法内部执行完了,但返回了个 null。为什么会返回 null?通常是被@Bean方法内部的一段逻辑抛出且被吞掉的异常导致,或者某个前置条件不满足,提前 return 了。
很多人在这个报错上花掉半天时间,就是因为只盯着第一行看,忽略了后半段。Spring 的异常体系本来就喜欢层层包装,BeanCreationException里面常常还套着BeanInstantiationException,再里面可能还有InvocationTargetException,最里面才是CommunicationsException或者XMLParseException这样的真实原因。不往最里层翻,永远找不到病根。
1.2 为什么异常链需要整个翻出来
我见过太多同学在群里发求助消息,截图只截了上面三四行,问“sqlSessionFactory 创建失败怎么回事”。说实话,单看这几行,谁也没法给结论。因为可能是数据库地址写错了,可能是 Mapper XML 文件路径不对,也可能是依赖冲突。这些根因完全不一样,但最外层报错都长一个样。
所以第一步永远是同一个动作:把完整堆栈捞出来,找到Caused by那一层。以我的经验,90% 以上的sqlSessionFactory创建失败问题,看两三层Caused by就能水落石出。
在 IDE 里运行项目,控制台可能只显示部分日志,需要手动往上翻;如果用nohup启动,就查日志文件。常见命令:
tail -200 nohup.out | grep -A 50 "APPLICATION FAILED TO START"或者更直接一点:
tail -200 nohup.out | grep "Caused by"这里你会看到一连串的Caused by,特点是从下往上越来越接近真正原因。通常最下面一两个Caused by才是现场,前面的都是包装。打个比方,就像一个快递盒,外面包了保护膜、放了填充物、再套纸箱,你拆到最里面才看到商品。上面的异常链每一层都在告诉你“外壳是哪一层的”,只有最后一层告诉你“商品是什么”。
2. 最常见的四类根因
2.1 数据源连接失败
sqlSessionFactory的创建依赖一个核心输入:DataSource。MyBatis 的SqlSessionFactoryBuilder拿到数据源之后才会去构造会话工厂对象。所以只要数据源这一层有问题,sqlSessionFactory必然创建失败。
数据源问题的典型表现有好几种:
第一种是数据库服务根本没起来,或者网络不通。底层异常一般是:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure或者:
java.net.ConnectException: Connection refused第二种是账号密码错误,底层抛Access denied for user。第三种是 JDBC URL 参数写错,尤其是 MySQL 8.x 版本,如果 URL 里没有带serverTimezone,会抛InvalidConnectionAttributeException,提示你设置时区。第四种最常见也最隐蔽:url属性没配上。Spring Boot 2.4 之后对数据源配置做了严格校验,如果检测到spring.datasource.url缺失,会在启动早期就报Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured。但注意,这个报错在某些情况下也会被包装成sqlSessionFactory创建失败,因为自动配置顺序的关系,数据源没初始化成功,后续依赖它的 bean 全都会挂。
排查数据源问题,重点检查三个地方:
spring: datasource: url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver注意driver-class-name要和你用的数据库类型匹配。MySQL 5.x 和 8.x 的驱动类也不一样,8.x 是com.mysql.cj.jdbc.Driver,如果你项目里引入的是老版本驱动,写新类名会报ClassNotFoundException。在本地排查时,最简单的验证方式是先用一个数据库客户端工具,用同样的地址、账号、密码去连一次,能连上再回来查 Spring 配置,否则很容易在配置和代码之间反复横跳。
2.2 MyBatis 配置项写错
第二类高发原因,是application.yml或者application.properties里的mybatis相关配置写错了。这里面有两个特别容易踩的坑。
一个是map-underscore-to-camel-case这种配置项的大小写写法。在application.yml里,推荐写法是:
mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl因为 YAML 对大小写敏感,如果你把它写成map-underscore-to-camel-case,绑定到Configuration对象时会变成属性mapUnderscoreToCamelCase的设置,但 MyBatis 的Configuration类并没有这个驼峰属性对应的方法名,就会导致属性绑定失败。报错信息往往就变成Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory],看起来像是在创建sqlSessionFactory时才出的问题。
另一个是mapper-locations的路径写法。常见写法是:
mybatis: mapper-locations: classpath*:mapper/**/*.xmlclasspath*:前缀表示扫描 classpath 下所有 jar 包和目录中的匹配文件,如果你只写classpath:mapper/*.xml,则只会扫描当前模块的 class 目录。项目从单模块变成多模块之后,如果这里路径写窄了,Mapper XML 文件加载不全,运行时就会报Invalid bound statement (not found),运气不好时启动阶段直接因为 XML 解析失败而挂掉。路径到底该写多深,要看你 XML 文件实际放的目录结构。一般情况下,我习惯用classpath*:mapper/**/*.xml,覆盖最深,踩坑概率最小。
2.3 Mapper XML 解析异常
第三种根因,在项目跑了一段时间、新增了一个 Mapper 文件之后特别容易出现:新写的 XML 文件有问题,导致 MyBatis 在解析时抛异常。常见的情况有:
- XML 文件里有中文注释,但文件编码不是 UTF-8,解析时报
Invalid byte 1 of 1-byte UTF-8 sequence。 <mapper namespace="...">和对应的 Mapper 接口全限定名不匹配。- 同一个
id在多个 XML 里重复定义,报Mapped Statements collection already contains value for ...。 - XML 里标签写错,比如
resultType写成resultMap,或者参数类型写了个不存在的类。
这类错误处理起来不难,难的是定位。因为异常栈里可能只会有一句org.apache.ibatis.builder.BuilderException: Error creating document instance,具体是哪个 XML 文件、哪一行,需要继续往下翻Caused by。
XML 解析这一类,我的建议是先把文件用 IDE 打开确认编码,再逐项检查 namespace 和 id。刚写完一个 Mapper 之后就启动报错,90% 是编码或者 namespace 的问题。
另外补充一种容易忽略的情况:@MapperScan扫描的包路径和 Mapper 接口实际不在同一个包下。Spring 启动时会尝试为每个 Mapper 接口生成代理,但 MyBatis 初始化时需要找到对应的 XML 或注解 SQL。如果接口找不到任何 SQL 定义,有些版本下会报Invalid mapper interface或者直接导致sqlSessionFactory初始化中断。解决方式是把@MapperScan("com.example.mapper")里的包路径改成和实际接口一致的路径。
2.4 依赖版本冲突
最后一类根因,排查起来比前三种都费劲:依赖版本冲突。
最常见的冲突场景是,项目里同时引入了不同版本的mybatis和mybatis-spring。比如 A 模块依赖了mybatis-spring-boot-starter,B 模块的传递依赖里又带了一份老版本mybatis-spring,Maven 对冲突版本有仲裁规则,但仲裁出来的版本不一定和主流程兼容。结果就是SqlSessionFactory初始化时,某个类找不到,报NoClassDefFoundError,或者调用某个方法时报NoSuchMethodError。
这种情况的典型异常特征是:
java.lang.NoSuchMethodError: org.apache.ibatis.session.Configuration.getDefaultScriptingLanguageInstance()Lorg/apache/ibatis/scripting/LanguageDriver;或者:
java.lang.NoClassDefFoundError: org/apache/ibatis/executor/ErrorContext看到这些 class 相关的错误,基本就是依赖冲突无疑。排查方法用 Maven 命令看依赖树:
mvn dependency:tree -Dincludes=org.mybatis输出结果里如果出现两个不同的mybatis或mybatis-spring版本,就需要手动排除旧版或统一版本号。乱用排除依赖容易引发二次问题,我个人的做法是:先确定项目使用的 Spring Boot 版本,再反查对应的 starter 版本。用一个对照关系,比如 Spring Boot 2.7 时代对应mybatis-spring-boot-starter2.x 系列,Spring Boot 3.x 对应 3.x 系列。不要在 3.x 项目里硬塞 1.x 的 starter,这种组合启动后各种奇怪问题层出不穷。
3. 一次完整的排查实录
3.1 现场:项目启动不到两秒就失败
这个案例我印象很深。当时是给一个老项目加新功能,本地怎么跑怎么通,提交到测试环境就起不来,运维给出的日志开头就是Error creating bean with name 'sqlSessionFactory' defined in class path resource。研发群里第一反应是环境问题,让运维检查数据库服务,结果数据库正常,账号也有权限。
我拿到完整日志后,先执行了tail -200看了一下全部堆栈。Caused by部分一行行往下翻,下部出现了:
Caused by: org.xml.sax.SAXParseException: Element type "mapper" must be followed by either attribute specifications, ">" or "/>".这就有方向了:不是数据库问题,是 XML 解析失败。于是我去测试环境的 jar 包里找 mapper XML,或者在编译输出目录找 class 文件夹下的 mapper 文件,发现其中有一个 XML 文件末尾被追加了一些特殊字符,是 merge 代码时产生的冲突标记没清理干净,比如<<<<<<< HEAD这样的内容被留在了 XML 里。这一行不在注释范围内,XML 解析器直接报错。问题出在代码合并时没处理干净,测试环境编译出来的文件带上了冲突标记。本地没问题,是因为我本地分支没有合并那段冲突代码。
3.2 用 DEBUG 日志快速缩小范围
上面这段排查如果只是看异常栈,也已经能指向 XML 解析失败。但更快的办法,是在application.yml里临时打开 MyBatis 的详细日志:
logging: level: org.apache.ibatis: DEBUG org.mybatis: DEBUG org.springframework.jdbc: DEBUG结果控制台会输出大量日志。你不需要逐行看,直接搜Parsing mapper XML或者Building SqlSessionFactory,能看到 MyBatis 加载了哪些 XML,加载到哪个文件时开始报错。这样就不用挨个文件猜。
这个方法特别适合“本地能跑,环境挂了”的场景,因为环境通常不能很好 debug,最靠谱的方式就是把日志级别调低,用日志还原现场。即使不是 XML 解析问题,DEBUG 日志里也会暴露数据库连接建立的细节,比如拿到什么 URL、连接哪个库用了多长时间、是否成功。
3.3 数据源连接失败的排查过程
再举一个数据源方向的实例。另一个项目报同样的错,Caused by是:
Caused by: com.mysql.cj.jdbc.exceptions.InvalidConnectionAttributeException: The server time zone value 'CST' is unrecognized or represents more than one time zone.这是 MySQL 8 驱动对时区要求严格导致的。报错里还明确提示了解决办法:在 URL 后面加serverTimezone=Asia/Shanghai,显式声明时区。我改完之后重启,又报了Access denied for user 'root'@'localhost',说明账号密码不对。运维给的密码里有两个特殊字符@和#,在 YAML 文件里没有加引号,导致密码在解析时被截断。直接看还是改了很长时间,形态是一个很标准的配置读取问题:把密码用单引号或双引号包起来就解决了。
这个案例给我的教训是:数据源报错往往不是单点问题,可能第一个问题解决了,第二个问题才浮出来。所以不要改完配置启动一次失败了就慌,按顺序排查:能不能连通、账号能不能登录、时区对不对、驱动类在不在,逐个击破。
3.4 从配置检查到依赖树验证
还有一种情况是修改完代码,重新打包后启动还是报错。这时我会优先怀疑依赖冲突。用前面提到的dependency:tree命令看org.mybatis的依赖情况。有一次排查时发现项目里存在mybatis-spring:1.3.2和mybatis-spring:2.0.6两个版本。Maven 仲裁结果选了 2.0.6,但某个内嵌模块引用的旧版本mybatis-spring在某些代码路径下被显式加载,导致初始化sqlSessionFactory的时候,某些接口方法签名对不上,直接NoSuchMethodError。
处理方式就是在pom.xml里显式声明版本统一:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency>同时,如果有模块引用了其它 mybatis 相关包,通过exclusions排除掉传递依赖中的旧版本,只保留统一版本。再 launch,瞬间就起来了。这类问题最难的一点不是修,而是敢判断——很多开发者看到NoSuchMethodError以为是 JDK 版本问题,到处换 JDK,其实和 JDK 完全没关系。
4. 报错关键词速查表与避坑清单
4.1 常见报错关键词对照表
我把实际项目中遇到过的情况整理成一张表,方便你拿着日志直接对照:
| 报错关键词/堆栈特征 | 可能原因 | 第一步操作 |
|---|---|---|
Communications link failure、Connection refused | 数据库地址不通、数据库未启动 | 用客户端工具测试连通性,检查 IP 端口 |
Access denied for user | 账号密码错误 | 检查密码里是否有 YAML 特殊字符,试手动连接 |
InvalidConnectionAttributeException | MySQL 8 缺少时区参数 | URL 增加serverTimezone=Asia/Shanghai |
Failed to configure a DataSource: 'url' attribute is not specified | spring.datasource.url缺失 | 检查配置项拼写和位置 |
SAXParseException、Error creating document instance | Mapper XML 文件编码或内容格式错误 | 检查 XML 文件编码和未闭合标签,搜索冲突标记 |
Mapped Statements collection already contains value for | Mapper XML 中 id 重复 | 全局搜索重复 id,确认 namespace 唯一 |
NoSuchMethodError、NoClassDefFoundError | 依赖版本冲突 | mvn dependency:tree查看 mybatis 相关版本 |
Invalid bound statement (not found) | mapper-locations 路径配置错误 | 检查classpath*:mapper/**/*.xml路径 |
Failed to instantiate [SqlSessionFactory],且没有更底层原因 | 某个@Bean方法内部返回 null | 检查 MyBatis 核心配置方法和自定义工厂 Bean |
这张表不能覆盖所有场景,但覆盖了我遇到过的八成情况。剩下的两成,几乎都能通过完整堆栈的最后一行Caused by找到线索。
4.2 几个容易被忽略的坑
配置项大小写这个坑,我再强调一次。Spring Boot 的@ConfigurationProperties支持松绑定,但 MyBatis 的configuration节点下的属性走的是配置类,和普通配置属性绑定的规则不完全一样。map-underscore-to-camel-case这种中划线写法在 YAML 里是安全的,但如果有人写成map_underscore_to_camel_case,那就是另一个属性了,完全不生效,但也不会报错。这种“不报错但不生效”比直接报错更折磨人,因为启动是成功的,只是查询结果里的下划线字段不会自动转驼峰。
还有log-impl配置。很多人想输出 MyBatis 里的执行 SQL,会在 configuration 下面加log-impl: org.apache.ibatis.logging.stdout.StdOutImpl。但是如果你用的是 logback,这种方式输出的 SQL 会以 System.out 形式直接打印,不经过日志框架,导致日志文件里看不到 SQL,只有控制台有。想统一走日志框架,应该用org.apache.ibatis.logging.slf4j.Slf4jImpl。这个不算启动报错,但排查 SQL 问题时容易被误导。
补充一个隐蔽情况:@MapperScan扫描了太多包。如果扫描的包路径写得特别大,比如写成了com.example,MyBatis 会把包下所有接口都当 Mapper 尝试解析,某些接口如果没有对应的 XML 也没有注解 SQL,启动时可能直接报错。建议@MapperScan精确到具体的 mapper 包,不要贪图省事扫一个大包。
5. 排查这类问题我养成的几个习惯
最后分享一点个人经验,也算是我踩过很多次坑之后总结出来的动作清单。
第一,碰到Error creating bean开头的问题,永远先把完整堆栈导出到文件里,再搜Caused by,不要只看 IDE 控制台那几十行。控制台缓冲有限,真正的重点可能被刷掉了。保存文件之后用 grep 或者编辑器查找,效率高得多。
第二,改动配置之后尽量mvn clean再启动,或者至少在 IDE 里重建项目一次。我遇到过太多次因为旧的编译残留 class 和新配置不对应,启动时报一些莫名其妙的问题,clean 之后所有现象都消失。尤其是 mapper XML 文件被修改后,如果没重新编译,target 目录里的旧 XML 还留在原路径,容易让人误判路径错误。
第三,数据源相关的参数,密码里如果有特殊字符,第一时间给 YAML 的值加上引号。很多“看起来完全没错配置但连不上数据库”的问题,都是 YAML 解析特殊字符时出的妖。这一点只花一秒钟就能避免,却让我省过不少调试时间。
第四,如果项目是多模块架构,检查 mapper-locations 时一定要结合模块打包后的目录结构来判断。单模块下能跑的配置,到了多模块下不一定还适用。用jar tf 包名.jar | grep mapper看看实际路径,是最稳的核对方式。
第五,不要一上来就怀疑框架有 bug。Error creating bean with name 'sqlSessionFactory'这个报错,绝大多数情况都是配置、环境或者依赖的问题。真正遇到 MyBatis 框架自身的 bug,概率极低。保持这个心态,排查方向就不会偏离。
说到底,这类启动报错并不可怕,可怕的是不看完整日志就乱改。先把日志翻透,再动手,你也能在几分钟内找到病根。