ARTICLE DETAIL

资讯详情

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

sqlSessionFactory创建失败?从Caused by开始,一文搞定MyBatis报错排查

sqlSessionFactory创建失败?从Caused by开始,一文搞定MyBatis报错排查 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只匹配一级目录下的 XMLmapper/**/*.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.x2.0.x2.1.x ~ 2.6.x2.1.x ~ 2.2.x2.7.x2.2.x 或 2.3.x3.0.x3.0.x3.1.x / 3.2.x3.0.x 及以上建议直接去 MyBatis 官方 GitHub 页面查看最新版本对应关系。我踩过一次最无语的坑是Spring Boot 2.7 项目里同事手滑引入了mybatis-spring-boot-starter3.0.3结果启动时一直报ClassNotFoundException: org.mybatis.spring.SqlSessionTemplate排查了大半天才发现是 starter 版本跨了主版本。排查依赖冲突的标准操作是看依赖树mvn dependency:tree -Dincludesorg.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.xmlmapper-locations 路径错误或 XML 未打包检查 mapper-locations 路径把 XML 放 resources重新mvn clean packageIllegalStateException: Configuration location must be specified or configuration properties but not bothconfig-location 与 configuration.* 冲突二选一精简配置IllegalArgumentException: jdbcUrl is required数据源 URL 缺失检查 spring.datasource.url 配置Failed to configure a DataSource数据源自动配置失败检查数据源依赖和配置确认引入了 JDBC StarterNoClassDefFoundError: javax/sql/DataSource版本是 Spring Boot 3.x 旧版 mybatis-starter升级 mybatis-spring-boot-starter 到 3.xClassNotFoundException: org.mybatis.spring.SqlSessionTemplatemybatis-spring 版本缺失或版本冲突用依赖树检查 mybatis 相关版本XMLParserException: Error resolving JAXBJDK 版本过高或缺少 XML 解析器检查 mapper XML 的 DOCTYPE 声明BindingException: Mapper method not foundXML 的 namespace 与接口不匹配修正 namespace 或 Mapper 接口路径BuilderException: Error parsing Mapper XMLXML 内容语法错误用 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 里有没有对应 XMLunzip -l common-module.jar | grep xml如果没找到就要在公共模块的pom.xml里配置资源目录把 XML 也打包进去build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/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 生态打交道的人我最大的体会就是这类报错其实是在帮你做配置自检。它把问题暴露在启动阶段而不是等你跑到线上才出岔子。从这个角度看看到这行报错不完全是坏事——至少你还有机会在用户面前把它修好。
返回列表