MyBatis 是怎么和 Spring 整合的?@MapperScan、SqlSessionTemplate、事务是怎么协作的?
简化版
MyBatis 和 Spring 的整合靠 mybatis-spring 这个「桥接库」,它解决三件事:① 把 Mapper 接口变成 Spring 容器里的 Bean(能注入);② 让 SqlSession 线程安全、自动管理;③ 让 MyBatis 的事务跟随 Spring 的 @Transactional。具体:① @MapperScan(或 MapperScannerConfigurer)——扫描 Mapper 接口,为每个接口注册一个 Bean(用 MapperFactoryBean 生成 JDK 动态代理),于是你能 @Autowired 注入 Mapper;② SqlSessionTemplate——一个「线程安全的 SqlSession 代理」,替你管理 SqlSession(每个线程/事务拿到正确的那个),你不用手动 openSession/close;③ 事务整合——mybatis-spring 通过 Spring 的「事务同步」机制,让 MyBatis 用的 SqlSession/连接和 Spring 事务绑定:@Transactional 开启事务后,同一事务内的 Mapper 操作用同一个 SqlSession/同一个连接,@Transactional 提交/回滚时,MyBatis 的操作一起提交/回滚。核心:mybatis-spring 把「Mapper 注入 + SqlSession 管理 + 事务一致」这三件麻烦事自动化了。
详细版
整合的三大组件:
| 组件 | 作用 |
|---|---|
@MapperScan / MapperScannerConfigurer | 扫描 Mapper 接口注册成 Bean |
MapperFactoryBean | 为每个 Mapper 接口生成代理 Bean |
SqlSessionFactoryBean | 创建 SqlSessionFactory(整合配置、数据源) |
SqlSessionTemplate | 线程安全的 SqlSession,替代手动管理 |
DataSourceTransactionManager | Spring 事务管理器,MyBatis 事务跟随它 |
@Configuration
@MapperScan("com.example.mapper") // ① 扫描 Mapper 接口注册成 Bean
@EnableTransactionManagement
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(ds); // 用 Spring 管理的数据源
return fb.getObject();
}
@Bean
public DataSourceTransactionManager txManager(DataSource ds) {
return new DataSourceTransactionManager(ds); // ③ 事务管理器
}
}
@Service
public class UserService {
@Autowired private UserMapper userMapper; // ① Mapper 直接注入
@Transactional // ③ 事务:内部 Mapper 操作同事务
public void transfer() {
userMapper.update(...); // 同一 SqlSession/连接
userMapper.update(...); // 一起提交/回滚
}
}
⚠️ 整合的核心机密是「SqlSession 与 Spring 事务绑定」——没有 Spring 时,你要手动
openSession()→ 操作 →commit()→close(),多个 DAO 操作想在一个事务里,得手动传递同一个 SqlSession。整合后,SqlSessionTemplate通过 Spring 的TransactionSynchronizationManager(事务同步管理器) 做了一件关键事:当前线程如果处在@Transactional开启的事务里,就把 SqlSession(和它的连接)绑定到这个事务上,同一事务内所有 Mapper 操作复用它;@Transactional结束时统一commit/rollback。这就是为什么你在一个@Transactional方法里调好几个 Mapper,它们会在同一个事务里、一起成功或一起失败——不需要你手动传递 SqlSession。
完整版教学
一、整合要解决的三个问题
先明确 mybatis-spring 到底解决什么:
没有整合时,用原生 MyBatis + 手动管理,很麻烦:
问题一:Mapper 怎么用?
要 factory.openSession().getMapper(),拿到的 Mapper 是临时的
→ 没法像 Spring Bean 那样 @Autowired 注入、单例复用
问题二:SqlSession 谁来管?
手动 openSession/close,忘了 close 就连接泄漏
→ 繁琐、易错
问题三:事务怎么办?
手动 commit/rollback;想让多个操作在一个事务里,
要手动传递同一个 SqlSession → 和 Spring 的 @Transactional 割裂
mybatis-spring 的目标:
① 让 Mapper 成为可注入的 Spring Bean
② 自动管理 SqlSession(线程安全、自动关闭)
③ 让 MyBatis 事务跟随 Spring 的 @Transactional
→ 把这三件麻烦事自动化
mybatis-spring 解决三个问题:① Mapper 怎么变成可注入的 Bean(原生的 Mapper 是临时的,没法 @Autowired);② SqlSession 谁管(原生要手动 open/close,繁琐易错);③ 事务怎么和 @Transactional 统一(原生手动 commit、跨操作要手动传 SqlSession)。它的目标是把「Mapper 注入 + SqlSession 管理 + 事务一致」三件麻烦事自动化。理解「mybatis-spring 解决三问题:Mapper 变可注入 Bean、自动管理 SqlSession、事务跟随 @Transactional」,就理解了整合的目标。
二、@MapperScan:让 Mapper 成为 Bean
第一件事是把 Mapper 接口变成 Spring Bean,靠 @MapperScan:
问题:Mapper 是"接口",没有实现类,Spring 怎么把它变成 Bean?
(普通接口没实现类,容器无法实例化)
@MapperScan("com.example.mapper") 的机制:
1. MapperScannerConfigurer(一个 BeanDefinitionRegistryPostProcessor)
扫描指定包下的 Mapper 接口
2. 为每个接口注册一个 BeanDefinition,
但把 beanClass 设成 MapperFactoryBean(一个 FactoryBean)
3. MapperFactoryBean.getObject() 返回该接口的"JDK 动态代理"
(代理背后用 SqlSession 执行 SQL)
→ 于是容器里有了每个 Mapper 接口对应的"代理 Bean"
→ 你就能 @Autowired UserMapper 了
关键点(呼应 BeanDefinition 题):
这是"动态注册 Bean"的经典案例——
没有实现类的接口,靠 FactoryBean 凭空造出代理 Bean
和 Spring Boot 自动配置、动态注册 Bean 是同一套机制
第一件事靠 @MapperScan——它触发 MapperScannerConfigurer(一个 BeanDefinitionRegistryPostProcessor)扫描 Mapper 接口,为每个接口注册一个 BeanDefinition,beanClass 设为 MapperFactoryBean(FactoryBean),由它返回接口的 JDK 动态代理(代理背后用 SqlSession 执行 SQL)。于是容器里有了每个 Mapper 的代理 Bean,你能 @Autowired 注入。这是「没有实现类的接口靠 FactoryBean 凭空造代理 Bean」的经典案例(呼应 BeanDefinition 题的动态注册)。理解「@MapperScan 触发 MapperScannerConfigurer 扫描接口、为每个注册 BeanDefinition(beanClass=MapperFactoryBean)、FactoryBean 返回 JDK 代理、于是 Mapper 可注入」,就理解了第一件事。
三、SqlSessionFactoryBean:整合配置和数据源
整合时,SqlSessionFactory 由 SqlSessionFactoryBean 创建,接入 Spring 的数据源:
SqlSessionFactoryBean(一个 FactoryBean):
负责创建 SqlSessionFactory,并把它整合进 Spring:
- setDataSource(dataSource):用 Spring 管理的数据源
(关键!这样 MyBatis 的连接和 Spring 事务能共享数据源)
- setMapperLocations(...):指定 Mapper XML 位置
- setConfiguration(...) / setConfigLocation(...):MyBatis 配置
- setPlugins(...):注册插件(分页等)
- setTypeAliasesPackage(...):类型别名
为什么用 Spring 的 DataSource:
Spring 的事务管理器(DataSourceTransactionManager)管理这个 DataSource 的连接
MyBatis 也从这个 DataSource 拿连接
→ 两者共享同一个数据源、同一套连接
→ 事务才能统一(下一节)
所以 SqlSessionFactoryBean 是"整合点":
把 MyBatis 的配置 + Spring 的数据源 拼在一起
SqlSessionFactory 由 SqlSessionFactoryBean(FactoryBean) 创建——它把 MyBatis 配置和 Spring 管理的数据源整合(setDataSource、setMapperLocations、setPlugins 等)。用 Spring 的 DataSource 是关键:Spring 的事务管理器管理这个 DataSource 的连接,MyBatis 也从它拿连接,两者共享同一数据源和连接,事务才能统一。所以 SqlSessionFactoryBean 是「MyBatis 配置 + Spring 数据源」的整合点。理解「SqlSessionFactoryBean 创建 SqlSessionFactory、整合 MyBatis 配置和 Spring 数据源、共享数据源是事务统一的前提」,就理解了配置整合。
四、SqlSessionTemplate:线程安全的会话管理
第二件事是自动管理 SqlSession,靠 SqlSessionTemplate:
SqlSessionTemplate 是"线程安全的 SqlSession 代理"(实现 SqlSession 接口):
Mapper 代理背后用的就是它
它怎么做到线程安全 + 自动管理:
每次执行 SQL 时,它不是用一个固定的 SqlSession,而是:
1. 通过 Spring 的 TransactionSynchronizationManager
查"当前线程/当前事务"有没有绑定的 SqlSession
2. 有(在事务里)→ 复用那个(保证同事务用同一个)
3. 没有 → 创建一个新的临时 SqlSession,用完就关
结果:
- 线程安全:每个线程拿到自己的 SqlSession(互不干扰)
- 自动管理:不用手动 openSession/close
- 事务感知:在事务里就复用事务的 SqlSession
所以你注入的 Mapper,底层 SqlSession = SqlSessionTemplate
它替你解决了"SqlSession 线程不安全、要手动管理"的问题
第二件事靠 SqlSessionTemplate(线程安全的 SqlSession 代理)——Mapper 代理背后用它。它每次执行 SQL 时通过 TransactionSynchronizationManager 查当前线程/事务绑定的 SqlSession:在事务里就复用那个(同事务同 SqlSession)、不在就创建临时的用完即关。所以它做到线程安全(每线程各用各的)+ 自动管理(不用手动 open/close)+ 事务感知(事务里复用)。理解「SqlSessionTemplate 是线程安全 SqlSession 代理、按事务同步查当前 SqlSession(事务里复用/否则临时)、线程安全+自动管理+事务感知」,就理解了第二件事。
五、事务整合:SqlSession 与 @Transactional 绑定
第三件事也是最核心的——让 MyBatis 事务跟随 Spring 的 @Transactional:
完整流程(一个 @Transactional 方法里调多个 Mapper):
1. 进入 @Transactional 方法,Spring 事务 AOP 开启事务:
- DataSourceTransactionManager 从 DataSource 拿一个连接
- 把连接设成"手动提交"、绑定到当前线程(事务同步)
2. 方法里调 Mapper:
- SqlSessionTemplate 查到当前线程有事务绑定的连接
- 用这个连接创建/复用 SqlSession
- ★ 所有 Mapper 操作都用这个连接、这个 SqlSession
3. 方法正常结束:
- Spring 事务 AOP 提交事务 → 连接 commit
- MyBatis 的所有操作一起提交
4. 方法抛异常:
- Spring 事务 AOP 回滚 → 连接 rollback
- MyBatis 的所有操作一起回滚
关键:MyBatis 从"Spring 事务绑定的连接"拿连接
→ 所以 MyBatis 的操作天然在 Spring 事务里
→ @Transactional 的提交/回滚 = MyBatis 操作的提交/回滚
★ 前提:SqlSessionFactoryBean 用的 DataSource 和
DataSourceTransactionManager 管的 DataSource 是同一个
(否则连接不共享,事务管不到 MyBatis)
第三件事(最核心)是事务整合:@Transactional 开启事务时,DataSourceTransactionManager 从 DataSource 拿连接、绑定到当前线程;Mapper 操作时,SqlSessionTemplate 用这个事务绑定的连接创建/复用 SqlSession,所以同一事务内所有 Mapper 操作用同一连接、同一 SqlSession;@Transactional 提交/回滚时,连接 commit/rollback,MyBatis 操作一起提交/回滚。前提是 SqlSessionFactoryBean 和事务管理器用同一个 DataSource(否则连接不共享、事务管不到 MyBatis)。理解「事务整合:@Transactional 拿连接绑线程、SqlSessionTemplate 用事务绑定的连接、同事务 Mapper 操作用同一连接一起提交回滚、前提是共享同一 DataSource」,就理解了整合最核心的机制。
六、Spring Boot 下的自动化与常见问题
Spring Boot 里这套整合基本自动化了,但要知道原理和常见坑:
Spring Boot 的自动化(mybatis-spring-boot-starter):
- 自动配置 SqlSessionFactory(用 Spring Boot 的 DataSource)
- 自动配置 SqlSessionTemplate
- @MapperScan 扫描(或用 @Mapper 标注单个接口)
- 自动配置事务管理器
→ 引入 starter + 标 @MapperScan/@Mapper,开箱即用
常见问题:
① Mapper 注入不到(NoSuchBeanDefinition):
忘了 @MapperScan 或 @Mapper,Mapper 没被注册成 Bean
② 事务不生效:
- @Transactional 用在非 public 方法 / 同类自调用(AOP 失效)
- 数据源不一致(SqlSessionFactory 和事务管理器用了不同 DataSource)
- 异常被 catch 没抛出 / 抛的是 checked 异常(默认只回滚 RuntimeException)
③ 多数据源:要为每个数据源配独立的
SqlSessionFactory + SqlSessionTemplate + 事务管理器 + @MapperScan(指定)
排查事务问题先查:AOP 是否生效、数据源是否一致、异常是否传播
Spring Boot 里 mybatis-spring-boot-starter 自动化了这套整合(自动配 SqlSessionFactory/SqlSessionTemplate/事务管理器,@MapperScan/@Mapper 扫描),开箱即用。常见坑:① Mapper 注入不到(忘了 @MapperScan/@Mapper);② 事务不生效(@Transactional AOP 失效如自调用/非 public、数据源不一致、异常被吞或抛 checked 异常默认不回滚);③ 多数据源要为每个配独立的 factory/template/事务管理器/@MapperScan。排查事务先查 AOP 是否生效、数据源是否一致、异常是否传播。理解「Spring Boot starter 自动化整合;坑:Mapper 注入不到(忘@MapperScan)、事务不生效(AOP 失效/数据源不一致/异常被吞或 checked)、多数据源要独立配」,就掌握了实际使用的要点。
记忆钩子:「mybatis-spring 整合解决三件事:①@MapperScan(触发 MapperScannerConfigurer 扫描 Mapper 接口、为每个注册 BeanDefinition beanClass=MapperFactoryBean、返回 JDK 代理)→Mapper 可注入②SqlSessionFactoryBean 创建 SqlSessionFactory 并整合 Spring 数据源③SqlSessionTemplate(线程安全 SqlSession 代理,按 TransactionSynchronizationManager 查当前事务绑定的 SqlSession)自动管理会话;核心-事务整合:@Transactional 拿连接绑线程,SqlSessionTemplate 用事务绑定的连接,同事务 Mapper 操作用同一连接一起提交回滚,前提是 SqlSessionFactory 和事务管理器共享同一 DataSource;坑:忘@MapperScan/AOP 失效/数据源不一致」。
七、常见误区与追问
- 误区:Mapper 接口没有实现类,Spring 没法注入。 能——@MapperScan 触发 MapperScannerConfigurer,为每个 Mapper 接口注册 BeanDefinition(beanClass 设为 MapperFactoryBean),由 FactoryBean 返回接口的 JDK 动态代理,于是接口也成了可注入的 Bean。
- 误区:Spring 里每个 Mapper 操作都新开一个 SqlSession。 不一定——SqlSessionTemplate 通过事务同步查当前线程/事务:在 @Transactional 事务里就复用同一个 SqlSession(同一连接),不在事务里才创建临时的用完即关;这样同事务内多个 Mapper 操作共享 SqlSession。
- 误区:@Transactional 能管住 MyBatis 事务,和数据源无关。 有关键前提——SqlSessionFactoryBean 用的 DataSource 必须和 DataSourceTransactionManager 管理的 DataSource 是同一个;否则 MyBatis 从别的数据源拿连接,Spring 事务管不到它,事务不生效。
- 误区:事务不生效一定是配置问题。 常见原因还有 AOP 失效——@Transactional 用在非 public 方法、同类内部自调用(不走代理);或异常处理不当——异常被 catch 没抛出、或抛的是 checked 异常(@Transactional 默认只对 RuntimeException 回滚);排查要综合看。
- 追问:为什么在一个 @Transactional 方法里调多个 Mapper,它们会在同一个事务里? 因为 @Transactional 开启事务时把连接绑定到当前线程(TransactionSynchronizationManager),Mapper 操作时 SqlSessionTemplate 查到这个事务绑定的连接、用它创建/复用 SqlSession,所有 Mapper 操作都用同一连接、同一 SqlSession,@Transactional 结束时统一提交/回滚。
- 追问:SqlSessionTemplate 是怎么做到线程安全的? 它本身无「固定的 SqlSession 状态」——每次操作时通过 Spring 的事务同步机制拿到「当前线程/事务对应的 SqlSession」(有事务就用事务绑定的、没有就创建临时的用完关);因为每个线程拿到的是自己的 SqlSession,所以单例的 SqlSessionTemplate 被多线程用也线程安全。
- 追问:多数据源下 MyBatis 怎么整合? 为每个数据源配一套独立的:DataSource + SqlSessionFactory(SqlSessionFactoryBean)+ SqlSessionTemplate + DataSourceTransactionManager,并用 @MapperScan 的 sqlSessionTemplateRef/sqlSessionFactoryRef 指定各自扫描的 Mapper 包用哪套;保证每个 Mapper 走对应数据源、事务各自独立。
八、加强记忆
MyBatis 和 Spring 靠 mybatis-spring 桥接库整合,解决三件事:① 把 Mapper 接口变成可注入的 Bean——@MapperScan 触发 MapperScannerConfigurer(BeanDefinitionRegistryPostProcessor)扫描 Mapper 接口,为每个注册 BeanDefinition(beanClass 设为 MapperFactoryBean),由 FactoryBean 返回接口的 JDK 动态代理(没有实现类的接口凭空造代理 Bean,呼应动态注册);② 自动管理 SqlSession——SqlSessionFactoryBean 创建 SqlSessionFactory 并整合 Spring 的数据源;SqlSessionTemplate(线程安全的 SqlSession 代理)通过 TransactionSynchronizationManager 查当前线程/事务绑定的 SqlSession(事务里复用、否则临时用完关)——线程安全 + 自动管理 + 事务感知;③ 事务整合(最核心)——@Transactional 开启事务时 DataSourceTransactionManager 拿连接绑定到当前线程,Mapper 操作时 SqlSessionTemplate 用事务绑定的连接创建/复用 SqlSession,同一事务内所有 Mapper 操作用同一连接、同一 SqlSession,一起提交/回滚;前提是 SqlSessionFactoryBean 和事务管理器共享同一 DataSource。Spring Boot 的 mybatis-spring-boot-starter 自动化了这套。常见坑:Mapper 注入不到(忘 @MapperScan)、事务不生效(AOP 失效/数据源不一致/异常被吞)、多数据源要独立配。一句话「mybatis-spring 整合:@MapperScan 把 Mapper 接口注册成代理 Bean(MapperFactoryBean)可注入、SqlSessionTemplate 线程安全托管 SqlSession(按事务同步)、事务整合让同一 @Transactional 内 Mapper 操作用同一连接一起提交回滚(前提共享同一 DataSource)」。