Spring Boot + MyBatis 整合核心:AutoConfiguration 与 MapperScan

2 阅读33分钟

概述

衔接前文

前文《MyBatis 与 Spring 整合原理》已经从 Spring 扩展点的角度深入剖析了 MapperScannerRegistrarMapperFactoryBeanSqlSessionTemplate 等组件是如何让 MyBatis 无缝接入 Spring 容器的。在 Spring Boot 环境下,这些组件的创建和协调被进一步自动化——通过 MybatisAutoConfiguration 与条件装配,开发者只需引入 mybatis-spring-boot-starter 并配置数据源,其余全部由 Boot 接管。本文将正面拆解这一自动配置引擎,揭示它如何根据类路径和已有 Bean 智能决策,以及如何在多数据源等复杂场景下进行优雅的隔离与定制。

总结性引言

在 Spring Boot 体系中,引入 mybatis-spring-boot-starter 后,开发者的工作几乎只剩下编写 Mapper 接口和 SQL。Boot 不仅自动创建了 SqlSessionFactorySqlSessionTemplate 和事务管理器,还通过 @AutoConfigurationPackage 配合 AutoConfiguredMapperScannerRegistrar 自动发现主启动类所在包下的所有 @Mapper 接口,彻底消灭了手动配置 @MapperScan 的需要。这一切的“魔法”都收敛在 MybatisAutoConfiguration 的条件装配逻辑中。本文将逐行拆解这一自动配置类,从 @ConditionalOnClass 的环境感知,到 @EnableConfigurationProperties 的属性绑定,再到 @ConditionalOnMissingBean 的优雅扩展点,为读者呈现 Spring Boot 如何将 MyBatis 的集成变成一件几乎无感却高度可控的事情。

核心要点

  • MybatisAutoConfiguration 条件装配:根据类路径和数据源 Bean 的存在与否自动激活。
  • 基础设施自动创建SqlSessionFactoryBeanSqlSessionFactorySqlSessionTemplate 的自动化链路。
  • 自动 Mapper 扫描AutoConfiguredMapperScannerRegistrar 利用 BasePackages 自动检测 @Mapper 接口。
  • 多数据源隔离@Primary 与命名 Bean 在多个 DataSourceSqlSessionFactory 间的选择策略。
  • 高度可定制ConfigurationCustomizerMybatisProperties 提供的配置入口。

文章组织架构图

flowchart TD
  1[1. Spring Boot 整合 MyBatis 总览<br/>从 Starter 到自动配置] --> 2[2. MybatisAutoConfiguration 源码拆解]
  2 --> 3[3. SqlSessionFactory 与 SqlSessionTemplate<br/>的自动创建]
  3 --> 4[4. MapperScan 的自动扫描<br/>与 Mapper 注解处理]
  4 --> 5[5. 配置属性与定制<br/>MybatisProperties 与 ConfigurationCustomizer]
  5 --> 6[6. 多数据源场景下的<br/>自动化配置隔离]
  6 --> 7[7. 与 Spring 事务的自动集成]
  7 --> 8[8. 生产事故排查专题]
  8 --> 9[9. 面试高频专题]
  1 -.- 2 -.- 3 -.- 4 -.- 5 -.- 6 -.- 7 -.- 8 -.- 9

架构图说明

  • 总览说明:全文9个模块从Boot整合总览开始,逐步深入自动配置、基础设施创建、自动扫描、配置定制、多数据源和事务,最后通过事故和面试完成闭环。
  • 逐模块说明:模块1-2建立自动配置的全局认知;模块3-5揭示各核心Bean的创建原理;模块6-7聚焦高级场景与协作;模块8-9落地排查与应试。
  • 关键结论Spring Boot 通过条件装配和自动配置将 MyBatis 的整合门槛降到了最低。理解 MybatisAutoConfiguration 的决策逻辑是定制和排查 Boot 环境下 MyBatis 问题的根本基础。

1. Spring Boot 整合 MyBatis 总览:从 Starter 到自动配置

1.1 mybatis-spring-boot-starter 依赖拓扑

引入 mybatis-spring-boot-starterpom.xml 中仅做一件事:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>

该 starter 自身是一个空项目,通过 pom.xml 传递以下核心依赖:

  • mybatis-spring-boot-autoconfigure:包含 MybatisAutoConfiguration 及属性绑定类。
  • mybatis-spring:提供 SqlSessionTemplateMapperFactoryBeanMapperScannerConfigurer 等 Spring 整合核心。
  • mybatis:MyBatis 内核。
  • spring-boot-starter-jdbc(依赖传递):提供 DataSource 自动配置、DataSourceTransactionManagerJdbcTemplate 等。

这种组织方式完美实践了 Spring Boot 的“starter 只做依赖聚合,autoconfigure 负责自动配置”设计理念。

1.2 自动配置入口:AutoConfiguration.imports

mybatis-spring-boot-autoconfigureMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中(Spring Boot 2.7.x 新位置),声明了:

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

Spring Boot 启动时,@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 加载该文件并批量处理配置类。MybatisAutoConfiguration 被纳入自动配置候选,随后接受条件注解筛选,最终决定是否注册相关 Bean。

1.3 与传统整合方式的对比

在非 Boot 环境中,整合 MyBatis 需要显式配置:

  • XML 方式:定义 SqlSessionFactoryBeanMapperScannerConfigurer 等 Bean。
  • 纯 Java Config:使用 @Import(MapperScannerRegistrar.class) 或直接 @Bean 定义。

而 Boot 的“零配置”理念下,上述工作完全消失。开发者只需关注 DataSource 和 Mapper 接口,其余全部由 MybatisAutoConfiguration 按条件自动装配。这种机制背后正是 Spring Boot 条件装配与 @ConfigurationProperties 属性绑定的强大能力,也是本文要深挖的核心。


2. MybatisAutoConfiguration 源码拆解

2.1 类级别条件注解:激活的边界

MybatisAutoConfiguration 位于 org.mybatis.spring.boot.autoconfigure 包下,Spring Boot 2.7.x 版本典型源码如下(已简化)。

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
    // ...
}

解读:

  • @ConditionalOnClass:类路径必须同时存在 SqlSessionFactorySqlSessionFactoryBeanSqlSessionFactory 由 MyBatis 内核提供,SqlSessionFactoryBeanmybatis-spring 提供。缺少任一 jar 均不会激活。
  • @ConditionalOnBean(DataSource.class):容器中至少有一个 DataSource Bean。这自然依赖于 DataSourceAutoConfiguration,所以使用 @AutoConfigureAfter 确保数据处理自动配置先行。
  • @EnableConfigurationProperties(MybatisProperties.class):将 application.yml 中以 mybatis 为前缀的属性绑定到 MybatisProperties 实例,并注册到容器。

这种条件装配策略确保了 Boot “有数据源才配 MyBatis”的智能决策。

2.2 基础设施 Bean 的注册

该配置类通过 @Bean 方法创建三大核心基础设施:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setConfiguration(this.properties.getConfiguration());
    // 处理 configLocation、mapperLocations 等属性
    this.applyConfiguration(factory);
    return factory.getObject();
}

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
}

@Bean
@ConditionalOnMissingBean
public DataSourceTransactionManager platformTransactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

要点:

  • @ConditionalOnMissingBean 允许用户通过自定义同名 Bean 覆盖自动配置的任何组件。
  • SqlSessionFactoryBeangetObject() 返回 SqlSessionFactory 实例,利用了 Spring 的 FactoryBean 机制(详见核心容器系列第 16 篇)。
  • SqlSessionTemplate 直接包装第一个(或唯一的)SqlSessionFactory,保证线程安全。
  • 事务管理器同样由数据源驱动,与 Spring 事务抽象无缝结合。

2.3 自动扫描的触发:内部类与 Registrar

