从 IDEA 启动 Spring 项目,控制台突然甩出一行红字:
Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
我第一次看到这个报错时,第一反应是 MyBatis 配置文件写错了,翻出application.yml和 XML 配置逐行检查,结果什么也没改出来。后来才想明白:这个报错根本不是说 SQL 写错,也不是 XML 映射文件找不到,而是 Spring 在创建 Mapper 接口的代理 Bean 时,容器里没有给它注入SqlSessionFactory或SqlSessionTemplate这两个关键属性中的任何一个。
换句话说,MyBatis 已经准备就绪,Spring 也知道有 Mapper 接口要管理,但两者之间的“接线员”没接上。这篇文章就围绕这个报错,从根因、定位、三种可行方案、高频坑点几个方向,结合我在 IDEA 里的实操经验,一次性说透。
1. 先搞清楚报错的本质:谁在要求这两个属性
1.1 报错来自哪里:MapperFactoryBean 的 checkDaoConfig()
这个报错并不是 MyBatis 核心抛出来的,而是 MyBatis 和 Spring 整合包mybatis-spring抛出的。它的出生点在MapperFactoryBean这个类里。
MapperFactoryBean负责把程序员写的 Mapper 接口变成可注入的 Spring Bean,也就是为每个 Mapper 接口生成一个动态代理对象。在生成代理之前,它会调用一个叫checkDaoConfig()的方法检查环境。这个方法里有一段逻辑大致是这样的:
protected void checkDaoConfig() { super.checkDaoConfig(); if (this.sqlSessionFactory == null && this.sqlSessionTemplate == null) { throw new IllegalArgumentException( "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }看到没?它检查的是两个属性:sqlSessionFactory和sqlSessionTemplate。只要这两个至少有一个不为空,检查就能通过;两个都为空,直接抛异常。所以这个报错的本质非常单纯:Spring 创建 Mapper 代理 Bean 时,没能从容器里拿到有效的 MyBatis 会话工厂或会话模板。
1.2 SqlSessionFactory 和 SqlSessionTemplate 到底是什么关系
很多新人会把这两个概念搞混。我说个生活化的类比。
SqlSessionFactory像是数据库连接工厂,它是重量级组件,负责读取 MyBatis 全局配置、绑定数据源、加载 Mapper XML 文件。它创建出来之后,整个应用通常只有一个实例,用来生产SqlSession。
SqlSessionTemplate则是 MyBatis-Spring 整合包提供的一个线程安全类。它包装了SqlSessionFactory,管理 SqlSession 的创建、提交、回滚、关闭,是 Spring 环境下执行 SQL 的核心入口。
可以把SqlSessionFactory理解成发电站,SqlSessionTemplate理解成输电线路。MapperFactoryBean要么直接接发电站的电,要么接输电线路的电,至少得有一样,否则它没法工作。
1.3 为什么同时检查两个:兼容老写法与新写法
有人会问,既然SqlSessionTemplate内部也持有SqlSessionFactory,为什么不干脆只检查SqlSessionFactory一个属性?
这是历史原因。早期整合时,很多项目直接在 Spring XML 里配置MapperFactoryBean,显式传入sqlSessionFactory。后来为了更方便,整合包推出了SqlSessionTemplate,让开发者可以只配置一个 Template,由它内部去关联 Factory。为了兼容两种写法,checkDaoConfig()才会出现“两个属性二选一”的判断逻辑。
所以要解决这个报错,思路就清晰了:给MapperFactoryBean能用的SqlSessionFactory或SqlSessionTemplate即可。关键不在于记住这句报错,而在于搞明白为什么容器里没给它。
1.4 从源码环节理解整条链路
再看一下 Spring 环境下的完整调用链,这对后续排查非常有帮助:
DataSource→SqlSessionFactoryBean生产出SqlSessionFactory→ 需要时包装成SqlSessionTemplate→MapperFactoryBean用SqlSessionFactory或SqlSessionTemplate给 Mapper 接口生成代理对象 → Service 通过@Autowired注入代理对象。
这条链路上任何一环断了,都有可能触发各种怪异的报错。我们现在遇到的这个报错,断点通常出现在“MapperFactoryBean拿不到 Factory 或 Template”这一环。
2. 在 IDEA 里快速定位:别到处乱改配置
2.1 看到报错先回答三个问题
遇到这个报错,不要急着改代码。先在脑子里过三个问题,能帮你迅速缩小范围:
第一,你的项目是 Spring Boot 还是原生 Spring XML 配置?
第二,你是否自定义了SqlSessionFactory、数据源或配置类?
第三,你的 Mapper 接口是靠@Mapper、@MapperScan、还是 XML 里的MapperScannerConfigurer扫描注册的?
这三个问题的答案组合,基本决定了报错的大概率原因。我见过太多人把application.yml翻来覆去改,却忽略了自己手动定义的DataSource已经让 MyBatis 自动配置失效了。
2.2 看启动日志:SqlSessionFactory 到底创建了没有
在 IDEA 的控制台里,项目启动后有一部分日志通常会在很靠前的位置。你可以搜索SqlSessionFactory这几个字。
如果日志里能找到类似“Creating SqlSessionFactory”或 Spring 注册 Factory 的记录,说明 Factory 本身创建成功了。那问题多半出在扫描器没把这个 Factory 喂给 Mapper 代理。
如果日志里连 Factory 的影子都没有,问题很可能是 FactoryBean 根本没被 Spring 加载,或者数据源没配置成功。
这个检查动作非常快,比盯着报错堆栈猜半天有用得多。
2.3 堆栈信息里最重要的几行
IDEA 的 console 会打印出长长的堆栈。别被后面的at com.example...吓住,重点看这两个地方:
- 异常信息正上方通常有
BeanCreationException,说明是创建某个 Bean 时失败了。 - 堆栈里会出现
org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig,这就指向了 Mapper 代理创建环节。
你还可以顺着堆栈找到具体是哪个 Mapper 造成的,比如mapperFactoryBean for [com.example.mapper.UserMapper]。这个信息能帮你确认是全部 Mapper 出问题,还是只有某几个出问题。
2.4 在 IDEA 里检查依赖是否真的在
很多时候报错是因为根本没有引入mybatis-spring,或者版本不对。在 IDEA 右侧 Maven 面板中,双击打开项目的依赖树,搜索mybatis-spring。
如果没有这个依赖,说明整合包没有进来。假如你用的是 Spring Boot,必须加入mybatis-spring-boot-starter才会带上它。假如你用的是原生 Spring,需要在 pom 里显式加入org.mybatis:mybatis-spring。
注意版本匹配问题:Spring Boot 3.x 应配合mybatis-spring-boot-starter3.x,Spring Boot 2.x 则用 2.x 版本。版本错位也容易导致 FactoryBean 无法被正确注册,甚至出现方法签名不匹配的奇怪报错。
3. 三种典型修复方案:从应急到根治
3.1 方案一:原生 Spring XML 漏配 Mapper 扫描器
如果你用原生 Spring + MyBatis,没有 Spring Boot 自动配置,那最常见的场景就是:SqlSessionFactory配好了,但没配MapperScannerConfigurer,导致 Mapper 接口根本没有被扫描注册成 Bean。
这时候需要在 Spring XML 配置文件里加一个扫描器:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath*:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>这里有一个非常重要的细节:MapperScannerConfigurer的sqlSessionFactory属性在低版本设计时允许直接注入,但更稳妥的写法是使用sqlSessionFactoryBeanName。
为什么?因为MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,它会在 Spring 容器的早期阶段执行。这时候如果用<property name="sqlSessionFactory" ref="sqlSessionFactory"/>去强制解析SqlSessionFactoryBean,容易造成循环依赖或者 Bean 实例化顺序问题。而sqlSessionFactoryBeanName只是传一个字符串名称进去,不提前触发 Bean 的实例化,等真正需要时再按名称查找,稳妥得多。
配置完成后,启动项目,IDEA 控制台不会再报这个错,Mapper 接口也能正常注入 Service。
3.2 方案二:Spring Boot 默认路线,把 @MapperScan 用对
在 Spring Boot 项目里,如果没有手动搞复杂的数据源,通常只需要引入mybatis-spring-boot-starter,MyBatis 自动配置就会生效。
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency>然后,在启动类或者任意配置类上加@MapperScan:
@SpringBootApplication @MapperScan("com.example.mapper") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }如果你不想用@MapperScan,也可以在每一个 Mapper 接口上加@Mapper注解:
@Mapper public interface UserMapper { User selectByUsername(String username); }这两种方式有什么区别?@MapperScan一次性把一个包下的所有接口都扫描注册;@Mapper则是逐个标记接口。实际开发里我更推荐@MapperScan,直观、集中、可读性强。
如果你坚持用@Mapper,就务必确认每个接口都加了注解,少加一个,那个 Mapper 仍然会触发类似的问题,只是报错时间更晚,容易绕晕人。
在application.yml中,至少把核心配置写上:
mybatis: mapper-locations: classpath*:mapper/*.xml configuration: map-underscore-to-camel-case: true type-aliases-package: com.example.entitymapper-locations要和你实际的 XML 路径一致。map-underscore-to-camel-case这个配置能避免数据库下划线字段没法自动映射到驼峰属性的问题。type-aliases-package是指定别名包,不写也不影响启动,但如果 XML 里用了简写别名,找不到实体时会出别的报错。
如果你在 Spring Boot 里什么都没有特殊改动,加了这个 Starter 和@MapperScan之后,报错通常就消失了。
3.3 方案三:自定义 SqlSessionFactory 后,手动补齐装配
Spring Boot 项目里,不少人会为了多数据源、定制插件、或者自定义驼峰配置去手动创建SqlSessionFactory。一旦你在某个@Configuration类里自己声明了SqlSessionFactoryBean,MyBatis 的自动配置会因为@ConditionalOnMissingBean(SqlSessionFactory.class)直接失效。
自动配置失效本身没问题,问题在于许多人只声明了 Factory,没有配套把SqlSessionTemplate和 Mapper 扫描也解决了,结果就出现当前报错。
推荐做法是把三件套一起配好。以下是一个单数据源场景下的手动配置类:
@Configuration public class MyBatisConfig { @Resource private DataSource dataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml")); return factory.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate( @Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer scanner = new MapperScannerConfigurer(); scanner.setBasePackage("com.example.mapper"); scanner.setSqlSessionFactoryBeanName("sqlSessionFactory"); return scanner; } }注意sqlSessionFactory()方法里,SqlSessionFactoryBean的getObject()返回的才是真正的SqlSessionFactory。如果你直接return factory;,返回的是SqlSessionFactoryBean本身,Spring 虽然也能处理部分场景,但有些情况下会引发 Bean 类型不匹配的连锁问题。我建议这里还是老老实实调getObject()。
sqlSessionTemplate的方法参数用@Qualifier("sqlSessionFactory")限定名称,防止容器里有多个 Factory 时注入混乱。mapperScannerConfigurer里依然用sqlSessionFactoryBeanName,和方案一同样的道理。
如果在多数据源场景下,你有两个SqlSessionFactory,比如primarySqlSessionFactory和secondarySqlSessionFactory,那需要给其中一个加上@Primary注解,或者分别为不同 Mapper 包配置各自的MapperScannerConfigurer。否则容器里有多个 Factory 时,Spring 不知道该给 Mapper 代理用哪一个,后果同样是“找不到合适的 SqlSessionFactory”。
3.4 三种方案背后的核心机制
这三个方案看起来各不相同,但内在逻辑一致:保证 MapperFactoryBean 创建时能拿到 SqlSessionFactory 或 SqlSessionTemplate。
- 原生 XML 方案,关键是补充
MapperScannerConfigurer,并让它认识已存在的SqlSessionFactory。 - Spring Boot 默认方案,关键是通过 Starter 触发自动配置,再由
@MapperScan完成 Mapper 注册。 - 手动配置方案,关键是把 Factory、Template、Scanner 三件套全部装配完整,避免自动配置失效后出现真空。
只要能理清这层关系,以后遇到类似“Property xxx are required”的报错,第一反应就不会是到处试,而是问自己:这个 Bean 需要什么属性?这些属性在容器里有没有?为什么没有?
4. 高发场景排查:这几个坑特别容易触发该报错
4.1 坑一:引入依赖后感觉“什么都没配”,但自定义了数据源
我见过一个典型例子:某项目用了mybatis-spring-boot-starter,启动类上也加了@MapperScan,平时跑得好好的。后来为了连多个数据库,自己写了一个DataSourceConfig,并把主数据源交给了@Primary修饰的 Bean。结果重新启动,MyBatis 相关 Bean 创建直接崩,报错就是当前这条。
原因很简单:自定义DataSource后,如果项目里还自定义了SqlSessionFactory或影响 MyBatis 自动装配的配置,自动配置可能失效。Spring Boot 的自动配置其实很“看脸色”,它对SqlSessionFactory、SqlSessionTemplate、DataSource都会判断是否已有存在。
解决方式:在自定义数据源的同时,保持 MyBatis 三件套的显式配置,或者确认你的自定义不会导致自动配置里的@ConditionalOnMissingBean失效。
4.2 坑二:MapperScannerConfigurer 的 basePackage 写错
这个坑出现的频率极高。有人把basePackage写成了com.example.mapper.*,或者写成了实体类的包名,导致扫描不到任何接口。这时候 Mapper 接口没有生成代理 Bean,等到 Service 注入时可能出现两种结果:报NoSuchBeanDefinitionException,或者 UnsupportedOperationException;在某些版本和配置组合下,也会出现当前的 Property required 错误。
建议:扫描包必须精确到存放 Mapper 接口的包,不要带着通配符,不要写成父包上一层。如果多个模块,用逗号分隔多个包路径,例如com.example.mapper, com.example.anothermapper。
4.3 坑三:手动 new 了 MapperFactoryBean 却不给属性
有些老项目会手动配置 Mapper Bean,例如:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.example.mapper.UserMapper"/> </bean>这种写法里,如果忘了加sqlSessionFactory或sqlSessionTemplate的 property,报错就是我们现在讨论的这个。解决办法很简单:补上属性引用。不过这种写法现在基本被 MapperScannerConfigurer 或@MapperScan取代了,建议直接换新写法。
4.4 坑四:Spring Boot 3.x 与 mybatis-spring-boot-starter 版本错配
Spring Boot 3.x 基于 Spring Framework 6,Javax 包迁移成了 Jakarta。如果你还在用mybatis-spring-boot-starter2.x,整合包内部引用的还是旧的javax.sql相关 API,可能导致SqlSessionFactoryBean初始化时抛异常,甚至出现类似属性注入失败的问题。
正确做法:Spring Boot 3.x 对应mybatis-spring-boot-starter3.x。建议在 pom 中明确指定版本号,不要交给依赖调级去瞎猜。
4.5 坑五:多个 @Configuration 配置类之间的加载顺序冲突
如果项目里既有个DataConfig又有个MyBatisConfig,两者的加载顺序没有保证时,可能出现SqlSessionFactory还没创建,MapperScannerConfigurer已经抢先读取sqlSessionFactoryBeanName的情况。
解决办法:让 Mapper 扫描器的配置依赖sqlSessionFactoryBeanName的字符串形式,避免强依赖 Bean 创建顺序。优先在启动类上直接@MapperScan,这是顺序最稳妥的一条路,因为启动类上的配置类会被优先处理。
4.6 快速自查速查表
| 场景特征 | 报错前现象 | 最可能原因 | 优先处理方式 |
|---|---|---|---|
| 原生 Spring XML | Factory 配置正常,Mapper 接口没人管 | 缺少 MapperScannerConfigurer | 补扫描器并设置 sqlSessionFactoryBeanName |
| Spring Boot Starter 正常 | 自行声明了 DataSource 或 SqlSessionFactory | 自动配置失效,扫描路径不对 | 手动补 Factory + Template + Scanner 三件套 |
| 多数据源 | 容器里有多个 Factory | Spring 不确定给哪个 | 用 @Primary 或按包拆分扫描器 |
| Mapper 手写 Bean | XML 里直接定义了 MapperFactoryBean | 忘记注入 Factory/Template | 给 Bean 增加对应 property,或改用扫描器 |
| 版本错配 | 整合包初始化期各种异常 | Spring Boot 3 与 mybatis starter 2.x 混用 | 升级到 starter 3.x 并统一 javax/jakarta |
5. 关键配置项对照与经验建议
5.1 核心配置参数速查
| 配置项 | 作用 | 典型值示例 |
|---|---|---|
dataSource | 数据源,FactoryBean 必须指定 | 各种连接池 DataSource Bean |
mapperLocations | Mapper XML 文件路径 | classpath*:mapper/*.xml |
typeAliasesPackage | 实体别名包路径 | com.example.entity |
mapUnderscoreToCamelCase | 下划线转驼峰映射 | true |
sqlSessionFactoryBeanName | MapperScannerConfigurer 指定工厂名称 | sqlSessionFactory |
basePackage | Mapper 接口扫描包 | com.example.mapper |
5.2 推荐从“日志 + 三问”开始排查
我现在的排查套路已经固化了:先搜启动日志里的SqlSessionFactory创建痕迹,然后跑一遍“三问”,再决定是走 Spring Boot 自动配置路线,还是手动三件套方案。这两步做完,基本能把 90% 的同类问题定位明确。
然后再到项目里检查依赖版本、扫描包路径、自定义配置类。整个过程通常不超过十分钟。
5.3 进阶建议:让问题“防患于未然”
位于同一层的配置不要分散在多处。我的习惯是,在项目的核心配置包里专门建一个MybatisConfig,把所有 MyBatis 相关的 Bean 都集中在这里。这样就算出现报错,也能迅速在同一个文件里排查。
另一个建议是把mapper-locations的路径固定下来,比如统一用classpath*:mapper/**/*.xml。如果团队里有人把 XML 放在别的子目录,也不会漏加载。
启动类加上@MapperScan后,Maven 依赖树里确认一下mybatis-spring的版本。这一步能避免大量莫名其妙的启动期诡异报错。
6. 个人经验与最后的话
说到最后,分享一点我在实际项目里的体会。
这个报错看起来像是个“配置缺失”问题,但根子上往往是“装配路径没打通”。像Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required这种 Red Flag,我并不建议死记硬背具体的修复口诀。因为同样一句报错,在 Spring Boot、原生 Spring、多数据源项目里,原因完全不同。你记了一百个方案,不如搞明白一条链路:DataSource → SqlSessionFactory → SqlSessionTemplate → MapperFactoryBean → Mapper 代理。
遇到这类问题,我总会想起一个比喻:MyBatis 的 Mapper 接口就像一部电话,SqlSessionFactory 是电信总局,MapperFactoryBean 是接线员。现在接线员打电话给总局,发现总局电话没人接,自然就喊出了“Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required”。你只需要确认总局存在、号码正确、接线员有总局联系方式即可。
如果让我给一个最省心的起步策略:Spring Boot 项目优先用mybatis-spring-boot-starter+@MapperScan,不要轻易自定义 Factory;非要自定义,就把 Factory、Template、Scanner 三件套写全。只要能记住这个原则,这个报错基本会从此在你的项目里绝迹。