← 返回题目列表

MyBatis 是怎么和 Spring 整合的?@MapperScan、SqlSessionTemplate、事务是怎么协作的?

高频 困难 第 15 / 24 题 更新于 2026/08/03
mybatis-springMapperScanSqlSessionTemplate事务整合

简化版

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,替代手动管理
DataSourceTransactionManagerSpring 事务管理器,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:整合配置和数据源

整合时,SqlSessionFactorySqlSessionFactoryBean 创建,接入 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 的数据源 拼在一起

SqlSessionFactorySqlSessionFactoryBean(FactoryBean) 创建——它把 MyBatis 配置和 Spring 管理的数据源整合(setDataSourcesetMapperLocationssetPlugins 等)。用 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 触发 MapperScannerConfigurerBeanDefinitionRegistryPostProcessor)扫描 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)」。