Spring Boot 2.x 早期版本直接在 MybatisAutoConfiguration 中通过 @Import(AutoConfiguredMapperScannerRegistrar.class) 触发自动扫描。在 2.7.x 中,该逻辑仍然存在,以内部类或顶层 AutoConfiguredMapperScannerRegistrar 的形式注册,并由 AutoConfigurationPackage 提供基础包路径。这部分将在第 4 节深入。

序列图:MybatisAutoConfiguration 创建核心 Bean 序列图

sequenceDiagram
    participant SC as Spring容器启动
    participant EAC as EnableAutoConfiguration
    participant MAC as MybatisAutoConfiguration
    participant SFB as SqlSessionFactoryBean
    participant ST as SqlSessionTemplate
    participant DTM as DataSourceTransactionManager

    SC->>EAC: 加载 AutoConfiguration.imports
    EAC->>MAC: 条件评估(@ConditionalOnClass/OnBean)
    alt 条件满足
        MAC->>MAC: 绑定 MybatisProperties (application.yml)
        MAC->>DTM: 创建 DataSourceTransactionManager (条件缺失)
        MAC->>SFB: 创建 SqlSessionFactoryBean 并注入 DataSource
        SFB-->>MAC: 返回 SqlSessionFactory
        MAC->>ST: 创建 SqlSessionTemplate(SqlSessionFactory)
        ST-->>MAC: 返回模板实例
        MAC->>MAC: 导入 AutoConfiguredMapperScannerRegistrar
    else 条件不满足
        MAC-->>SC: 跳过自动配置
    end
  • 图表主旨概括:展示 Spring Boot 启动时,MybatisAutoConfiguration 如何根据条件逐步创建 SqlSessionFactorySqlSessionTemplate 和事务管理器。
  • 逐元素分解:首先生效条件注解,然后绑定配置属性,依次检查并创建 DataSourceTransactionManager(由数据源驱动),再通过 SqlSessionFactoryBean 构建核心工厂,接着创建线程安全的 SqlSessionTemplate,最后触发 Mapper 扫描器的自动注册。
  • 设计原理映射@ConditionalOnMissingBean 实现策略模式,允许用户扩展;FactoryBean 负责复杂构建;属性绑定遵循“约定优于配置”。
  • 工程联系与关键结论自动配置的顺序逻辑完全依赖条件注解和 @AutoConfigureAfter,任何对数据源的变更或自定义 Bean 都会影响整个链路,这正是排查问题的起点。

3. SqlSessionFactory 与 SqlSessionTemplate 的自动创建

3.1 SqlSessionFactoryBean 的深度绑定

sqlSessionFactory 方法中,SqlSessionFactoryBean 被赋予两项核心配置:

  1. DataSource:直接注入容器中的 DataSource,若存在多个且没有 @Primary 标注,Boot 会启动失败。
  2. MybatisPropertiesgetConfiguration():该方法返回一个 org.apache.ibatis.session.Configuration 对象,核心逻辑如下(出自 MybatisProperties):
public Configuration getConfiguration() {
    if (this.configuration == null) {
        this.configuration = new Configuration();
        // 将 configuration 子属性映射到 MyBatis Configuration
        PropertyMapper mapper = PropertyMapper.get();
        mapper.from(this::getLogImpl).whenNonNull().to(this.configuration::setLogImpl);
        mapper.from(this::isCacheEnabled).to(this.configuration::setCacheEnabled);
        // ... 其他属性映射
    }
    return this.configuration;
}

属性映射利用 Spring Boot 的 PropertyMapper 工具,将 mybatis.configuration.* 下的设置安全赋值。例如 mybatis.configuration.cache-enabled=true 将设置 Configuration.setCacheEnabled(true)。此外,config-location(指向外部 XML)和 mapper-locations(映射文件路径)则在 applyConfiguration 方法中处理,支持 Resource 模式匹配。

3.2 SqlSessionTemplate 的线程安全与事务同步

SqlSessionTemplate 是 Spring 管理的 SqlSession 代理,内部通过 SqlSessionInterceptor(动态代理)确保每次调用都从 TransactionSynchronizationManager 获取事务绑定的 SqlSession,若不存在则新建并绑定。自动配置将其注册为 @ConditionalOnMissingBean,即若用户自定义了 SqlSessionTemplate,该自动步骤跳过。这极大保留了灵活性。

3.3 自定义覆盖策略演示

场景:用户想使用 MyBatis 的 VFS 自定义实现或添加插件。

@Configuration
public class MyBatisCustomConfig {

    @Bean
    public ConfigurationCustomizer myBatisCustomizer() {
        return configuration -> {
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setDefaultFetchSize(100);
        };
    }

    // 若直接自定义 SqlSessionFactory,可使用如下方式:
    // @Bean
    // public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    //     SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    //     factory.setDataSource(dataSource);
    //     factory.setTypeAliasesPackage("com.example.domain");
    //     return factory.getObject();
    // }
}

由于 @ConditionalOnMissingBean 的存在,用户只需在同名 Bean 中定义即可自动覆盖,无需排除整个 MybatisAutoConfiguration


4. @MapperScan 的自动扫描与 @Mapper 注解处理

4.1 传统 @MapperScan 与 Spring 扩展点

前文(核心容器系列第 16 篇)已详述 MapperScannerRegistrar 通过 @Import 导入,在 registerBeanDefinitions 中注册 MapperScannerConfigurer 实现了指定包路径下的 Mapper 动态代理注册。Spring Boot 在此基础上进行了增强:当用户未显式使用 @MapperScan 时,框架自动进行扫描,进一步降低配置负担。

4.2 AutoConfiguredMapperScannerRegistrar 的触发

MybatisAutoConfiguration 内部通过 @Import(AutoConfiguredMapperScannerRegistrar.class) 自动注册扫描器。源码片段(org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar)如下:

static class AutoConfiguredMapperScannerRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        // 获取 AutoConfigurationPackage 注册的包路径
        List<String> packages = AutoConfigurationPackages.get(registry);
        if (Collections.isEmpty(packages)) {
            return;
        }
        // 构建 ClassPathMapperScanner
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        scanner.doScan(packages.toArray(new String[0]));
    }
}

解读:

  • AutoConfigurationPackages.get(registry):这是 Spring Boot 内部工具,用于获取由 @AutoConfigurationPackage 保存的基础包。该注解本质是 @Import(AutoConfigurationPackages.Registrar.class),会在启动类所在包及子包自动注册。
  • ClassPathMapperScanner:继承自 ClassPathBeanDefinitionScanner,专门用于扫描标记 @Mapper 注解的接口,并将其 BeanDefinition 替换为 MapperFactoryBean(详细过程见核心容器系列第 16 篇)。
  • setAnnotationClass(Mapper.class):明确只扫描 MyBatis 的 @Mapper 注解。

4.3 自动扫描与显式 @MapperScan 的协作

当用户显式使用了 @MapperScan(例如指定特定包),此时容器中会存在 MapperScannerConfigurerMapperFactoryBean 的定义,AutoConfiguredMapperScannerRegistrar 会通过 @ConditionalOnMissingBean 相关条件跳过自动扫描。具体实现是通过在 MybatisAutoConfiguration 中增加条件:

@Configuration
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
@Import(AutoConfiguredMapperScannerRegistrar.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
}

因此,若用户自定义了 @MapperScan 或直接注册了 MapperScannerConfigurer Bean,自动扫描即失效,避免了重复扫描或冲突。

序列图:自动扫描 @Mapper 接口

