报错信息里那行红字,大多数人不陌生:Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required。尤其是用IDEA搭MyBatis项目、改配置、搬代码的时候,突然跳出来,第一反应是百度,第二反应是怀疑自己是不是少加了依赖,第三反应可能就是想把IDEA重装一遍。其实这行报错的根源通常不在IDEA,而在Spring容器里根本没有MyBatis的核心Bean。这篇文章我就把这条报错掰开揉碎,从原理到排查,从Spring Boot到老Spring项目,把能踩的坑都过一遍,最后给你一份可以直接照着做的排查清单。
这条报错适合谁看?只要是用了MyBatis和Spring/Spring Boot开发,对这些报错没把握的人,都值得看看。哪怕你不是源码党,只要按着文章里的排查顺序走一遍,基本能解决九成以上的情况。我尽量用大白话讲清楚,遇到专业术语会补一句解释。
1. 先搞清楚这个报错在说什么
1.1 报错背后其实是Spring容器缺Bean
这句报错的英文直译是“必须提供sqlSessionFactory或者sqlSessionTemplate属性”。什么意思?MyBatis真正干活的时候需要两个东西:一个是SqlSessionFactory,它负责创建数据库会话;另一个是SqlSessionTemplate,它是Spring封装过的会话模板,我们平时写的Mapper接口最终就是靠它来执行SQL的。Spring要管理Mapper,就必须往容器里放一个这样的对象。如果某个地方希望注入这个属性却找不到Bean,Spring就会抛出“are required”。
我打个比方:你去餐厅点菜,厨房需要一个厨师才能出菜。现在后厨登记表里根本没厨师的名字,厨房系统就会报“必须要有厨师”。这里的厨师就是SqlSessionFactory或SqlSessionTemplate,报错就是系统告诉你:没有厨师,开不了火。
所以第一反应别去怀疑IDEA,也别急着清缓存。你要查的是:Spring容器启动过程中,有没有成功创建MyBatis相关的Bean,以及创建完之后有没有正确注入到需要的地方。
1.2 两种触发这个报错的高频场景
我遇到过的情况基本可以分成两大类。
第一类,也是最常见的:用了mybatis-spring-boot-starter但配置不对。比如application.yml里没有写任何MyBatis配置,或者启动类漏了@MapperScan,又或者依赖被注释掉、版本冲突导致自动配置没有生效。这种情况下,Spring Boot不会自动注册Mapper,等MapperScan或者MapperFactoryBean想干活时,发现没有SqlSessionFactory可用,就开始报错。
第二类,老式Spring项目,或者是你自己手动定义MyBatis配置类。比如在Spring MVC项目里,没有引入starter,只引了mybatis和mybatis-spring,这时候你必须手动声明SqlSessionFactoryBean,并且把它交给Spring管理。如果漏了Bean定义,或者配置类没有被组件扫描到,就会报同样的错。
还有一类比较隐蔽:多个ApplicationContext导致Bean不在同一个容器里,或者多个@MapperScan路径冲突。这类问题在微服务拆包、公共模块抽取时特别容易出现。我会在后面几节展开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先从Spring Boot项目排查(最常见)
2.1 检查pom.xml依赖是否真的引入完整
如果你的项目是Spring Boot,第一件事就是打开pom.xml,确认你不是只依赖了mybatis,而是依赖了mybatis-spring-boot-starter。很多初学者会写成这样:
xml复制<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.x</version>
</dependency>
这只能让MyBatis核心类可用,但不能让Spring Boot自动配置MyBatis。你需要的是:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.x</version>
</dependency>
这里有个容易忽略的点:starter版本和Spring Boot版本之间有兼容关系。比如Spring Boot 2.x对应mybatis-spring-boot-starter 2.x,Spring Boot 3.x对应的是3.x。如果版本不匹配,自动配置类可能不会加载,或者加载后报ClassNotFoundException。这种时候报错可能不是直接给这一句,而是加载MapperScannerRegistrar失败,但最终弹出的错误里会夹杂着这句话。
我建议你把mvn dependency:tree跑一遍,看看mybatis-spring-boot-autoconfigure到底有没有进来。如果明明写了依赖但没出现,那就是依赖被排除了,或者父POM里dependencyManagement把版本覆盖掉了。这个现象特别坑,我也被坑过几次。
2.2 确认application.yml里的MyBatis配置是否被加载
很多人以为只要配置了Mapper XML路径,Spring Boot就会自动去建SqlSessionFactory。其实不是这样。Spring Boot的MybatisAutoConfiguration会在检查到容器中没有SqlSessionFactory且没有SqlSessionTemplate时,自动去创建一个。这个创建过程完全依赖SqlSessionFactoryBean,它需要数据源。
所以如果你的项目里没有配置spring.datasource.url、username、password,SqlSessionFactoryBean的后处理器会因为数据源没准备好而失败,最终可能导致启动报错。这个报错常常不是直接指向数据源,而是先指向MyBatis的session工厂。
你可以先在application.yml里确认有没有这段最小配置:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.entity
如果数据源配置没问题,再检查mybatis.mapper-locations路径写没写对。路径写错不会立即报错,但当你调用Mapper时会得到“Invalid bound statement (not found)”,这和我们要排查的报错不是同一个,但很多人容易混在一起。
还有一个小细节:mybatis.configuration和mybatis.*配置项只有在mybatis-spring-boot-starter生效时才会被读取。如果你是自己手动定义的Bean,那这些配置对自动配置不生效,需要你自己在Bean里设置。
2.3 启动类上的@MapperScan到底该不该加
这是新手问得最多的问题:用了starter还要不要加@MapperScan?
答案是可以加,也可以不加。前提是你得理解两种方式的区别。
如果不加@MapperScan,Spring Boot会扫描启动类同级包及其子包下的所有接口。如果Mapper接口带有@Mapper注解,它就会被注册。但如果Mapper接口位于启动类子包之外,而且没有注解,就扫描不到,调用时会报“Invalid bound statement”。
如果你加了@MapperScan(basePackages = "com.example.demo.mapper"),那么指定包下的所有接口都会被当作Mapper扫描注册,不需要每个接口都写@Mapper。这个注解会触发MapperScannerRegistrar,原理后续我会讲。
很多项目其实两种方式混着用,导致重复注册或者扫描路径不一致。比如你启动类上加了@MapperScan(basePackages = "com.example.mapper"),但Mapper接口却在com.example.dao包里,那么容器里就没有这些Mapper。但请注意,没有Mapper和没有SqlSessionFactory不是一回事。@MapperScan只是解决了Mapper接口的注册,它内部会需要SqlSessionFactory。如果此时容器里没有任何SqlSessionFactory,同样会报“Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required”。
所以如果看到这个报错,先别急着怀疑@MapperScan,你要先确认的是工厂是否存在。你可以通过几种方式判断:
- 在启动类中临时注入
SqlSessionFactory,如果能注入成功说明Bean存在; - 如果注入失败,那就去查为什么自动配置没生效。
我自己常用的是第一种,直接在某个@Configuration类里写一个临时的@Autowired SqlSessionFactory,启动试试。能注入说明工厂没问题,再往下查别的原因;不能注入说明问题就在工厂创建这一环。
3. 老式Spring项目或手动配置的踩坑记录
3.1 没有使用starter时,SqlSessionFactory这个Bean得自己定义
如果你的项目不是Spring Boot,而是在传统Spring MVC里集成MyBatis,或者你故意不用starter,想自己控制MyBatis配置,那SqlSessionFactory不可能自动出现。你需要手动创建Bean。
最标准的做法是写一个配置类:
java复制@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 如果有Mapper XML,需要指定路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
// 可以设置其他属性,比如下划线转驼峰
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
}
这里有个非常关键的细节:SqlSessionFactoryBean实现了Spring的FactoryBean接口,所以你在@Bean方法里可以直接返回SqlSessionFactoryBean,Spring会自动调用getObject()拿到真正的SqlSessionFactory。但如果你写的是:
java复制@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean;
}
这种写法也OK,Spring容器里暴露出来的Bean类型是SqlSessionFactory,因为FactoryBean的泛型决定返回类型。然后你需要额外定义一个SqlSessionTemplate:
java复制@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
没有SqlSessionTemplate的话,MyBatis Spring模块的很多组件(比如MapperFactoryBean)依然可以工作,但如果你在Service层直接注入SqlSessionTemplate,就会因为没有这个Bean而报错。两种Bean至少存在一个,才能让那句“are required”消失。
3.2 当心MyBatis配置类被ComponentScan漏扫
手动配置模式下,配置类本身必须被Spring容器扫描到。如果你的MyBatisConfig放在com.example.config,而<context:component-scan base-package="com.example.service"/>只扫了service包,那配置类完全不会加载。
这种漏扫特别容易发生在拆模块的时候。比如公共模块里放了SqlSessionFactory配置,但主项目扫描的是com.company.app,公共模块是com.company.common,那主项目根本不会去加载公共模块的配置。解决办法是在主项目的扫描路径里加上公共包的路径,或者通过@Import显式引入配置类。
这里有个小经验:传统的spring.xml配置方式里,如果你用了<mybatis:scan>或者<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">,还要特别注意SqlSessionFactory的Bean名称。MapperScannerConfigurer默认会去找名为sqlSessionFactory的Bean。如果Bean名字起得不一样,比如叫mySqlSessionFactory,它会忽略掉,导致报错。手动指定属性可以解决:
xml复制<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory"/>
</bean>
用字符串名称而不是ref引用,目的是避免创建顺序问题。这个坑很细,可能很多老开发都遇到过。
4. 核心Bean定义与源码级理解
4.1 SqlSessionFactory和SqlSessionTemplate到底是什么关系
要彻底摆脱这个报错,光会配置还不够,最好理解一下这两个Bean的关系。MyBatis原生编程里,你通常这样获取会话:
java复制String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);
}
这里SqlSessionFactory负责创建SqlSession,SqlSession是数据库会话,执行SQL、提交事务都靠它。但在Spring环境里,我们不会手动管理SqlSession的生命周期,Spring容器需要的是线程安全的、能自动参与事务的会话管理方案。SqlSessionTemplate就是Spring提供的对SqlSession的代理实现,它实现了SqlSession接口,但内部会把每一次数据库操作委托给当前事务绑定或新建的SqlSession。
你可以把SqlSessionTemplate理解成一个帮Spring和MyBatis“互相翻译”的中间层。它既控着会话的生命周期,又兼容Spring的事务管理。所以MyBatis Spring集成中,生成的Mapper代理最终都会持有SqlSessionTemplate或者SqlSessionFactory。当这两个Bean不存在时,Spring无法创建Mapper代理,于是报错“are required”。
你可能会问:为什么两个只需要有一个?因为有了SqlSessionFactory,MapperFactoryBean会自己创建SqlSessionTemplate。如果直接提供了SqlSessionTemplate,那就不需要再额外创建了。报错信息把两个都列出来,意思是:你至少给我一个,否则我开不了工。
4.2 @MapperScan是怎么帮我们生成Mapper对象的
@MapperScan导入的是MapperScannerRegistrar,这个类会扫描指定包下所有接口,并为每个接口注册一个MapperFactoryBean。MapperFactoryBean是一个FactoryBean,它的目标类型就是接口类型。当Spring需要注入某个Mapper接口时,MapperFactoryBean就会调用getObject()生成一个代理对象。
看一下MapperFactoryBean的源码(3.x版本不完全一样,但核心思路类似):
java复制public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
// 注入时检查
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
// ...
}
}
它继承了SqlSessionDaoSupport,而SqlSessionDaoSupport中有这样一个方法:
java复制public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null) {
this.sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
}
}
看到这里,你应该明白报错的出处了:SqlSessionDaoSupport要求必须设置sqlSessionFactory或sqlSessionTemplate,否则checkDaoConfig()会抛出那句经典的异常。换句话说,MapperFactoryBean在创建之前,Spring必须给它注入一个SqlSessionFactory。如果容器里一个都没有,@MapperScan处理到Mapper接口时就炸了。
所以有时候报错日志会显示Mapped statement路径,实际上炸的锅是:org.mybatis.spring.support.SqlSessionDaoSupport的checkDaoConfig方法里,检查到sqlSessionTemplate == null且sqlSessionFactory == null,然后抛出:
Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
这个源头一找到,排查方向就清清楚楚了:要么没自动配置出工厂,要么自动配置被干扰。
5. 常见问题速查与终极排查清单
5.1 高频报错组合速查表
我在多个团队里帮人排过这个错,下面这些组合非常典型:
| 场景 | 报错现场 | 最可能原因 |
|---|---|---|
| Spring Boot + starter 启动即报错 | Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required | 数据源没有配置,或starter依赖缺失,MyBatis自动配置没有生效 |
| Spring Boot + 自定义配置类 | 启动时提示同一条异常 | 配置类没有被扫描到,或Configuration类里没有定义SqlSessionFactoryBean |
| 多模块项目 | 服务启动成功,但调用Mapper时报这个错 | 公共模块的MyBatis配置没有生效,主项目扫描不到核心Bean |
| Spring Boot 3.x + mybatis starter 2.x | 启动失败,同时出现NoClassDefFoundError | 版本不兼容,自动配置类引用的类不存在 |
| 老Spring项目 + MapperScannerConfigurer | 启动时报这个错 | Bean名称不是默认的sqlSessionFactory,或者漏配数据源 |
这张表不能覆盖所有情况,但可以帮你快速定位方向。如果你的场景不在表里,请直接跳到5.2的逐项排除法。
5.2 一个超实用的“逐项排除”排查顺序
这个顺序是我自己总结的,按着做,能提高定位问题的效率。
第一步,确认依赖。Spring Boot项目先检查mybatis-spring-boot-starter是否存在,且版本和Spring Boot匹配。老项目检查mybatis和mybatis-spring是否都在。
第二步,确认数据源。写一个非常简单的Controller或者在配置类里临时注入DataSource,启动看看能否成功。如果DataSource都注入不了,那是数据源问题,和MyBatis无关。连DataSource都起不来,SqlSessionFactoryBean自然没得吃。
第三步,临时注入SqlSessionFactory。在某个配置类里写:
java复制@Slf4j
@Configuration
public class DebugConfig {
public DebugConfig(SqlSessionFactory factory) {
log.info("SqlSessionFactory loaded: {}", factory != null);
}
}
如果这个配置类能正常启动并打印日志,说明SqlSessionFactory存在。如果启动直接报错说没有这个Bean,那就说明MyBatis自动配置或手动配置没成功。
第四步,检查配置类扫描路径。Spring Boot启动类在com.example.demo,配置类放在com.example.demo.config一般没问题。但如果配置类放在com.example下而启动类在com.example.demo,那扫描路径覆盖不到,配置类加载不了。把启动类用scanBasePackages = "com.example"指定一下,或者把配置类移到子包下。
第五步,检查@MapperScan与Mapper XML路径。如果Mapper接口扫描成功,但XML路径不对,会出现另一个“Invalid bound statement”报错,而不是本文的报错。但为了保险,还是要顺手确认mapper-locations。
第六步,检查是否有多余的@SpringBootApplication(exclude = MybatisAutoConfiguration.class)。有些团队为了自定义配置,会排除MyBatis的自动配置,结果后面又忘了手动定义Bean。如果写了排除,请仔细确认后面有没有补上。
第七步,仍然是本源。如果以上都查了,还是一模一样的报错,大概率是Bean定义确实缺失。你可以全局搜索一下项目里有没有SqlSessionFactoryBean、@MapperScan、MapperScannerConfigurer、SqlSessionTemplate这些关键字。一个都没有,那这就是根源。
5.3 分享几个看着奇怪但实际很常见的原因
除了上面按部就班的排查,有些“特殊”原因我不得不再提一下。
第一,同一个类里既有@Configuration又有@MapperScan,但@Configuration没有被CGLIB代理。如果配置类被final修饰或者方法被final修饰,CGLIB无法代理,@Bean方法不会按预期执行。Spring Boot 2.x和3.x对这种行为的处理不完全一样,但都建议配置类不要设final。
第二,多个项目模块共享同一个Mapper接口和Mapper XML,但各自的数据源不同。这种情况建议每个模块自己声明SqlSessionFactory,不要试图共用。我见过一个项目在两个模块里都扫描了同一个Mapper包,结果Spring Bean冲突,启动时一会儿报这个错,一会儿报BeanName冲突。最后是给每个模块的SqlSessionFactory设置不同的BeanName,并给@MapperScan指定不同的sqlSessionFactoryRef解决。
第三,IDEA缓存导致的自动配置类没有热加载。老版本IDEA偶尔会出现类文件更新但容器还是旧字节码的诡异问题。我试过mvn clean都执行了,还报一模一样的错,最后是在IDEA里执行File > Invalidate Caches / Restart,然后重新导入Maven项目才好。所以如果代码检查一百遍都没问题,可以试一下清缓存。
不过核心思路依然是:这句报错说的是“需要一个Bean”,不是“IDEA坏了”。别把时间浪费在重装IDE上。
6. 我在IDEA里踩过的几个坑和最终心得
说实话,这个报错我最早遇到的时候也懵过。那会刚用IDEA,项目是Spring Boot 1.5 + MyBatis,代码是从同事那里拷的。我引入项目后启动,第一行就是这段异常。当时我怀疑是IDEA没有下载依赖,疯狂点刷新Maven,还去网上找“idea mybatis property sqlSessionFactory”的帖子。后来发现,真正原因是我自己的application.yml文件没有放在resources目录下,而是被IDEA识别成了普通目录,压根没被编译进classpath。
这算是一个IDEA相关的小坑。新导入项目的时候,最好看一下Project Structure里的Modules,确认src/main/resources被标记为了Resources图标,而不是普通文件夹。如果图标不对,Spring Boot不会把配置文件、Mapper XML打包进去。等到运行的时候,数据源配置读不到,MyBatis的Mapped Statement路径也找不到,然后可能就会出现一系列连锁错误,其中就包括“Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required”。
之后我又遇到几次,基本都是因为写公共模块时,顺手把@MapperScan写在了公共jar包里,结果那个jar包被多个服务引用。每个服务如果只扫描了公共包,但没有正确引入MyBatis配置,就会在启动时报这个错。后来我养成的习惯是:MyBatis相关的自动配置一定写在每个可启动服务自己的模块中,公共模块只放Mapper接口和XML,不放配置类。这样各服务能独立控制数据源和工厂,避免互相干扰。
再后来,我调试时学会了一个笨办法:在报错堆栈里找到第一行出现的用户业务代码,而不是只看异常标题。控制台输出的堆栈里,前面几行往往是框架内部信息,真正有用的可能是中间的“at com.example.xxx.XXXConfig.xxxMethod”。沿着这个思路,我能更快定位到是哪个配置类导致的工厂缺失,而不是只盯着那句英文报错。
最后分享一个小技巧:如果你用的是高版本Spring Boot,且只是想快速看一下SqlSessionFactory到底有没有被创建,可以在application.yml里临时打开MyBatis日志:
yaml复制logging:
level:
org.mybatis: debug
org.springframework.boot.autoconfigure: info
启动时你会看到MybatisAutoConfiguration的匹配报告。如果显示Negative matches里有MybatisAutoConfiguration,那说明自动配置没有被激活,赶紧去查starter和数据源。如果显示Positive matches,那就说明工厂Bean确实创建过,问题更可能出在扫描路径或者Bean注入顺序上。
说回那句话,“Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required”,本质就是Spring容器里的“厨师”没到位。你可以按这篇文章的顺序查:依赖、数据源、自动配置、手写配置、扫描路径、缓存。理论上九成以上的问题都能解决。如果排查完还是没头绪,建议直接用最小复现方法,新建一个Spring Boot项目,只加MyBatis starter,配置一个简单数据源,写一个Mapper接口,一步步加回原来的代码。这个方法很土,但往往是最快的排查方式。毕竟这个报错背后可能有几十种原因,但最小项目跑通了,剩下的就是在回归过程中找差异。
