
在IDEA里开发Spring Boot项目遇到Property sqlSessionFactory or sqlSessionTemplate are required这个报错几乎每个和MyBatis打过交道的人都绕不过去。这个报错信息本身很短但背后藏着的可能性非常杂依赖没引对、自动配置没生效、扫描路径写错、多数据源漏配了Bean……我以前第一次遇到时盯着控制台看了快半小时硬是没搞清楚问题出在哪。这篇文章我会把我自己排查这个报错的过程完整复现一遍从报错现场、底层原理到分步骤的排查链路再到几个高频踩坑场景的对照修复。如果你也正好卡在这建议按照文章的顺序走一遍大概率几分钟就能定位到根因。1. 报错现场复盘堆栈里真正值得看的几行内容先说这个报错长什么样。项目启动时控制台刷出一大片红色日志常见的有两种形态。第一种是Spring Boot启动框架级别的失败*************************** APPLICATION FAILED TO START *************************** Description: Property sqlSessionFactory or sqlSessionTemplate are required第二种更隐蔽一些会先抛一个BeanCreationException里面裹着真正的异常org.springframework.beans.factory.BeanCreationException: Error creating bean with name userMapper defined in file [xxx/UserMapper.class]: Instantiation of bean failed; ... Caused by: java.lang.IllegalArgumentException: Property sqlSessionFactory or sqlSessionTemplate are required很多人习惯盯着第一行看其实没用。第一种形态是Spring Boot用FailureAnalyzer包装后的结果信息被压缩得就剩下那一句第二种形态的Caused by才是真正值得分析的线索。我在IDEA里排查这个报错时有个习惯先把IDEA底部控制台的日志模式切换成按时间线显示然后把日志拉到Caused by之后的三四行看它是在创建哪个Bean时报的错。如果Caused by前面紧跟着Error creating bean with name xxxMapper说明问题出在某个Mapper接口的注册环节如果紧跟着的是xxxConfiguration那基本可以断定是某个配置类里的Bean定义出了问题。这个报错还有一个让人迷惑的点它可能发生在MybatisAutoConfiguration里也可能发生在MapperScannerConfigurer扫描之后。同样是这一句话一个是因为SqlSessionFactory本身没创建另一个是因为Mapper扫描器创建了MapperBean但拿不到工厂。这两种情况排查方向完全不同所以第一步永远是看堆栈而不是直接搜这句话。2. 报错原理拆解为什么Spring会向MapperBean要这两个属性既然要排查就得知道这个异常是从哪抛出来的。它不在Spring容器核心代码里而是在MyBatis和Spring的桥接包mybatis-spring里具体位置是MapperFactoryBean这个类。MapperFactoryBean实现了Spring的FactoryBean和InitializingBean两个接口。Spring在创建Bean时如果发现它实现了InitializingBean就会在属性赋值完成后调用afterPropertiesSet()方法。这个方法里有一段关键校验public abstract class SqlSessionDaoSupport implements InitializingBean { private SqlSessionTemplate sqlSessionTemplate; public void afterPropertiesSet() { this.sqlSessionTemplate createSqlSessionTemplate(this.sqlSessionFactory); } }真正的校验还得看MapperFactoryBean的父类SqlSessionDaoSupport之前的逻辑。简单说在创建Mapper代理对象之前Spring必须拿到一个SqlSessionFactory或者SqlSessionTemplate这两个属性至少要有其中一个。如果两个都是空就会抛出这段异常if (this.sqlSessionFactory null this.sqlSessionTemplate null) { throw new IllegalArgumentException(Property sqlSessionFactory or sqlSessionTemplate are required); }我把这个过程翻译成人话Spring容器收到指令去创建userMapper这个Bean而userMapper本质上是一个动态代理。生成代理需要一把钥匙去连接数据库的SqlSession这把钥匙就是SqlSessionFactory。结果Spring找了一圈发现容器里根本没有SqlSessionFactory也没有现成的SqlSessionTemplate于是只能罢工。那正常情况下这东西从哪来如果用的是mybatis-spring-boot-starterSpring Boot的自动配置类MybatisAutoConfiguration会在启动时自动注册两个关键的Bean一个是SqlSessionFactory一个是SqlSessionTemplate。换句话说只要自动配置正常生效MapperBean永远不会缺钥匙。所以这个报错的本质往往不是MyBatis本身的问题而是自动配置失效或Bean定义被意外覆盖导致的连锁反应。我排查时脑子里始终绷着一根弦不是去手动往容器里塞SqlSessionFactory而是搞清楚为什么它没被自动注册进来。3. 从依赖到配置的四步排查链路3.1 第一步确认引入的是不是mybatis-spring-boot-starter这一步看起来低级但真的有不少人栽在这。如果你的pom.xml里只有dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.16/version /dependency那不好意思MybatisAutoConfiguration是不会被激活的。因为自动配置类在mybatis-spring-boot-autoconfigure这个模块里starter的作用只是把mybatis核心、mybatis-spring、autoconfigure打包到一起来触发Spring Boot的自动配置机制。正确写法是dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency这里还要提醒一下版本适配。Spring Boot 2.x对应的是mybatis-spring-boot-starter2.xSpring Boot 3.x对应的是3.x。如果用Spring Boot 3.2却塞一个2.3.2的starter启动虽然不一定立刻报错但后续非常容易触发各种Bean类型不匹配的问题。3.2 第二步检查MapperScan的扫描路径这个报错和扫描路径有很微妙的关系。如果MapperScan写在启动类上但basePackages写的是Mapper接口所在的包路径的父包或子包一般没问题但如果你写了精确到某个子包而Mapper接口在另一个包下Spring不会报找不到接口而是会静默跳过——因为Spring的类路径扫描本身就不报错。我们的项目里就出过这么一件事MapperScan(basePackages com.demo.dao)但Mapper接口实际上放在com.demo.mapper包下。启动时控制台很安静结果一访问业务接口就报这个sqlSessionFactory的错。原因就是Mapper扫描器确实尝试创建了某个代理但在生成代理时发现没有对应的Factory可用。3.3 第三步判断是否动了MyBatis自动配置的奶酪Spring Boot的自动配置有一个特点如果你在Configuration类里手动定义了同类型的Bean自动配置会通过ConditionalOnMissingBean条件放弃注册。这本来是好事给了你自定义的空间但也很容易踩坑。比如有人写了这样一个配置Configuration public class MyBatisConfig { Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean; } }这段代码有个隐蔽问题方法返回类型是SqlSessionFactoryBean。在Spring容器里它注册的是一个FactoryBean而自动配置需要的是SqlSessionFactory类型。两个Bean的类型不一样MybatisAutoConfiguration发现容器里没有SqlSessionFactory于是继续执行自己创建。结果你自定义的FactoryBean和自动配置创建的Factory产生了冲突Mapper扫描时拿到的是错乱的Bean最终一样报missing属性。正确做法是让自定义配置和自动配置各自安好或者直接用MapperScan走扫描路线而不是再手写FactoryBean。如果真的需要自定义SqlSessionFactory比如加插件、加类型处理器方法返回类型应该直接写SqlSessionFactory并且让Spring Boot自动配置的逻辑承接你的Bean。3.4 第四步多数据源场景下的特殊雷区如果你用了多个数据源排查思路又要变一下。一个项目同时配置了主库、从库每个数据源都需要各自的SqlSessionFactory或SqlSessionTemplate。常见错误是主库配了工厂从库只给了DataSource没配工厂。Mapper扫描器把两个库的Mapper接口混在一个扫描路径里结果生成代理时发现从库的Mapper没有对应的Factory于是整锅炸掉。这种情况的报错堆栈通常会指向一个具体的Mapper名字比如Error creating bean with name db2UserMapper。看到这个信号基本可以断定是某条扫描链路缺了一条Factory的分链。修复方案很明确要么把两个库的Mapper拆开放到不同路径用两个MapperScan分别绑定各自的SqlSessionTemplateConfiguration public class DataSourceConfig { Primary Bean(name sqlSessionTemplate) public SqlSessionTemplate sqlSessionTemplate(Qualifier(sqlSessionFactory) SqlSessionFactory factory) { return new SqlSessionTemplate(factory); } }要么给从库单独建一个SqlSessionFactory并保证它也能被Spring扫描到。4. 四个高频复现场景的修复对照4.1 场景一pom里只引了mybatis没引starter我见过不止一次这种配置其他同事在项目中直接用原生mybatis写了一个工具后来又引了一个Spring Boot的模块两边一混合pom里出现了两个MyBatis相关的依赖但偏偏没有mybatis-spring-boot-starter。启动时项目不报依赖冲突也不报缺包就是到了创建MapperBean这一步突然冒出来这个错误。这种场景下修复特别简单。把org.mybatis那个纯依赖删掉或者明确加上starter依赖。如果项目里确实需要原生的org.mybatis:mybatis比如自己写了不依赖Spring的纯JDBC工具那也没关系让starter的版本覆盖它即可。一般情况下mybatis-spring-boot-starter已经包含了mybatis和mybatis-spring没必要重复声明。4.2 场景二自定义配置类被IDE自动优化成了private方法这个坑很少有人提但IDEA确实会给你添乱。当你在配置类里写Bean方法时IDEA的某些重构操作可能会把方法改成private。比如Bean private SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean bean new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); }Spring对Bean方法的要求是非private、非final并且要能被CGLIB代理。一旦方法变成privateSpring会认为这个方法不满足Bean注册条件直接忽略它。结果就是容器里没有SqlSessionFactory自动配置又发现不了它最终触发同样的异常。排查时要养成看方法修饰符的习惯特别是当你发现代码看起来完全正确却报错时先看Bean方法是不是public。4.3 场景三MapperScan路径写在了非配置类上MapperScan必须用在Configuration类上才会被Spring处理。如果你不小心把它加在了普通Service类上比如Service MapperScan(com.demo.mapper) public class UserService { }Spring扫描到这个注解时会创建MapperScannerConfigurer但由于所在类不是配置类这个注解不会被ConfigurationClassPostProcessor处理扫描器等于没执行。然后Mapper接口没有注册成Bean等到其他代码尝试注入Mapper时要么报NoSuchBeanDefinitionException要么在某种代理场景下报当前的missing属性错误。我遇到过一个变种MapperScan放在启动类上没问题但启动类本身用了ComponentScan而且过滤规则把配置类排斥了。这种配置下来启动日志里甚至不会出现MyBatis相关的任何输出非常迷惑。4.4 场景四手动new了SqlSessionTemplate但没set工厂有人为了追求灵活在配置类里自己new了一个SqlSessionTemplateBean public SqlSessionTemplate sqlSessionTemplate() { return new SqlSessionTemplate(null); }这代码能编译通过因为SqlSessionTemplate构造器接受一个SqlSessionFactory参数传null不报编译错误。等Spring初始化MapperBean时把这个空壳template传进去内部一执行就出问题而且报的错误往往就是这个missing属性的变体。正确做法是在构造时传入真实的SqlSessionFactory或者干脆把FactoryBean返回给Spring让容器来完成装配。5. 排查过程中非常实用的两个调试手段5.1 通过启动日志反查Bean注册情况遇到这种报错最怕的就是对着代码干瞪眼。我的做法是先把日志级别调起来看Bean注册的全过程。最简单的方式是在application.properties里加一句logging.level.org.springframework.boot.autoconfigureDEBUG重启之后再刷新日志重点搜两个关键词MybatisAutoConfiguration和SqlSessionFactory。如果日志里能看到类似MybatisAutoConfiguration matched: - ConditionalOnClass found required classes org.apache.ibatis...说明自动配置类的激活条件全部满足排除了一类问题。如果压根没有匹配记录说明依赖或者类路径有问题。5.2 用条件断点卡在异常抛出前如果你用的是IDEA这个方法特别适合定位问题链路。先在依赖包里找到SqlSessionDaoSupport.afterPropertiesSet()方法在这行代码上打一个条件断点。条件可以写成this.sqlSessionFactory null this.sqlSessionTemplate null。这样每次启动到了一个MapperBean创建时只要满足两个属性都为空的条件IDEA就会停住。这时候切换到Debug窗口展开调用栈从下往上翻你就能看到到底是谁创建了这个MapperBean——是MapperScan扫描器还是某个配置类手动注册的。这一步基本能把问题定位到具体代码行。我用的另一个小技巧是直接在启动时给MybatisAutoConfiguration类加一个断点看它是如何注册SqlSessionFactory的。这能直接判断是不是自定义配置把自动配置挤掉了。5.3 临时写一个CommandLineRunner打印Bean清单如果上面的断点太麻烦还有一个更粗暴但很有效的方法在启动类里临时加一段代码把容器里所有名称带SqlSession的Bean打出来Bean public CommandLineRunner printBeans(ApplicationContext ctx) { return args - { String[] beanNames ctx.getBeanNamesForType(SqlSessionFactory.class); System.out.println(SqlSessionFactory beans: Arrays.toString(beanNames)); String[] templateNames ctx.getBeanNamesForType(SqlSessionTemplate.class); System.out.println(SqlSessionTemplate beans: Arrays.toString(templateNames)); }; }如果输出结果是空数组那就说明容器里压根没有这两个Bean问题聚焦在为什么没有自动创建。如果输出有Bean但启动仍然报错那就是另一条链路的冲突了——比如多个SqlSessionFactory但MapperBean拿错了。6. 这个报错修好之后还有哪些连带问题要一起处理报错修好后不等于MyBatis就完全正常了。我在这边额外说三个自己碰到过的连带问题避免你修完一个坑又掉进下一个。第一是Mapper接口已经能被Spring管理但运行时调用方法报Invalid bound statement (not found)。这种情况通常是XML文件路径没配置好MyBatis扫描不到SQL语句。检查mybatis.mapper-locations配置是否正确例如mybatis.mapper-locationsclasspath:mapper/*.xml注意如果用了多数据源每个SqlSessionFactory都要分别设置自己的mapperLocations只配一个全局的不够。第二是事务不生效。当SqlSessionFactory被手动创建后Spring的事务管理器需要通过DataSourceTransactionManager来协调事务。如果事务管理器创建的DataSource和你SqlSessionFactory里的不是同一个事务就会静默失效。排查时先确认容器里是否只有一个数据源Bean以及事务管理器是否引用了正确那个。第三是懒加载问题。假如项目里配置了MapperScan和自定义SqlSessionFactoryBean但FactoryBean没有调用getObject()而是直接返回了工厂对象那么在注入Mapper时会拿到一个错误的代理表现就是启动时不报错一调用就炸。这类问题在Spring 5.3版本之后尤其容易混淆因为FactoryBean的早期解析行为有调整。说实话这个报错本身排查难度不算高真正费时间的是搞不清楚问题到底出在自动配置失效、Bean冲突还是扫描路径错位。我的个人经验是遇到它先别急着改代码先去pom确认starter再去启动日志确认自动配置是否匹配最后才去看配置类。三步走完大多数情况就水落石出了。