sequenceDiagram
    participant SC as Spring容器启动
    participant MAC as MybatisAutoConfiguration
    participant AMSR as AutoConfiguredMapperScannerRegistrar
    participant ACP as AutoConfigurationPackages
    participant CPS as ClassPathMapperScanner
    participant MPR as MapperFactoryBean

    SC->>MAC: 条件满足,加载内部配置类
    MAC->>AMSR: @Import 注册 Registrar
    SC->>AMSR: 调用 registerBeanDefinitions()
    AMSR->>ACP: 获取基础包路径 (BasePackages)
    ACP-->>AMSR: 返回 "com.example"
    AMSR->>CPS: 创建 ClassPathMapperScanner
    AMSR->>CPS: setAnnotationClass(Mapper.class)
    AMSR->>CPS: doScan("com.example")
    CPS->>CPS: 扫描classpath下所有@Mapper接口
    loop 每个 Mapper 接口
        CPS->>CPS: 生成 MapperFactoryBean 定义
        CPS->>MPR: 注册到容器
    end
  • 图表主旨概括:展示 Boot 自动扫描机制如何利用 AutoConfigurationPackageImportBeanDefinitionRegistrar 将 Mapper 接口自动纳入 Spring 容器。
  • 逐元素分解:自动配置类通过 @Import 引入 AutoConfiguredMapperScannerRegistrar,该 Registrar 从 AutoConfigurationPackages 取得默认包路径,构建 ClassPathMapperScanner 并设置 @Mapper 为扫描标记,最终执行扫描并将每个接口转换为 MapperFactoryBean
  • 设计原理映射:这是 ImportBeanDefinitionRegistrar 扩展点的典型应用,配合 Spring Boot 的 AutoConfigurationPackage 实现“约定包结构”下的零配置。
  • 工程联系与关键结论只要 Mapper 接口位于主启动类所在包或其子包,并标记 @Mapper,即使没有 @MapperScan,Boot 也会自动注册。这一特性极易被忽略导致‘为什么我的 Mapper 能注入?’的疑惑。

5. 配置属性与定制:MybatisProperties 与 ConfigurationCustomizer

5.1 MybatisProperties 属性全览与源码流向

MybatisProperties 使用 @ConfigurationProperties(prefix = "mybatis") 绑定以下核心属性:

  • configLocation:指定外部 MyBatis XML 配置文件位置,优先级高于其它属性。
  • mapperLocations:映射文件路径,支持 classpath*:mapper/**/*.xml 模式。
  • typeAliasesPackage:别名扫描包。
  • typeHandlersPackage:类型处理器扫描包。
  • configuration 内部属性:logImplcacheEnabledlazyLoadingEnabledmapUnderscoreToCamelCase 等,映射到 org.apache.ibatis.session.Configuration
  • configurationProperties:传递给 MyBatis 配置的额外 properties。

源码片段展示映射逻辑:

public void applyConfiguration(SqlSessionFactoryBean factory) {
    if (this.configLocation != null) {
        factory.setConfigLocation(this.configLocation);
    }
    factory.setConfiguration(this.getConfiguration());
    if (this.mapperLocations != null) {
        factory.setMapperLocations(this.mapperLocations);
    }
    // 其他设置...
}

getConfiguration() 中,Spring Boot 通过 PropertyMapper 将属性逐项拷贝至 Configuration 实例,避免了硬编码。这种将外部配置流向框架 Configuration 的路径是整个自动配置的灵魂。

5.2 ConfigurationCustomizer 扩展回调

ConfigurationCustomizer 是一个函数式接口:

@FunctionalInterface
public interface ConfigurationCustomizer {
    void customize(Configuration configuration);
}

用户可在容器中注册一个或多个 ConfigurationCustomizer Bean,它们会在 SqlSessionFactoryBean 创建最终 SqlSessionFactory 之前被回调,以实现对 MyBatis 配置的精细干预。MybatisAutoConfiguration 内部注入所有 ConfigurationCustomizer 列表,并在 applyConfiguration 中调用:

List<ConfigurationCustomizer> customizers = this.configurationCustomizers;
for (ConfigurationCustomizer customizer : customizers) {
    customizer.customize(factory.getObject().getConfiguration());
}

示例:添加插件、设置默认执行器等。

序列图:属性绑定到 MyBatis Configuration

sequenceDiagram
    participant YML as application.yml
    participant MP as MybatisProperties
    participant MAC as MybatisAutoConfiguration
    participant SFB as SqlSessionFactoryBean
    participant CFG as MyBatis Configuration

    YML->>MP: 绑定 mybatis 前缀属性
    MAC->>MP: 调用 getConfiguration()
    MP->>CFG: 创建 Configuration 实例
    MP->>MP: PropertyMapper 复制属性
    MP-->>MAC: 返回 Configuration
    MAC->>SFB: setConfiguration(cfg)
    SFB->>SFB: 构建时应用自定义器
    SFB->>CFG: 最终 Configuration 生效
    SFB-->>MAC: 返回 SqlSessionFactory
  • 图表主旨概括:说明 application.yml 中的 MyBatis 配置如何一步步传递到 org.apache.ibatis.session.Configuration 实例。
  • 逐元素分解:属性绑定发生在 @EnableConfigurationProperties 之后,自动配置调用 MybatisProperties.getConfiguration() 创建并填充 MyBatis Configuration,然后传递给 SqlSessionFactoryBeanSqlSessionFactoryBean 合并自定义配置,最终构建 SqlSessionFactory
  • 设计原理映射:Spring Boot 的属性映射机制(@ConfigurationProperties)和“回调定制”模式(ConfigurationCustomizer)体现了开放封闭原则。
  • 工程联系与关键结论所有 XML 配置均等价于 YAML 的 mybatis.configuration.* 属性,但 configLocation 优先级最高;利用 ConfigurationCustomizer 可注入任何未暴露为属性的高级设置。

6. 多数据源场景下的自动化配置隔离

6.1 场景与挑战

在实际项目中,经常需要配置多个数据源(例如主库、从库,或不同业务库),并期望不同包下的 Mapper 使用不同数据库连接。Boot 的默认自动配置只针对单个数据源,故多数据源场景通常需要部分或全部排除默认配置。

挑战:

  • 容器中存在多个 DataSource Bean 时,MybatisAutoConfiguration 会因为 @ConditionalOnBean(DataSource.class) 仍然激活,但其 sqlSessionFactory 方法注入 DataSource 会因歧义而启动失败(除非指定 @Primary)。
  • 需要创建多个 SqlSessionFactorySqlSessionTemplate 和独立事务管理器。
  • 必须将相应 Mapper 绑定到特定的 SqlSessionFactory

6.2 手动配置隔离策略

典型的做法是在一个或多个 @Configuration 类中排除 MybatisAutoConfiguration(在 @SpringBootApplication(exclude = MybatisAutoConfiguration.class) 中),并手动构建所需 Bean。同时,利用 @MapperScansqlSessionFactoryRefsqlSessionTemplateRef 精确指定。

实例代码(主/从库):

@Configuration
@MapperScan(basePackages = "com.example.mapper.primary",
            sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {

    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "primarySqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource,
                                               MybatisProperties properties) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setConfiguration(properties.getConfiguration());
        // 可额外设置 mapperLocations 等
        return factory.getObject();
    }

    @Primary
    @Bean(name = "primarySqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sf) {
        return new SqlSessionTemplate(sf);
    }

    @Primary
    @Bean(name = "primaryTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource ds) {
        return new DataSourceTransactionManager(ds);
    }
}

从库配置类似,省略 @Primary,并指定不同的包路径和 Bean 名称。通过 @MapperScansqlSessionFactoryRef 指向各自工厂,实现彻底隔离。事务管理也需使用对应的 TransactionManager@Transactional 需指定 transactionManager 属性。

6.3 @Primary 的作用与陷阱

@Primary 确保在有多个同类型 Bean 时,默认注入(例如无 @QualifierDataSource)会选择该 Bean。若未正确设置 @Primary,默认的自动配置或其它自动连线(如 DataSourceTransactionManager 创建)会因无法确定唯一 Bean 而报错。当主库标记 @Primary 后,任何未显式限定符的注入都将得到主库数据源,因此从库相关的所有 Bean 必须通过 @Qualifier 明确指定。

绑定原理图:多数据源 Mapper 绑定

classDiagram
    class DataSourcePrimary
    class DataSourceSecondary
    
    class SqlSessionFactoryPrimary
    class SqlSessionFactorySecondary
    
    class SqlSessionTemplatePrimary
    class SqlSessionTemplateSecondary
    
    class MapperScanPrimary {
        -sqlSessionFactoryRef = "primarySqlSessionFactory"
    }
    class MapperScanSecondary {
        -sqlSessionFactoryRef = "secondarySqlSessionFactory"
    }

    DataSourcePrimary <.. SqlSessionFactoryPrimary : 注入
    DataSourceSecondary <.. SqlSessionFactorySecondary : 注入
    SqlSessionFactoryPrimary <.. SqlSessionTemplatePrimary
    SqlSessionFactorySecondary <.. SqlSessionTemplateSecondary
    MapperScanPrimary ..> SqlSessionFactoryPrimary : 绑定
    MapperScanSecondary ..> SqlSessionFactorySecondary : 绑定
    note for MapperScanPrimary "包: com.example.mapper.primary"
    note for MapperScanSecondary "包: com.example.mapper.secondary"
  • 图表主旨概括:展示多数据源场景下,如何通过指定 sqlSessionFactoryRef 将不同包下的 Mapper 接口绑定到专属工厂。
  • 逐元素分解:两个独立的 DataSource 分别创建各自的 SqlSessionFactory 和模板,@MapperScan 注解通过引用工厂名称,使得扫描到的接口均使用对应的连接。
  • 设计原理映射:基于 Spring 限定符和命名 Bean 的策略,结合 MyBatis-Spring 的 MapperFactoryBeansqlSessionFactory 引用的支持,实现了完全的上下文隔离。
  • 工程联系与关键结论多数据源下必须显式配置并排除默认自动配置;依靠 @Primary 解决大部分模糊匹配,但务必在 @Transactional 声明中指定对应的事务管理器,否则数据操作会落入错误的数据源。

7. 与 Spring 事务的自动集成

7.1 DataSourceTransactionManager 的自动创建回放

MybatisAutoConfiguration 中:

@Bean
@ConditionalOnMissingBean
public DataSourceTransactionManager platformTransactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

只要容器中没有其他 PlatformTransactionManager Bean,Boot 便基于数据源创建 DataSourceTransactionManager。该事务管理器支持声明式事务 @Transactional

7.2 SqlSessionTemplate 如何感知事务

SqlSessionTemplate 的每个数据库操作都经过 SqlSessionInterceptor 代理,其核心逻辑为:

  1. TransactionSynchronizationManager.getResource(holderKey) 获取当前事务绑定的 SqlSession
  2. 若存在,直接使用并增加引用计数。
  3. 若不存在,从 SqlSessionFactory 打开新 SqlSession,并通过 TransactionSynchronizationManager.bindResource 绑定,注册事务同步器以便在事务提交或回滚时关闭会话。

该机制完全依赖于 Spring 的事务同步管理器,确保在同一事务中多次调用 Mapper 共用同一个数据库连接,无需额外配置。Boot 自动装配的 SqlSessionTemplate 已内置此行为,因此开发者仅需使用 @Transactional 即可获得支持。

7.3 注意区分事务管理器

在多数据源下,必须为每个数据源创建独立的 DataSourceTransactionManager,并在 @Transactional 中明确指定 transactionManager = "xxxTransactionManager",否则默认取 @Primary 的事务管理器,极易导致跨库操作事务失效或数据错乱。


8. 生产事故排查专题

8.1 事故一:升级 Spring Boot 版本后 Mapper 突然无法注入

现象:项目从 Spring Boot 2.5 升级到 2.7 后,原本正常运行的 Mapper 接口注入报 NoSuchBeanDefinitionException,启动失败。

排查思路

  1. 检查 mybatis-spring-boot-starter 版本是否兼容,已升级至 2.3.x。
  2. 查看 MybatisAutoConfiguration 是否激活,发现日志无相关注册信息。
  3. 分析条件注解,发现新版本中增加了 @ConditionalOnBean(DataSource.class)(一直存在,但之前版本可能条件判定有差异)或改变了扫描条件。进一步发现项目中使用了 DataSourceBuilder 创建数据源但未暴露为 Bean,或者数据源的自动配置类执行顺序问题导致 DataSource 未被提前注册。
  4. 实际上,多数情况是因为升级后 DataSourceAutoConfiguration 的某些条件改变导致 DataSource Bean 未创建。经过检查,发现 spring.datasource.url 等配置被放入某些 profile 未激活,导致 DataSource 缺失。

根因MybatisAutoConfiguration 的条件装配严格依赖 DataSource Bean 的存在。Spring Boot 2.7.x 对某些内部条件进行了调整,加上项目配置分离,使得 DataSource 未成功创建,从而连累 MyBatis 自动配置关闭。

解决:补全 spring.datasource 必要属性,或在配置类中主动定义 DataSource Bean。

最佳实践:升级 Boot 版本后,务必完整核对自动配置报告(--debug 启动)中 MyBatis 相关条件的正负匹配情况。理解 @ConditionalOnBean 的本质:只有在容器中确实存在对应 Bean 时才生效。

8.2 事故二:多数据源下写操作误写入从库,导致数据污染

现象:某次版本上线后,发现部分用户数据丢失。排查日志发现更新操作 SQL 执行在从库上(只读),导致业务逻辑未生效,后续操作覆盖了正确数据。

排查思路

  1. 应用配置了主从两个数据源,主库 @Primary,从库未标记。
  2. Mapper 扫描配置:主库 Mapper 包路径正确指向主库 SqlSessionFactory,从库映射只读查询包。
  3. 但业务代码中一个新功能调用了从库包中的 Mapper 进行写操作(代码错误),同时该 Service 方法上有 @Transactional,但未指定事务管理器,默认使用了 @Primary 主库事务管理器。
  4. 然而,问题的严重性在于:由于该 Mapper 实际绑定的 SqlSessionTemplate 是从库工厂,Spring 事务管理虽然开启在主库事务管理器,但 SqlSession 通过 SqlSessionTemplate 获取时使用了从库连接,导致写操作实际发送到从库。

根因:多数据源下,@Transactional 未指定对应的事务管理器,而 Mapper 的 SqlSessionFactory 与事务管理器操作的数据源不一致,Spring 不会自动对齐。Spring 事务仅管理指定事务管理器对应的连接,而 Mapper 却使用了自己的会话,导致事务与数据源脱节。

解决:重构代码,将写操作移动至主库 Mapper 包,或严格指定 @Transactional(transactionManager = "secondaryTransactionManager") 并确保从库具备写权限(但实际不应写)。根本措施:从库数据源应配置为只读,或通过防火墙限制避免此类误用。

最佳实践:多数据源环境必须建立明确的包和代码边界,配合架构约束检查。同时,统一在 @Transactional 中显式指定事务管理器,防止默认行为带来的潜在风险。

序列图:事故排查序列图(多数据源误写)

sequenceDiagram
    participant SVC as 业务Service
    participant TX as 事务拦截器
    participant ST1 as 主库SqlSessionTemplate
    participant ST2 as 从库SqlSessionTemplate
    participant DBS as 数据库从库(只读)

    SVC->>TX: 调用方法(标注@Transactional)
    TX->>TX: 获取主库事务管理器(默认@Primary)
    TX->>ST1: 获取主库连接(未使用,因为调用从库Mapper)
    SVC->>ST2: 调用从库Mapper.update()
    ST2->>DBS: 通过从库连接执行UPDATE
    DBS-->>ST2: 执行成功(意外写入)
    ST2-->>SVC: 返回
    TX->>TX: 提交主库事务(空事务)
    Note over SVC,TX: 写操作绕过主库事务,污染从库
  • 图表主旨概括:直观展示因事务管理器与 Mapper 绑定的 SqlSessionTemplate 数据源不一致导致的误写入从库事故链路。
  • 逐元素分解:事务拦截器基于 @Primary 选择主库事务管理器,但业务代码调用了从库 Mapper。从库 SqlSessionTemplate 独立于事务管理,直接在其数据源上执行 SQL,造成数据污染。
  • 设计原理映射:Spring 声明式事务依靠 TransactionManager 控制连接,但 MyBatis 的 SqlSession 来源由 SqlSessionTemplate 决定,两者之间缺乏自动的统一性检查。
  • 工程联系与关键结论多数据源下务必保持事务管理器与 SqlSessionFactory 的一致性,并在 @Transactional 中显式指定,否则就是“定时炸弹”。

9. 面试高频专题

9.1 Spring Boot 是如何自动配置 MyBatis 的?

标准回答
Spring Boot 通过 mybatis-spring-boot-autoconfigure 模块中的 MybatisAutoConfiguration 实现自动配置。激活条件为类路径存在 SqlSessionFactorySqlSessionFactoryBean,并且 Spring 容器中至少存在一个 DataSource Bean。配置类通过 @EnableConfigurationProperties(MybatisProperties.class) 绑定 mybatis.* 前缀的配置。内部通过三个 @Bean 方法分别创建 SqlSessionFactory(使用 SqlSessionFactoryBean)、SqlSessionTemplateDataSourceTransactionManager,全部标注 @ConditionalOnMissingBean 允许用户覆盖。同时,当容器中不存在 MapperFactoryBeanMapperScannerConfigurer 时,会通过 @Import(AutoConfiguredMapperScannerRegistrar.class) 自动扫描主启动类所在包及其子包下标记 @Mapper 的接口,完成 Mapper 代理注册。

追问 1MybatisAutoConfigurationDataSourceAutoConfiguration 的加载顺序如何保证?
答:MybatisAutoConfiguration 上标注了 @AutoConfigureAfter(DataSourceAutoConfiguration.class),Spring Boot 在排序自动配置类时会根据该注解确保 DataSource 相关配置优先执行,从而满足 @ConditionalOnBean(DataSource.class)

追问 2:如果用户自定义了 SqlSessionFactory,自动扫描的 AutoConfiguredMapperScannerRegistrar 还会生效吗?
答:会生效,因为自动扫描的触发条件(@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}))与 SqlSessionFactory 的自定义独立。只要用户没有显式使用 @MapperScan 或手动注册扫描配置器,自动扫描依然会执行。

追问 3:自动配置中的条件注解评估发生在什么阶段?与 spring.factories 机制的关系是什么?
答:Spring Boot 启动时,AutoConfigurationImportSelector 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件获取候选配置类,然后通过 FilterCondition 接口在 Bean 定义注册前进行评估。条件不满足的配置类直接被跳过,不会创建任何 Bean 定义。这一机制使得不满足环境的自动配置完全不会占用资源。

追问 4:如果在 application.yml 中不配置任何 mybatis 属性,自动配置创建的 SqlSessionFactory 使用的是 MyBatis 的默认配置吗?
答:是的,MybatisProperties.getConfiguration() 将创建一个全新的 org.apache.ibatis.session.Configuration 对象,默认值与 MyBatis 内核一致(如缓存开启、JDBC 日志等)。若需定制,可通过 mybatis.configuration.* 覆盖。

加分回答
可提及 Spring Boot 2.7 将 spring.factories 中的 AutoConfiguration 条目迁移到了专用的 AutoConfiguration.imports 文件,并支持 AutoConfiguration.valueAttribute 等方式。此外,可以结合 MybatisAutoConfiguration 的完整源码,说明 PropertyMapper 如何将 MybatisProperties 的属性安全地复制到 MyBatis Configuration 实例,甚至能处理 null 源值不覆盖的问题。


9.2 MybatisAutoConfiguration 使用了哪些条件注解?各有什么作用?

标准回答
类级别:

  • @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}):校验 MyBatis 核心库和 mybatis-spring 整合库都在类路径下。
  • @ConditionalOnBean(DataSource.class):确保容器中已经注册了至少一个 DataSource Bean。
  • @AutoConfigureAfter(DataSourceAutoConfiguration.class):指定加载顺序,保证数据源先配置。

方法级别:

  • @ConditionalOnMissingBean:在每个 @Bean 方法上使用,保证只有当容器中不存在同类型的 Bean 时,才创建默认的 SqlSessionFactorySqlSessionTemplateDataSourceTransactionManager,实现用户覆盖的扩展点。

追问 1:如果项目中同时引入了 HikariCP 和 Druid,DataSource Bean 会被自动配置成哪个?
答:这取决于 DataSourceAutoConfiguration 的条件匹配顺序,默认使用 HikariCP(如果可用)。即使多个数据源依赖存在,最终只会注册一个 DataSource Bean。MybatisAutoConfiguration 只检测该 Bean 存在性,不关心具体实现。

追问 2@ConditionalOnClass 中的类名可以随意写吗?如果这个类不存在会发生什么?
答:必须真实存在于类路径。Spring Boot 在解析时会尝试用类加载器加载这些类,如果找不到则条件为 false,对应的配置类完全忽略,不会报错。这保证了缺少 jar 包时只是功能缺失,而不会启动失败。

追问 3@ConditionalOnMissingBean 能指定 Bean 名称吗?
答:可以,它提供了 name 属性,如 @ConditionalOnMissingBean(name = "customSqlSessionFactory")。如果不指定,默认按类型匹配。MyBatis 自动配置不加名称,直接按类型覆盖。

加分回答
可以深入讨论 Spring Boot 条件注解的实现原理,比如 SpringBootConditionConditionOutcome,以及如何在 META-INF/spring-autoconfigure-metadata.properties 中预计算条件,以加速启动。同时可以指出 @ConditionalOnBean 的强依赖可能导致 Bean 循环依赖,因此在复杂项目中需要谨慎设计。


9.3 如果在 Boot 项目中不配置 @MapperScan,MyBatis 能自动扫描到 Mapper 吗?

标准回答
可以。MybatisAutoConfiguration 内部定义了 MapperScannerRegistrarNotFoundConfiguration,在满足 @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) 时,会通过 @Import(AutoConfiguredMapperScannerRegistrar.class) 自动注册扫描器。该扫描器从 AutoConfigurationPackages.get(registry) 获取基础包(即主启动类所在包),然后扫描该包及子包下所有标注了 @Mapper 注解的接口,并注册为 MapperFactoryBean

追问 1:如果 Mapper 接口位于其他独立模块的包中,但仍在 Spring Boot 扫描范围内,会被自动扫描吗?
答:是的,只要主启动类的 @SpringBootApplication(内含 @AutoConfigurationPackage)指定的包范围能覆盖到那些接口,就会被自动扫描。如果其他模块的包不在启动类包的子包下,则需要显式使用 @MapperScan 指定。

追问 2:自动扫描只认 @Mapper 注解吗?如果使用自定义注解怎么办?
答:默认 ClassPathMapperScanner 设置的标记注解就是 org.apache.ibatis.annotations.Mapper。如果要扫描自定义注解(如 @MyMapper),必须显式使用 @MapperScan(annotationClass = MyMapper.class),此时自动扫描会因为 MapperScannerConfigurer 的存在而失效。

追问 3:如果容器中已经通过其他方式(如 XML 中定义 MapperFactoryBean)注册了 Mapper,自动扫描还会重复注册吗?
答:不会,自动扫描的条件是 @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})。只要存在任何一个 MapperFactoryBean 类型的 Bean,该条件就不满足,整个自动扫描逻辑被跳过。

加分回答
深入 AutoConfigurationPackages 的源码,它是 Spring Boot 提供的一个工具,底层使用 BeanDefinitionRegistry 存储基础包列表。AutoConfiguredMapperScannerRegistrar 通过 AutoConfigurationPackages.get(registry) 获得这些包,这完全依赖于 @AutoConfigurationPackage 注解的处理。若用户有意不使用主启动类(如测试中),包集合可能为空,自动扫描会静默跳过。理解这一点对排查“为什么我的 Mapper 没自动注册”非常关键。


9.4 如何在使用 Spring Boot 时定制 MyBatis 的全局配置?

标准回答
有三种主要方式:

  1. application.yml 中使用 mybatis.configuration.* 配置项,如 mybatis.configuration.map-underscore-to-camel-case: true,它会通过 MybatisProperties 映射到 MyBatis 的 Configuration 对象。
  2. 实现 ConfigurationCustomizer 接口并注册为 Spring Bean,在 MyBatis 创建 SqlSessionFactory 前会调用所有 ConfigurationCustomizerConfiguration 进行定制,适合无法通过属性暴露的复杂设置(如注册插件、TypeHandler)。
  3. 直接自定义 SqlSessionFactory Bean,因为自动配置方法上有 @ConditionalOnMissingBean,用户提供的 SqlSessionFactory 会完全替代默认的。

追问 1:如果同时使用了 mybatis.configuration 属性和 ConfigurationCustomizer,谁的优先级高?
答:MybatisProperties 先创建并填充 Configuration,然后 ConfigurationCustomizer 再依次定制,后者可以覆盖前者设置的值。同理,若存在 config-location 指定的 XML 配置,其加载顺序也是 XML → MybatisProperties 属性 → ConfigurationCustomizer

追问 2ConfigurationCustomizer 可以被多次调用吗?
答:可以,Spring 容器中可以注册多个 ConfigurationCustomizer Bean,自动配置会通过注入 List<ConfigurationCustomizer> 并遍历调用,执行顺序由 Spring 的 @Order 控制。

追问 3:如何为不同环境(如 dev 和 prod)设置不同的缓存策略?
答:可以在 application-{profile}.yml 中分别设置 mybatis.configuration.cache-enabled 的值;或者编写一个 ConfigurationCustomizer 并注入 Environment 对象,动态读取当前 activeProfiles 来决定是否启用缓存。

加分回答
可提及 Spring Boot 的 EnvironmentPostProcessor 扩展点,在配置源加载之前就动态添加属性,实现更早阶段的配置干预。同时,结合 MybatisPropertiesconfigurationProperties 可以传递额外的 java.util.Properties,供 MyBatis XML 配置文件中的占位符使用,实现高度灵活的配置。


9.5 Boot 下多数据源时,如何为不同的 Mapper 配置不同的 SqlSessionFactory?

标准回答
首要步骤是排除 MybatisAutoConfiguration,避免其默认创建歧义的 SqlSessionFactorySqlSessionTemplate。然后分别定义多个数据源、SqlSessionFactorySqlSessionTemplateDataSourceTransactionManager,并严格使用 @Primary 标注默认的主数据源相关 Bean。接着利用 @MapperScan 注解的 sqlSessionFactoryRefsqlSessionTemplateRef 属性,为不同包路径下的 Mapper 指定不同的工厂或模板。

示例架构:

@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig { ... }

@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig { ... }

追问 1:在多数据源下,@Transactional 需要注意什么?
答:必须显式指定事务管理器。例如 @Transactional(transactionManager = "secondaryTransactionManager"),否则默认使用 @Primary 的事务管理器,可能导致跨数据源操作时事务不匹配,甚至数据写入错误数据库。

追问 2:能不能复用 DataSourceTransactionManager?或者每个数据源必须单独一个?
答:每个数据源必须独立的事务管理器,因为事务管理器内部持有特定数据源,负责从该数据源获取连接并进行事务控制。混用会导致连接混乱。

追问 3:如果 Mapper 之间需要跨库操作怎么办?
答:跨库事务需要使用分布式事务方案(如 JTA),单个 DataSourceTransactionManager 无法处理。Spring Boot 可通过引入 Atomikos 或 Bitronix 并配置 JTA 事务管理器来实现。

加分回答
可深入讨论 MapperFactoryBean 如何通过名称引用 sqlSessionFactory,其内部通过 BeanFactory 根据名称获取对应的工厂实例。同时,使用 @Primary 解决歧义的原理是 Spring 的依赖注入消歧策略,若没有 @Primary 且未指定 @Qualifier,Spring 会抛出 NoUniqueBeanDefinitionException。多数据源场景下,通常建议为每个数据源创建完全隔开的配置模块,甚至使用自定义注解来标记 Mapper 属于哪个数据源。


9.6 SqlSessionTemplate 在 Boot 中是如何被自动创建的?它的线程安全原理是什么?

标准回答
MybatisAutoConfiguration.sqlSessionTemplate() 方法注入唯一的 SqlSessionFactory,并直接 new SqlSessionTemplate(sqlSessionFactory) 返回。SqlSessionTemplate 的线程安全性由内部设计保证:它是一个单例 Bean,但实际数据库操作通过 JDK 动态代理创建的 sqlSessionProxy 进行。每次调用 Mapper 方法时,SqlSessionInterceptor 会根据 TransactionSynchronizationManager 获取当前事务绑定的 SqlSession,如果没有则临时创建,调用后根据是否在事务中决定立即关闭或延迟到事务结束。这样,每个线程实际使用的是独立的 SqlSession 实例,避免了并发问题,同时确保了同一事务内复用会话。

追问 1:如果不存在事务,SqlSessionTemplate 如何管理 SqlSession 生命周期?
答:在非事务环境下,每次执行 SQL 都会新建一个 SqlSession,执行完毕后立即 close(),返回到连接池。这保证了连接及时释放,但每次调用都重复获取连接。

追问 2SqlSessionTemplate 可以被多个 SqlSessionFactory 共享吗?
答:不能,一个 SqlSessionTemplate 对象在构造时必须绑定一个明确的 SqlSessionFactory。如果需要对多个数据源,需要为每个工厂建立对应的 SqlSessionTemplate

追问 3:为什么 SqlSessionTemplate 被称为“模板”,而不是直接使用 DefaultSqlSession
答:模板模式屏蔽了 SqlSession 的获取、关闭、事务同步等复杂细节,使得 DAO 层可以直接注入 SqlSessionTemplate 或通过 Mapper 代理透明使用。它符合 Spring 一贯的模板风格(如 JdbcTemplate)。

加分回答
甚至可以展示 SqlSessionTemplate 构造函数的核心代码:

this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());

以及 SqlSessionInterceptor 中如何通过 SqlSessionUtils.getSqlSession() 与 Spring 事务抽象协作,这体现了代理模式和模板模式的完美结合。


9.7 @ConditionalOnMissingBean 在 MyBatis 自动配置中起到什么作用?

标准回答
它是“约定优于配置,但可灵活覆盖”的核心机制。在 MybatisAutoConfiguration 中,关键的 @Bean 方法(sqlSessionFactorysqlSessionTemplateplatformTransactionManager)都标注了该注解,意思是只有当容器中不存在对应类型的 Bean 时,才自动创建。这允许开发者在自己的 @Configuration 类中定义相同类型的 Bean,完全接管该组件的创建,而无需排除整个自动配置类。这是一种策略模式的应用:默认策略是 Boot 自动提供,用户可以通过注册自己的实现来替换。

追问 1:如果用户自定义了 SqlSessionFactory,但忘记自定义 SqlSessionTemplate,会发生什么?
答:自动配置还会尝试创建默认的 SqlSessionTemplate,但由于此时容器中可能已经有多个 SqlSessionFactory 或自定义的那个没有标识为 @Primary,注入时会发生歧义异常。因此,只要自定义了 SqlSessionFactory,通常也需要配套自定义 SqlSessionTemplate

追问 2@ConditionalOnMissingBean 如何与 @Primary 交互?
答:@ConditionalOnMissingBean 检查的是容器中是否存在对应类型的 Bean,不考虑 @Primary。如果用户定义了两个 SqlSessionFactory,即使其中一个标注了 @Primary,条件仍然判断为“存在”,自动配置的 sqlSessionFactory 不会创建。但其他依赖注入点(如 sqlSessionTemplate 方法参数)会依据 @Primary 进行选择。

追问 3:有没有办法部分覆盖自动配置,比如只改变 SqlSessionFactory 的某一部分而保留其他默认 Bean?
答:有,可以使用 ConfigurationCustomizer 或者暴露自定义 MybatisProperties 的属性,甚至通过 BeanPostProcessor 在初始化完成后修改,但这些属于不推翻自动配置的细粒度定制。若要从根本上替换,则仍需使用 @ConditionalOnMissingBean 的覆盖方式。

加分回答
讨论 Spring Boot 的其他条件注解组合,例如 @ConditionalOnProperty 可以与 @ConditionalOnMissingBean 联合使用,实现基于配置 switch 的动态组件替换。MyBatis 自动配置没有这样做,但用户可以自行扩展。另外,了解 @ConditionalOnMissingBean 的作用域仅限于当前应用上下文,对父子容器无效。


9.8 MybatisProperties 是如何将 YAML 配置映射到 MyBatis 的 Configuration 对象的?

标准回答
MybatisProperties 是一个 @ConfigurationProperties(prefix = "mybatis") 的 POJO,Spring Boot 启动时将 application.yml 中以 mybatis 开头的属性值绑定到它的字段上。需要使用时,MybatisAutoConfiguration 调用 MybatisProperties.getConfiguration() 方法,内部会延迟创建 org.apache.ibatis.session.Configuration 实例,并使用 Spring Boot 的 PropertyMapper 工具将内部 configuration 子属性安全地映射到该实例。例如,mybatis.configuration.log-impl 会映射到 configuration.setLogImpl(...)。最终,这个 Configuration 对象传递给 SqlSessionFactoryBean

追问 1:如果配置了 mybatis.config-location 指向 XML,和 mybatis.configuration 同时存在,哪个生效?
答:XML 文件先被 SqlSessionFactoryBean.setConfigLocation() 加载,随后 setConfiguration() 方法传入 MybatisProperties 创建的 Configuration 对象。MyBatis 内部处理时,后设置的属性会覆盖 XML 中的同名设置,所以 mybatis.configuration 优先级更高。若必须保持 XML,则应避免重复设置。

追问 2MybatisProperties 可以支持未知属性吗?
答:通过 @ConfigurationProperties(ignoreUnknownFields = true)(Spring Boot 默认行为)会忽略未知属性,不会报错。但 Spring Boot 2.x 会在启动时报告位置属性。

追问 3:如果需要将 MyBatis 的配置属性写入一个自定义的 PropertySource,能不能在 @ConfigurationProperties 之前完成?
答:可以,通过 EnvironmentPostProcessorApplicationContextInitializer 在环境准备时添加 PropertySource,提供的属性可以被 MybatisProperties 识别。

加分回答
可以深入 PropertyMapper 源码,PropertyMapper.from(source).whenNonNull().to(target) 这种流式调用可以避免 source 为 null 时覆盖 target 的默认值,是一种优雅的防御性编程。此外,MybatisPropertiesgetConfiguration() 还传递 configurationProperties 字段中的额外属性到 MyBatis 的 Configuration.getVariables() 中,这些变量可在 XML 映射文件中通过 ${} 占位符引用,增强了动态性。


9.9 如果我想让 MyBatis 同时支持 MyBatis 和 MyBatis Plus,Boot 下该如何处理冲突?

标准回答
MyBatis Plus 的 mybatis-plus-boot-starter 也包含了 MybatisPlusAutoConfiguration,它同样会创建 SqlSessionFactory 和扫描 Mapper,会和原生 MybatisAutoConfiguration 冲突。解决办法通常是排除原生 MyBatis 的自动配置,使用 MyBatis Plus 提供的。具体做法:

  • 在启动类上使用 @SpringBootApplication(exclude = MybatisAutoConfiguration.class)
  • 如果使用了 @MapperScan,应替换为 com.baomidou.mybatisplus.annotation.MapperScan(兼容 MyBatis 原 @Mapper)。
  • ConfigurationCustomizer 仍然可以正常使用,因为 MyBatis Plus 的自动配置也会注入它们。

追问 1:如果不想完全排除,能否让两个自动配置共存?
答:理论上可以手动定义两个 SqlSessionFactory 分别对应原生和 Plus,但实际中 MyBatis Plus 的内核扩展了对映射的处理,混合使用极易导致类型处理、分页等问题,不推荐生产应用。

追问 2:MyBatis Plus 的分页插件在 Boot 下如何自动装配?
答:MyBatis Plus 提供了 PaginationInnerInterceptor,通常通过在 ConfigurationCustomizer 中手动添加,或者定义 MybatisPlusInterceptor Bean 自动添加到 SqlSessionFactory

追问 3:如果引入了一个第三方库也依赖 mybatis-spring-boot-starter 但我们需要用 Plus,有什么最佳实践?
答:可以在全局剔除原生 starter 的依赖,或者确保 Plus starter 在依赖树中靠前,Maven 的 exclusions 可以排除传递依赖。然后集中使用 MyBatis Plus 的统一配置。

加分回答
分析 MybatisPlusAutoConfiguration 的源码发现,它实际上扩展了 MybatisAutoConfiguration,重用了许多逻辑。它也通过 AutoConfiguration.imports 注册。通过查看自动配置报告可以清晰看到哪个自动配置生效。如果两者都没被排除,通常会因为 SqlSessionFactory 冲突导致启动失败。


9.10 Boot 环境下,手动开启二级缓存需要注意什么?自动配置是否会干扰?

标准回答
MyBatis 的二级缓存是全局开关与局部声明配合生效。自动配置默认创建的 SqlSessionFactory 使用 MybatisProperties.getConfiguration(),其中 cacheEnabled 默认为 true,所以全局开关是开着的。但要真正使用,还需要在 Mapper XML 中添加 <cache/> 标签或在接口上使用 @CacheNamespace。自动配置不会干扰二级缓存,但需要注意:

  1. 在多数据源下,各 SqlSessionFactory 拥有独立的 Configuration,因此缓存空间隔离,可能造成缓存不一致。
  2. SqlSessionTemplate 管理 SqlSession 的生命周期,二级缓存的生命周期与 SqlSessionFactory 相同,是进程级别的,事务提交后才刷新到缓存,注意缓存刷新操作。
  3. 如果自定义了 SqlSessionFactory,必须显式设置 configuration.setCacheEnabled(true) 并注入自定义缓存实现(如 Redis)。

追问 1mybatis.configuration.cache-enabled=false 能完全禁用二级缓存吗?
答:可以,全局开关关闭后,所有的 XML 中的 <cache> 也会失效,不会再维护缓存。

追问 2:二级缓存和 Spring 事务结合时,缓存更新时机是怎样的?
答:MyBatis 在 SqlSession 关闭时将缓存数据从一级移到二级。使用 SqlSessionTemplate,当没有事务时,每次调用结束后 SqlSession 关闭,缓存立即被更新;在事务中,SqlSession 会被暂存,直到事务提交后关闭,此时才会刷新到二级缓存,保证了事务内的隔离性。

追问 3:如果使用自定义的 SqlSessionTemplate,缓存行为会不会变?
答:只要底层是通过 SqlSessionFactory 获得的 SqlSession,缓存行为不变。自定义模板通常只是改变了获取策略,二级缓存依然由 Configuration 和映射语句控制。

加分回答
深入讨论 MyBatis 二级缓存的 TransactionalCacheManagerTransactionalCache,它们负责暂存缓存并在提交/回滚时清理。Spring 事务同步器会在事务完成后回调,最终触发缓存刷新。如果自定义了缓存实现(如 Hazelcast),需要确保线程安全和序列化支持。


9.11 AutoConfiguredMapperScannerRegistrar 是如何决定扫描范围的?

标准回答
该 Registrar 通过 AutoConfigurationPackages.get(registry) 获取 Spring Boot 自动配置包(即 @AutoConfigurationPackage 注册的包)。@AutoConfigurationPackage 本身通过 @Import(AutoConfigurationPackages.Registrar.class) 在启动时把主启动类所在包存储到 BeanDefinitionRegistry 中。AutoConfiguredMapperScannerRegistrarregisterBeanDefinitions 中拿到这些包路径,然后创建 ClassPathMapperScanner,调用 doScan(packages) 进行类路径扫描。

追问 1:如果项目中有多个 @AutoConfigurationPackage 怎么办?
答:Spring Boot 通常只有一个主启动类,因此只有一个 @AutoConfigurationPackage。如果有多个,AutoConfigurationPackages.get() 会返回所有注册的包,可能会导致重复扫描,但不常见。

追问 2:测试环境下包路径不同,自动扫描会生效吗?
答:测试时,@SpringBootTest 会寻找 @SpringBootConfiguration,通常还是主启动类,所以基本一致。如果使用 @ContextConfiguration 指定特定配置类,可能不会自动注册包,可以通过 @TestPropertySource 或显式 @MapperScan 解决。

追问 3:能否在配置文件中通过属性修改自动扫描的包?
答:自动扫描不支持外部属性配置,但可以通过自定义 @MapperScan 并利用 basePackageClasses 或通过 @MapperScan 的占位符 "${mybatis.base-package}" 实现动态配置,后者是显式 @MapperScan 的功能。

加分回答
深入 ClassPathMapperScannerdoScan 方法,它不仅扫描 @Mapper 接口,还可以通过 registerFilters() 添加对自定义注解的支持。在自动扫描时,它忽略了 @Component 等 Spring 注解,只寻找 @Mapper。这与显式 @MapperScan 的扫瞄逻辑略有差异:显式 @MapperScan 允许通过 annotationClass 属性指定标记注解。


9.12 (系统设计题)设计一个公司内部的 Starter,它要求能够根据不同环境(dev/test/prod)自动切换 MyBatis 的缓存开启状态和日志输出级别,同时支持多租户插件自动装配。请利用 MyBatis 自动配置的扩展点(如 ConfigurationCustomizer),给出核心的配置与实现思路。

标准回答
核心设计思路:

  1. 创建一个自定义 Starter,依赖 mybatis-spring-boot-starter,并包含自定义的自动配置类 MultiEnvMybatisAutoConfiguration
  2. 定义 MultiEnvProperties,使用 @ConfigurationProperties(prefix = "mybatis.enhanced") 绑定配置:devtestprod 对应不同的缓存和日志设置,以及多租户插件开关。
  3. 在自动配置类中,使用 @AutoConfigureBefore(MybatisAutoConfiguration.class),确保在官方配置之前注册必要的 ConfigurationCustomizerEnvironmentPostProcessor(用于提前设定 mybatis.configuration 属性)。
  4. 环境适配实现:通过 EnvironmentPostProcessor 读取当前激活的 profile,将对应的 mybatis.configuration.cache-enabledmybatis.configuration.log-implMapPropertySource 形式添加到 Environment 中(最高优先级),这样 MybatisProperties 会绑定到正确的值。
  5. 多租户插件:实现一个 TenantInterceptor implements Interceptor,在 SQL 执行前根据线程局部变量或上下文(如 Spring Security)拼接租户条件。自动配置类定义 ConfigurationCustomizer Bean,将插件添加到 Configuration 的拦截器链中。通过 @ConditionalOnProperty("mybatis.enhanced.multi-tenant.enabled") 控制是否启用。
  6. 最终用户在项目中引入该 Starter 后,只需在 application.yml 配置环境,即可自动获得对应的缓存和日志策略,多租户插件自动列入。

追问 1:如果用户也想自定义日志实现,会不会与 Starter 的 EnvironmentPostProcessor 冲突?
答:EnvironmentPostProcessor 添加的 PropertySource 通常设置为高优先级,会覆盖 application.yml 的配置,因此用户若想覆盖,必须使用更高优先级的属性源(如系统属性)。也可以设计一个开关属性,例如 mybatis.enhanced.log-override=false 来跳过环境自动配置。

追问 2:多租户插件如何与 MyBatis 的二级缓存协同工作?
答:需要对缓存 key 加入租户 ID 以保证隔离,可以重写缓存 key 的生成方式,或者采用租户隔离的缓存实现(如基于租户划分缓存命名空间)。插件中可以在查询前修改 boundSql,同时也应关注缓存的 key 计算入口。

追问 3:这个 Starter 如何保证在 MybatisAutoConfiguration 之前运行?
答:使用 @AutoConfigureBefore(MybatisAutoConfiguration.class) 可确保自定义自动配置类在 MybatisAutoConfiguration 之前被评估。这样 ConfigurationCustomizer Bean 提前注册,或者添加的 PropertySource 在 MyBatis 属性绑定前就位。

追问 4:如何测试这种 Starter 的自动配置?
答:使用 Spring Boot 的 ApplicationContextRunner 对自动配置进行切片测试,模拟不同的 profile 和用户自定义配置,断言 Configuration 中的属性值和插件存在与否。

加分回答
可以进一步设计 @EnableMultiTenant 注解,通过 @Import 导入一个 ImportBeanDefinitionRegistrar 动态注册多租户拦截器,并提供灵活的租户解析策略接口。这套设计展示了 Spring Boot 的条件装配、EnvironmentPostProcessorConfigurationCustomizer 和自动配置顺序控制等关键扩展点的综合运用,是公司级基础框架的典型方案。


文末速查表

核心组件自动配置类条件核心 Bean定制方式
SqlSessionFactoryMybatisAutoConfiguration@ConditionalOnClass, @ConditionalOnBean(DataSource)SqlSessionFactory@Bean覆盖,ConfigurationCustomizer
SqlSessionTemplateMybatisAutoConfiguration@ConditionalOnMissingBeanSqlSessionTemplate@Bean覆盖
MapperScannerAutoConfiguredMapperScannerRegistrar无MapperFactoryBean/MapperScannerConfigurer时扫描注册MapperFactoryBean使用@MapperScan覆盖
TransactionManagerMybatisAutoConfiguration@ConditionalOnMissingBeanDataSourceTransactionManager@Bean覆盖
属性绑定MybatisProperties@EnableConfigurationPropertiesMybatisProperties在YAML中配置mybatis前缀
多数据源定制手动排除自动配置N/A多组SqlSessionFactory等排除自动配置,手动创建Bean

总结:Spring Boot 与 MyBatis 的整合是条件装配、自动扫描和属性绑定的一场精密协作。掌握 MybatisAutoConfiguration 的内部逻辑,就能在单数据源下享受“零配置”的便利,并在复杂多数据源场景下从容驾驭隔离与调优。