MyBatis 源碼分析 - MyBatis-Spring 源碼分析


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

該系列文檔是本人在學習 Mybatis 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋(Mybatis源碼分析 GitHub 地址Mybatis-Spring 源碼分析 GitHub 地址Spring-Boot-Starter 源碼分析 GitHub 地址)進行閱讀

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

該系列其他文檔請查看:《精盡 MyBatis 源碼分析 - 文章導讀》

在前面的一系列文檔中對整個 MyBatis 框架進行了分析,相信你對 MyBatis 有了一個更加深入的了解。在使用它的過程中,需要自己創建 SqlSessionFactory 和 SqlSession,然后獲取到 Mapper 接口的動態代理對象,執行數據庫相關操作,對這些對象的管理並不是那么簡單。我們通常會結合 Spring 來使用 MyBatis,將這些對象作為 Spring Bean 注入到 Spring 容器,也允許參與到 Spring 的事務管理之中

Spring 官方並沒有提供對 MyBatis3 的集成方案,於是在 MyBatis 社區將對 Spring 的集成作為一個 MyBatis 子項目 MyBatis-Spring,幫助你將 MyBatis 代碼無縫地整合到 Spring 中,那么我們一起來看看這個子項目是如何集成到 Spring 中的

在開始讀這篇文檔之前,需要對 Spring 有一定的了解,可以結合我的源碼注釋(Mybatis-Spring 源碼分析 GitHub 地址)進行閱讀,MyBatis-Spring官方文檔

簡述

主要涉及到的幾個類:

  • org.mybatis.spring.SqlSessionFactoryBean:實現 FactoryBean、InitializingBean、ApplicationListener 接口,負責構建一個 SqlSessionFactory 對象

  • org.mybatis.spring.mapper.MapperFactoryBean:實現 FactoryBean 接口,繼承 SqlSessionDaoSupport 抽象類,Mapper 接口對應 Spring Bean 對象,用於返回對應的動態代理對象

  • org.mybatis.spring.support.SqlSessionDaoSupport抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 對象

  • org.mybatis.spring.mapper.MapperScannerConfigurer:實現了BeanDefinitionRegistryPostProcessor、InitializingBean接口,ApplicationContextAware、BeanNameAware接口,用於掃描Mapper接口,借助ClassPathMapperScanner掃描器對Mapper接口的BeanDefinition對象(Spring Bean 的前身)進行修改

  • org.mybatis.spring.mapper.ClassPathMapperScanner:繼承了ClassPathBeanDefinitionScanner抽象類,負責執行掃描,修改掃描到的 Mapper 接口的 BeanDefinition 對象(Spring Bean的前身),將其 Bean Class 修改為 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean 類型,實現創建 Mapper 動態代理對象

  • org.mybatis.spring.annotation.MapperScannerRegistrar:實現 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口

    作為@MapperScann 注解的注冊器,根據注解信息注冊一個 MapperScannerConfigurer 對象,用於掃描 Mapper 接口

  • org.mybatis.spring.config.MapperScannerBeanDefinitionParser:實現 BeanDefinitionParser 接口,<mybatis:scan /> 的解析器,和MapperScannerRegistrar的實現邏輯一樣

  • org.mybatis.spring.SqlSessionTemplate:實現 SqlSession 和 DisposableBean 接口,SqlSession 操作模板實現類,承擔 SqlSessionFactory 和 SqlSession 的職責

  • org.mybatis.spring.SqlSessionUtils:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命周期,借助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 對像

大致邏輯如下:

  1. 通過配置 MapperScannerConfigurer 的 Spring Bean,它會結合 ClassPathMapperScanner 掃描器,對指定包路徑下的 Mapper 接口對應 BeanDefinition 對象(Spring Bean 的前身)進行修改,將其 Bean Class 修改為 MapperFactoryBean 類型,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean 對象,實現創建 Mapper 動態代理對象
  2. MapperFactoryBean 對象中getObject()中,根據 SqlSessionTemplate 對象為該 Mapper 接口創建一個動態代理對象,也就是說在我們注入該 Mapper 接口時,實際注入的是 Mapper 接口對應的動態代理對象
  3. SqlSessionTemplate 對象中,承擔 SqlSessionFactory 和 SqlSession 的職責,結合 Spring 的事務體系進行處理

配置示例

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <property name="url" value="${url}" />
    <property name="driverClassName" value="${driver}" />
    <property name="username" value="${username}" />
    <property name="password" value="${password}" />
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- bean的名稱為sqlSessionFactory會出現錯誤 -->
    <property name="dataSource" ref="dataSource" />
    <!-- 引入配置文件 -->
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <!-- 自動掃描mapping.xml文件 -->
    <property name="mapperLocations" value="classpath:com/fullmoon/study/mapping/*.xml" />
</bean>
<!-- DAO接口所在包名,Spring會自動查找其下的類 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.fullmoon.study.dao" />
    <property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory" />
</bean>
  • 上面會創建DruidDataSource數據源,SqlSessionFactoryBeanMapperScannerConfigurer對象

SqlSessionFactoryBean

org.mybatis.spring.SqlSessionFactoryBean:實現 FactoryBean、InitializingBean、ApplicationListener 接口,負責構建一個 SqlSessionFactory 對象

關於Spring的FactoryBean機制,不熟悉的先去了解一下,大致就是Spring在注入該類型的Bean時,調用的是它的getObject()方法

構造方法

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
  private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

  /**
   * 指定的 mybatis-config.xml 路徑的資源
   */
  private Resource configLocation;

  private Configuration configuration;

  /**
   * 指定 XML 映射文件路徑的資源數組
   */
  private Resource[] mapperLocations;

  /**
   * 數據源
   */
  private DataSource dataSource;

  private TransactionFactory transactionFactory;

  private Properties configurationProperties;

  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  /**
   * SqlSession 工廠,默認為 DefaultSqlSessionFactory
   */
  private SqlSessionFactory sqlSessionFactory;

  // EnvironmentAware requires spring 3.1
  private String environment = SqlSessionFactoryBean.class.getSimpleName();

  private boolean failFast;

  private Interceptor[] plugins;

  private TypeHandler<?>[] typeHandlers;

  private String typeHandlersPackage;

  @SuppressWarnings("rawtypes")
  private Class<? extends TypeHandler> defaultEnumTypeHandler;

  private Class<?>[] typeAliases;

  private String typeAliasesPackage;

  private Class<?> typeAliasesSuperType;

  private LanguageDriver[] scriptingLanguageDrivers;

  private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;

  // issue #19. No default provider.
  private DatabaseIdProvider databaseIdProvider;

  private Class<? extends VFS> vfs;

  private Cache cache;

  private ObjectFactory objectFactory;

  private ObjectWrapperFactory objectWrapperFactory;
}

可以看到上面定義的各種屬性,這里就不一一解釋了,根據名稱可以知道屬性的作用

afterPropertiesSet方法

afterPropertiesSet()方法,實現的 InitializingBean 接口,在 Spring 容器中,初始化該 Bean 時,會調用該方法,方法如下:

@Override
public void afterPropertiesSet() throws Exception {
    // 校驗 dataSource 數據源不能為空
    notNull(dataSource, "Property 'dataSource' is required");
    // 校驗 sqlSessionFactoryBuilder 構建器不能為空,上面默認 new 一個對象
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    // configuration 和 configLocation 有且只有一個不為空
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    // 初始化 SqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
}
  1. 校驗 dataSource 數據源不能為空,所以配置該Bean時,必須配置一個數據源
  2. 校驗 sqlSessionFactoryBuilder 構建器不能為空,上面默認 new 一個對象
  3. configuration 和 configLocation 有且只有一個不為空
  4. 調用buildSqlSessionFactory()方法,初始化 SqlSessionFactory

buildSqlSessionFactory方法

buildSqlSessionFactory()方法,根據配置信息構建一個SqlSessionFactory實例,方法如下:

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    // 初始化 Configuration 全局配置對象
    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      // 如果已存在 Configuration 對象
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      // 否則,如果配置了 mybatis-config.xml 配置文件
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      // 否則,創建一個 Configuration 對象
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    /*
     * 如果配置了 ObjectFactory(實例工廠)、ObjectWrapperFactory(ObjectWrapper工廠)、VFS(虛擬文件系統)
     * 則分別往 Configuration 全局配置對象設置
     */
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

    /*
     * 如果配置了需要設置別名的包路徑,則掃描該包路徑下的 Class 對象
     * 往 Configuration 全局配置對象的 TypeAliasRegistry 別名注冊表進行注冊
     */
    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        // 過濾掉匿名類
        .filter(clazz -> !clazz.isAnonymousClass())
        // 過濾掉接口
        .filter(clazz -> !clazz.isInterface())
        // 過濾掉內部類
        .filter(clazz -> !clazz.isMemberClass())
        .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    /*
     * 如果單獨配置了需要設置別名的 Class 對象
     * 則將其往 Configuration 全局配置對象的 TypeAliasRegistry 別名注冊表注冊
     */
    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    // 往 Configuration 全局配置對象添加 Interceptor 插件
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    // 掃描包路徑,往 Configuration 全局配置對象添加 TypeHandler 類型處理器
    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    // 往 Configuration 全局配置對象添加 TypeHandler 類型處理器
    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    // 設置默認的枚舉類型處理器
    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    // 往 Configuration 全局配置對象添加 LanguageDriver 語言驅動
    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    // 設置默認的 LanguageDriver 語言驅動
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    // 設置當前數據源的數據庫 id
    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    // 添加 Cache 緩存
    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        // 如果配置了 mybatis-config.xml 配置文件,則初始化 MyBatis
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    // 設置 Environment 環境信息
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    // 如果配置了 XML 映射文件的路徑
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        /*
         * 遍歷所有的 XML 映射文件
         */
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            // 解析 XML 映射文件
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    // 通過構建器創建一個 DefaultSqlSessionFactory 對象
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

方法有點長,主要是通過配置信息創建一個 Configuration 對象,然后構建一個 DefaultSqlSessionFactory 對象

  1. 初始化 Configuration 全局配置對象

    1. 如果已存在 Configuration 對象,則直接使用該對象
    2. 否則,如果配置了 mybatis-config.xml 配置文件,則創建一個 XMLConfigBuilder 對象,待解析
    3. 否則,創建一個 Configuration 對象
  2. 往 Configuration 對象中設置相關配置屬性

  3. 如果是1.2步生成的 Configuration 對象,那么調用 XMLConfigBuilderparse() 方法進行解析,初始化 MyBatis,在《MyBatis 初始化(一)之加載mybatis-config.xml》中分析過

  4. 如果配置了 XML 映射文件的路徑mapperLocations,則進行遍歷依次解析,通過創建XMLMapperBuilder對象,調用其parse()方法進行解析,在《MyBatis 初始化(二)之加載Mapper接口與映射文件》XMLMapperBuilder小節中分析過

  5. 通過SqlSessionFactoryBuilder構建器創建一個DefaultSqlSessionFactory對象

在 MyBatis-Spring 項目中,除了通過 mybatis-config.xml 配置文件配置 Mapper 接口的方式以外,還提供了幾種配置方法,這里的SqlSessionFactoryBean.mapperLocations也算一種

這樣在 Spring 中,Mapper 接口和對應的 XML 映射文件名稱可以不一致,文件中里面配置 namepace 正確就可以了

getObject方法

getObject()方法,在 Spring 容器中,注入當前 Bean 時調用該方法,也就是返回 DefaultSqlSessionFactory 對象,方法如下:

@Override
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      // 如果為空則初始化 sqlSessionFactory
      afterPropertiesSet();
    }

    // 返回 DefaultSqlSessionFactory 對象
    return this.sqlSessionFactory;
}

onApplicationEvent方法

onApplicationEvent(ApplicationEvent event)方法,監聽 ContextRefreshedEvent 事件,如果還存在未初始化完成的 MapperStatement 們,則再進行解析,方法如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 如果配置了需要快速失敗,並且監聽到了 Spring 容器初始化完成事件
    if (failFast && event instanceof ContextRefreshedEvent) {
      // fail-fast -> check all statements are completed
      // 將 MyBatis 中還未完全解析的對象,在這里再進行解析
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
}

MapperFactoryBean

org.mybatis.spring.mapper.MapperFactoryBean:實現 FactoryBean 接口,繼承 SqlSessionDaoSupport 抽象類,Mapper 接口對應 Spring Bean 對象,用於返回對應的動態代理對象

構造方法

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  /**
   * Mapper 接口
   */
  private Class<T> mapperInterface;

  /**
   * 是否添加到 {@link Configuration} 中,默認為 true
   */
  private boolean addToConfig = true;

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
}
  • mapperInterface:對應的Mapper接口

  • addToConfig:是否添加到 Configuration 中,默認為 true

getObject方法

getObject()方法,在 Spring 容器中,注入當前 Bean 時調用該方法,也就是返回 Mapper 接口對應的動態代理對象,方法如下:

@Override
public T getObject() throws Exception {
    // getSqlSession() 方法返回 SqlSessionTemplate 對象
    return getSqlSession().getMapper(this.mapperInterface);
}

getSqlSession()方法在SqlSessionDaoSupport中定義,返回的是SqlSessionTemplate對象,后續會講到

可以先暫時理解為就是返回一個 DefaultSqlSession,獲取mapperInterfaceMapper接口對應的動態代理對象

這也就是為什么在Spring中注入Mapper接口Bean時,我們可以直接調用它的方法

checkDaoConfig方法

checkDaoConfig()方法,校驗該 Mapper 接口是否被初始化並添加到 Configuration 中

@Override
protected void checkDaoConfig() {
    // 校驗 sqlSessionTemplate 非空
    super.checkDaoConfig();

    // 校驗 mapperInterface 非空
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    /*
     * 如果該 Mapper 接口沒有被解析至 Configuration,則對其進行解析
     */
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 將該 Mapper 接口添加至 Configuration,會對該接口進行一系列的解析
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
}
  1. 校驗 sqlSessionTemplate 非空
  2. 校驗 mapperInterface 非空
  3. 如果該 Mapper 接口沒有被解析至 Configuration,則對其進行解析

因為繼承了DaoSupport抽象類,實現了 InitializingBean 接口,在 afterPropertiesSet() 方法中會調用checkDaoConfig()方法

SqlSessionDaoSupport

org.mybatis.spring.support.SqlSessionDaoSupport抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 對象,代碼如下:

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }

  public final SqlSessionFactory getSqlSessionFactory() {
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
  }

  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

  public SqlSessionTemplate getSqlSessionTemplate() {
    return this.sqlSessionTemplate;
  }

  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}
  • setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)方法,將 SqlSessionFactory 構建成我們需要的 SqlSessionTemplate 對象,該對象在后續講到
  • checkDaoConfig()方法,校驗 SqlSessionTemplate 非空

MapperScannerConfigurer

org.mybatis.spring.mapper.MapperScannerConfigurer:實現了BeanDefinitionRegistryPostProcessor、InitializingBean接口,ApplicationContextAware、BeanNameAware接口

用於掃描Mapper接口,借助ClassPathMapperScanner修改Mapper接口的BeanDefinition對象(Spring Bean 的前身),將Bean的Class對象修改為MapperFactoryBean類型

那么在Spring初始化該Bean的時候,會初始化成MapperFactoryBean類型

構造方法

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

  /**
   * Mapper 接口的包路徑
   */
  private String basePackage;

  /**
   * 是否要將接口添加到 Configuration 全局配置對象中
   */
  private boolean addToConfig = true;

  private String lazyInitialization;

  private SqlSessionFactory sqlSessionFactory;

  private SqlSessionTemplate sqlSessionTemplate;

  private String sqlSessionFactoryBeanName;

  private String sqlSessionTemplateBeanName;

  private Class<? extends Annotation> annotationClass;

  private Class<?> markerInterface;

  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;

  private ApplicationContext applicationContext;

  private String beanName;

  private boolean processPropertyPlaceHolders;

  private BeanNameGenerator nameGenerator;

  private String defaultScope;
  
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

}

SqlSessionFactoryBean小節的示例中可以看到,定義了basePackagesqlSessionFactoryBeanName兩個屬性

  • basePackage:Mapper 接口的包路徑
  • addToConfig:是否要將接口添加到 Configuration 全局配置對象中
  • sqlSessionFactoryBeanName:SqlSessionFactory的Bean Name

afterPropertiesSet()方法中會校驗basePackage非空

在 MyBatis-Spring 項目中,除了通過 mybatis-config.xml 配置文件配置 Mapper 接口的方式以外,還提供了幾種配置方法,這里的配置 basePackage 屬性也算一種

這樣在 Spring 中,Mapper 接口和對應的 XML 映射文件名稱可以不一致,文件中里面配置 namepace 正確就可以了

postProcessBeanDefinitionRegistry方法

postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,在 BeanDefinitionRegistry 完成后進行一些處理

這里會借助ClassPathMapperScanner掃描器,掃描指定包路徑下的 Mapper 接口,方法如下:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      // 處理屬性中的占位符
      processPropertyPlaceHolders();
    }

    // 創建一個 Bean 掃描器
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 是否要將 Mapper 接口添加到 Configuration 全局配置對象中
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    // 設置 SqlSessionFactory 的 BeanName
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    // 添加幾個過濾器
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  1. 如果需要處理屬性中的占位符,則調用processPropertyPlaceHolders()方法
  2. 創建一個 Bean 掃描器 ClassPathMapperScanner 對象
  3. 設置一些 Mapper 接口掃描器的屬性,例如addToConfigsqlSessionFactoryBeanName
  4. 調用掃描器的registerFilters()方法,添加幾個過濾器,過濾指定路徑下的 Mapper 接口
  5. 調用其scan方法,開始掃描 basePackage路徑下的 Mapper 接口

ClassPathMapperScanner

org.mybatis.spring.mapper.ClassPathMapperScanner:繼承了ClassPathBeanDefinitionScanner抽象類

負責執行掃描,修改掃描到的 Mapper 接口的 BeanDefinition 對象(Spring Bean的前身),將其 Bean Class 修改為 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean 類型,實現創建 Mapper 動態代理對象

構造方法

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class);

  /**
   * 是否要將 Mapper 接口添加到 Configuration 全局配置對象中
   */
  private boolean addToConfig = true;

  private boolean lazyInitialization;

  private SqlSessionFactory sqlSessionFactory;

  private SqlSessionTemplate sqlSessionTemplate;

  private String sqlSessionTemplateBeanName;

  /**
   * SqlSessionFactory Bean 的名稱
   */
  private String sqlSessionFactoryBeanName;

  private Class<? extends Annotation> annotationClass;

  private Class<?> markerInterface;

  /**
   * 將 Mapper 接口轉換成 MapperFactoryBean 對象
   */
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

  private String defaultScope;
}
  • basePackage:Mapper 接口的包路徑
  • addToConfig:是否要將接口添加到 Configuration 全局配置對象中
  • sqlSessionFactoryBeanName:SqlSessionFactory的Bean Name

上面這幾個屬性在 MapperScannerConfigurer 創建該對象的時候會進行賦值

registerFilters方法

registerFilters()方法,添加幾個過濾器,用於掃描 Mapper 接口的過程中過濾出我們需要的 Mapper 接口,方法如下:

public void registerFilters() {
    // 標記是否接受所有的 Mapper 接口
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    // 如果配置了注解,則掃描有該注解的 Mapper 接口
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    // 如果配置了某個接口,則也需要掃描該接口
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }

    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      // 如果上面兩個都沒有配置,則接受所有的 Mapper 接口
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    // 排除 package-info.java 文件
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
}
  1. 標記是否接受所有的接口
  2. 如果配置了注解,則添加一個過濾器,需要有該注解的接口
  3. 如果配置了某個接口,則添加一個過濾器,必須是該接口
  4. 如果沒有第23步,則添加一個過濾器,接收所有的接口
  5. 添加過濾器,排除 package-info.java 文件

doScan方法

doScan(String... basePackages)方法,掃描指定包路徑,根據上面的過濾器,獲取路徑下對應的 BeanDefinition 集合,進行一些后置處理,方法如下:

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 掃描指定包路徑,根據上面的過濾器,獲取到路徑下 Class 對象的 BeanDefinition 對象
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      // 后置處理這些 BeanDefinition
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}
  1. 掃描指定包路徑,根據上面的過濾器,獲取到路徑下符合條件的 Resource 資源,並生成對應的 BeanDefinition 對象注冊到 BeanDefinitionRegistry 注冊表中,並返回
  2. 如果不為空,則調用 processBeanDefinitions 方法,進行一些后置處理

processBeanDefinitions方法

processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)方法,對指定包路徑下符合條件的BeanDefinition 對象進行一些處理,修改其 Bean Class 為 MapperFactoryBean 類型,方法如下:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    // <1> 獲取 BeanDefinition 注冊表,然后開始遍歷
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
        // 獲取被裝飾的 BeanDefinition 對象
        definition = (AbstractBeanDefinition) Optional
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
        scopedProxy = true;
      }
      // <2> 獲取對應的 Bean 的 Class 名稱,也就是 Mapper 接口的 Class 對象
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      // <3> 往構造方法的參數列表中添加一個參數,為當前 Mapper 接口的 Class 對象
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      /*
       * <4> 修改該 Mapper 接口的 Class對象 為 MapperFactoryBean.class
       * 這樣一來當你注入該 Mapper 接口的時候,實際注入的是 MapperFactoryBean 對象,構造方法的入參就是 Mapper 接口
       */
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // <5> 添加 addToConfig 屬性
      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      // <6> 開始添加 sqlSessionFactory 或者 sqlSessionTemplate 屬性
      /*
       * 1. 如果設置了 sqlSessionFactoryBeanName,則添加 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 對象
       * 2. 否則,如果配置了 sqlSessionFactory 對象,則添加 sqlSessionFactory 屬性
       */
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      /*
       * 1. 如果配置了 sqlSessionTemplateBeanName,則添加 sqlSessionTemplate 屬性
       * 2. 否則,如果配置了 sqlSessionTemplate 對象,則添加 sqlSessionTemplate 屬性
       * SqlSessionFactory 和 SqlSessionTemplate 都配置了則會打印一個警告
       */
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          // 如果上面已經清楚的使用了 SqlSessionFactory,則打印一個警告
          LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        // 添加 sqlSessionTemplate 屬性
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      /*
       * 上面沒有找到對應的 SqlSessionFactory,則設置通過類型注入
       */
      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }

      definition.setLazyInit(lazyInitialization);

      if (scopedProxy) {
        // 已經封裝過的則直接執行下一個
        continue;
      }

      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }

      /*
       * 如果不是單例模式,默認是
       * 將 BeanDefinition 在封裝一層進行注冊
       */
      if (!definition.isSingleton()) {
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
          registry.removeBeanDefinition(proxyHolder.getBeanName());
        }
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
      }

    }
}
  1. 獲取 BeanDefinition 注冊表,然后開始遍歷

  2. 獲取對應的 Bean 的 Class 名稱,也就是 Mapper 接口的 Class 對象

  3. 往構造方法的參數列表中添加一個參數,為當前 Mapper 接口的名稱,因為 MapperFactoryBean 的構造方法的入參就是 Mapper 接口

  4. 修改該 Mapper 接口的 Class 對象 為 MapperFactoryBean,根據第3步則會為該 Mapper 接口創建一個對應的 MapperFactoryBean 對象了

  5. 添加 addToConfig 屬性,Mapper 是否添加到 Configuration 中

  6. 開始添加 sqlSessionFactory 或者 sqlSessionTemplate 屬性

    1. 如果設置了 sqlSessionFactoryBeanName,則添加 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 對象,

      否則,如果配置了 sqlSessionFactory 對象,則添加 sqlSessionFactory 屬性

      SqlSessionDaoSupportsetSqlSessionFactory(SqlSessionFactory sqlSessionFactory)方法中你會發現創建的就是SqlSessionTemplate 對象

    2. 如果配置了 sqlSessionTemplateBeanName,則添加 sqlSessionTemplate 屬性

      否則,如果配置了 sqlSessionTemplate 對象,則添加 sqlSessionTemplate 屬性

    3. 上面沒有找到對應的 SqlSessionFactory,則設置通過類型注入

該方法的處理邏輯大致如上描述,主要做了一下幾個事:

  • 將 Mapper 接口的 BeanDefinition 對象的 beanClass 屬性修改成了MapperFactoryBean的 Class 對象

  • 添加了一個入參為 Mapper 接口,這樣初始化的 Spring Bean 就是該 Mapper 接口對應的 MapperFactoryBean 對象了

  • 添加MapperFactoryBean 對象的sqlSessionTemplate屬性

@MapperScan注解

org.mybatis.spring.annotation.@MapperScan 注解,指定需要掃描的包,將包中符合條件的 Mapper 接口,注冊成 beanClass 為 MapperFactoryBean 的 BeanDefinition 對象,從而實現創建 Mapper 對象

我們代碼如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

  String[] value() default {};

  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};

  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  Class<? extends Annotation> annotationClass() default Annotation.class;

  Class<?> markerInterface() default Class.class;

  String sqlSessionTemplateRef() default "";

  String sqlSessionFactoryRef() default "";


  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

  String lazyInitialization() default "";

  String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;

}
  • valuebasePackage都是指定 Mapper 接口的包路徑
  • @Import(MapperScannerRegistrar.class),該注解負責資源的導入,如果導入的是一個 Java 類,例如此處為 MapperScannerRegistrar 類,Spring 會將其注冊成一個 Bean 對象

MapperScannerRegistrar

org.mybatis.spring.annotation.MapperScannerRegistrar:實現 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口

作為@MapperScann 注解的注冊器,根據注解信息注冊一個 MapperScannerConfigurer 對象,用於掃描 Mapper 接口

registerBeanDefinitions方法

registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 獲得 @MapperScan 注解信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        // 生成 Bean 的名稱,'org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0'
        generateBaseBeanName(importingClassMetadata, 0));
    }
}
  1. 獲得 @MapperScan 注解信息
  2. 調用generateBaseBeanName方法,為MapperScannerConfigurer生成一個beanName:org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0
  3. 調用registerBeanDefinitions重載方法,注冊一個類型為MapperScannerConfigurer的 BeanDefinition 對象

registerBeanDefinitions重載方法

registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)方法

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
  BeanDefinitionRegistry registry, String beanName) {
    // 創建一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 對象
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // 添加是否處理屬性中的占位符屬性
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    /*
     * 依次添加注解中的配置屬性
     */
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    /*
     * 獲取到配置的 Mapper 接口的包路徑
     */
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    /*
     * 如果沒有 Mapper 接口的包路徑,則默認使用注解類所在的包路徑
     */
    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    // 添加 Mapper 接口的包路徑屬性
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // 往 BeanDefinitionRegistry 注冊表注冊 MapperScannerConfigurer 對應的 BeanDefinition 對象
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}
  1. 創建一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 對象
  2. 添加是否處理屬性中的占位符屬性processPropertyPlaceHolders
  3. 依次添加@MapperScan注解中的配置屬性,例如:sqlSessionFactoryBeanNamebasePackages
  4. 往 BeanDefinitionRegistry 注冊表注冊 MapperScannerConfigurer 類型的 BeanDefinition 對象

這樣在 Spring 容器初始化的過程中,會創建一個 MapperScannerConfigurer 對象,然后回到MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法中,對包路徑下的 Mapper 接口進行解析,前面已經分析過了

RepeatingRegistrar內部類

MapperScannerRegistrar的內部類,代碼如下:

static class RepeatingRegistrar extends MapperScannerRegistrar {
    /**
     * {@inheritDoc}
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      AnnotationAttributes mapperScansAttrs = AnnotationAttributes
          .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
      if (mapperScansAttrs != null) {
        // 獲取 MapperScan 注解數組
        AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
        /*
         * 依次處理每個 MapperScan 注解
         */
        for (int i = 0; i < annotations.length; i++) {
          registerBeanDefinitions(importingClassMetadata, annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
        }
      }
    }
}
  • 可以回到@MapperScan注解上面的@Repeatable(MapperScans.class)信息,可以看到如果同一個類上面定義多個@MapperScan注解,則會生成對應的@MapperScans注解
  • RepeatingRegistrar 用於處理@MapperScans注解,依次處理@MapperScan注解的信息
  • 和 MapperScannerRegistrar 一樣的處理方式,不過生成的多個 MapperScannerConfigurer 對應的 beanName 的后綴不一樣

自定義 <mybatis:scan /> 標簽

除了配置 MapperScannerConfigurer 對象和通過 @MapperScan 注解掃描 Mapper 接口以外,我們還可以通過 MyBatis 提供的 scan 標簽來掃描 Mapper 接口

示例

<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

spring.schemas

META-INF/spring.schemas 定義如下:

http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring.xsd
http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring.xsd
  • xmlns 為 http://mybatis.org/schema/mybatis-spring-1.2.xsdhttp://mybatis.org/schema/mybatis-spring.xsd
  • xsd 為 mybatis-spring.xsd

spring.handler

META-INF/spring.handlers 定義如下:

http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler
  • 定義了 MyBatis 的 XML Namespace 的處理器 NamespaceHandler 對象

NamespaceHandler

org.mybatis.spring.config.NamespaceHandler:繼承 NamespaceHandlerSupport 抽象類,MyBatis 的 XML Namespace 的處理器,代碼如下:

public class NamespaceHandler extends NamespaceHandlerSupport {

  /**
   * {@inheritDoc}
   */
  @Override
  public void init() {
    registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
  }

}
  • <mybatis:scan /> 標簽,使用 MapperScannerBeanDefinitionParser 解析

MapperScannerBeanDefinitionParser

org.mybatis.spring.config.MapperScannerBeanDefinitionParser:實現 BeanDefinitionParser 接口,<mybatis:scan /> 的解析器,代碼如下:

public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionParser {

  private static final String ATTRIBUTE_BASE_PACKAGE = "base-package";
  private static final String ATTRIBUTE_ANNOTATION = "annotation";
  private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface";
  private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator";
  private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref";
  private static final String ATTRIBUTE_FACTORY_REF = "factory-ref";
  private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class";
  private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization";
  private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope";

  /**
   * 這個方法和 {@link MapperScannerRegistrar} 一樣的作用
   * 解析 <mybatis:scan /> 標簽中的配置信息,設置到 MapperScannerConfigurer 的 BeanDefinition 對象中
   * {@inheritDoc}
   *
   * @since 2.0.2
   */
  @Override
  protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    // 創建一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 對象
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

    ClassLoader classLoader = ClassUtils.getDefaultClassLoader();

    // 添加是否處理屬性中的占位符屬性
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    /*
     * 解析 `scan` 標簽中的配置,添加到 BeanDefinition 中
     */
    try {
      String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION);
      if (StringUtils.hasText(annotationClassName)) {
        @SuppressWarnings("unchecked")
        Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) classLoader
            .loadClass(annotationClassName);
        builder.addPropertyValue("annotationClass", annotationClass);
      }
      String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE);
      if (StringUtils.hasText(markerInterfaceClassName)) {
        Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName);
        builder.addPropertyValue("markerInterface", markerInterface);
      }
      String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR);
      if (StringUtils.hasText(nameGeneratorClassName)) {
        Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName);
        BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class);
        builder.addPropertyValue("nameGenerator", nameGenerator);
      }
      String mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS);
      if (StringUtils.hasText(mapperFactoryBeanClassName)) {
        @SuppressWarnings("unchecked")
        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = (Class<? extends MapperFactoryBean>) classLoader
            .loadClass(mapperFactoryBeanClassName);
        builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
      }
    } catch (Exception ex) {
      XmlReaderContext readerContext = parserContext.getReaderContext();
      readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
    }

    builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF));
    builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF));
    builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION));
    builder.addPropertyValue("defaultScope", element.getAttribute(ATTRIBUTE_DEFAULT_SCOPE));
    builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE));

    return builder.getBeanDefinition();
  }

  /**
   * {@inheritDoc}
   *
   * @since 2.0.2
   */
  @Override
  protected boolean shouldGenerateIdAsFallback() {
    return true;
  }

}
  • 代碼的實現邏輯MapperScannerRegistrar一致,創建MapperScannerConfigurer對應的BeanDefinition對象,然后去解析<mybatis:scan /> 標簽中的配置信息

SqlSessionTemplate

org.mybatis.spring.SqlSessionTemplate:實現 SqlSession 和 DisposableBean 接口,SqlSession 操作模板實現類

實際上,代碼實現和 org.apache.ibatis.session.SqlSessionManager 相似,承擔 SqlSessionFactory 和 SqlSession 的職責

構造方法

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  /**
   * a factory of SqlSession
   */
  private final SqlSessionFactory sqlSessionFactory;

  /**
   * {@link Configuration} 中默認的 Executor 執行器類型,默認 SIMPLE
   */
  private final ExecutorType executorType;

  /**
   * SqlSessionInterceptor 代理對象
   */
  private final SqlSession sqlSessionProxy;

  /**
   * 異常轉換器,MyBatisExceptionTranslator 對象
   */
  private final PersistenceExceptionTranslator exceptionTranslator;

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  }

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  }

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    // 創建一個 SqlSession 的動態代理對象,代理類為 SqlSessionInterceptor
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }
}
  • sqlSessionFactory:用於創建 SqlSession 對象
  • executorType:執行器類型,創建 SqlSession 對象時根據它創建對應的 Executor 執行器,默認為
  • sqlSessionProxy:SqlSession 的動態代理對象,代理類為 SqlSessionInterceptor
  • exceptionTranslator:異常轉換器

在調用SqlSessionTemplate中的SqlSession相關方法時,內部都是直接調用sqlSessionProxy動態代理對象的方法,我們來看看是如何處理的

SqlSessionInterceptor

SqlSessionTemplate的內部類,實現了 InvocationHandler 接口,作為sqlSessionProxy動態代理對象的代理類,對 SqlSession 的相關方法進行增強

代碼如下:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // <1> 獲取一個 SqlSession 對象
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 執行 SqlSession 的方法
        Object result = method.invoke(sqlSession, args);
        // 當前 SqlSession 不處於 Spring 托管的事務中
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          // 強制提交
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          // 關閉 SqlSession 會話,釋放資源
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          // 對異常進行轉換,差不多就是轉換成 MyBatis 的異常
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
}
  1. 調用SqlSessionUtilsgetSqlSession方法,獲取一個 SqlSession 對象
  2. 執行 SqlSession 的方法
  3. 當前 SqlSession 不處於 Spring 托管的事務中,則強制提交
  4. 調用SqlSessionUtilscloseSqlSession方法,“關閉”SqlSession 對象,這里的關閉不是真正的關閉

SqlSessionHolder

org.mybatis.spring.SqlSessionHolder:繼承 org.springframework.transaction.support.ResourceHolderSupport 抽象類,SqlSession 持有器,用於保存當前 SqlSession 對象,保存到 org.springframework.transaction.support.TransactionSynchronizationManager 中,代碼如下:

public final class SqlSessionHolder extends ResourceHolderSupport {

  /**
   * SqlSession 對象
   */
  private final SqlSession sqlSession;

  /**
   * 執行器類型
   */
  private final ExecutorType executorType;

  /**
   * PersistenceExceptionTranslator 對象
   */
  private final PersistenceExceptionTranslator exceptionTranslator;

  public SqlSessionHolder(SqlSession sqlSession, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSession, "SqlSession must not be null");
    notNull(executorType, "ExecutorType must not be null");

    this.sqlSession = sqlSession;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
  }

}
  • 當存儲到 TransactionSynchronizationManager 中時,使用的 KEY 為創建該 SqlSession 對象的 SqlSessionFactory 對象,后續會分析

SqlSessionUtils

org.mybatis.spring.SqlSessionUtils:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命周期,借助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 對象

getSqlSession方法

getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)方法,注釋如下:

Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of current transaction.
If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the transaction if Spring TX is active and SpringManagedTransactionFactory is configured as a transaction manager.

從事務管理器(線程安全)中獲取一個 SqlSession 對象,如果不存在則創建一個 SqlSession,然后注冊到事務管理器中,方法如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
  PersistenceExceptionTranslator exceptionTranslator) {
    
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    // 從 Spring 事務管理器中獲取一個 SqlSessionHolder 對象
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    // 獲取到 SqlSession 對象
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    // 上面沒有獲取到,則創建一個 SqlSession
    session = sessionFactory.openSession(executorType);

    // 將上面創建的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器注冊
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}
  1. 從 Spring 事務管理器中,根據 SqlSessionFactory 獲取一個 SqlSessionHolder 對象

  2. 調用 sessionHolder 方法,獲取到 SqlSession 對象,方法如下

    private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
        SqlSession session = null;
        if (holder != null && holder.isSynchronizedWithTransaction()) {
          // 如果執行器類型發生了變更,拋出 TransientDataAccessResourceException 異常
          if (holder.getExecutorType() != executorType) {
            throw new TransientDataAccessResourceException(
                "Cannot change the ExecutorType when there is an existing transaction");
          }
    
          // 增加計數,關閉 SqlSession 時使用
          holder.requested();
    
          LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
          // 獲得 SqlSession 對象
          session = holder.getSqlSession();
        }
        return session;
    }
    
  3. 如果 SqlSession 對象不為 null,則直接返回,接下來會創建一個

  4. 上面沒有獲取到,則創建一個 SqlSession 對象

  5. 調用 registerSessionHolder 方法,將上面創建的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器注冊

  6. 返回新創建的 SqlSession 對象

registerSessionHolder方法

registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session)方法

注釋如下:

Register session holder if synchronization is active (i.e. a Spring TX is active).

Note: The DataSource used by the Environment should be synchronized with the transaction either through DataSourceTxMgr or another tx synchronization.

Further assume that if an exception is thrown, whatever started the transaction will handle closing / rolling back the Connection associated with the SqlSession.

如果事務管理器處於激活狀態,則將 SqlSession 封裝成 SqlSessionHolder 對象注冊到其中,方法如下:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
  PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      // <1> 如果使用 Spring 事務管理器
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        // <1.1> 創建 SqlSessionHolder 對象
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        // <1.2> 綁定到 TransactionSynchronizationManager 中
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        // <1.3> 創建 SqlSessionSynchronization 到 TransactionSynchronizationManager 中
        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        // <1.4> 設置同步
        holder.setSynchronizedWithTransaction(true);
        // <1.5> 增加計數
        holder.requested();
      } else {
        // <2> 如果非 Spring 事務管理器,拋出 TransientDataAccessResourceException 異常
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }
}
  1. 如果使用 Spring 事務管理器,才會進行注冊
  2. 創建 SqlSessionHolder 對象holder
  3. 綁定到 TransactionSynchronizationManager 中,key 為 SqlSessionFactory 對象
  4. 創建 SqlSessionSynchronization 對象(事務同步器)到 TransactionSynchronizationManager 中
  5. 設置 holdersynchronizedWithTransaction 屬性為ture,和事務綁定了
  6. 增加 holderreferenceCount 引用數量

closeSqlSession方法

closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)方法,注釋如下:

Checks if SqlSession passed as an argument is managed by Spring TransactionSynchronizationManager

If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call the close callback when the managed transaction ends

如果 SqlSessionFactory 是由 Spring 的事務管理器管理,並且和入參中的 session 相同,那么只進行釋放,也就是將 referenceCount 引用數量減一,否則就直接關閉了

方法如下:

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    // <1> 從 TransactionSynchronizationManager 中,獲得 SqlSessionHolder 對象
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    // <2.1> 如果相等,說明在 Spring 托管的事務中,則釋放 holder 計數
    if ((holder != null) && (holder.getSqlSession() == session)) {
      LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
      holder.released();
    } else {
      // <2.2> 如果不相等,說明不在 Spring 托管的事務中,直接關閉 SqlSession 對象
      LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
      session.close();
    }
}
  1. 從事務管理器中,根據 SqlSessionFactory 獲得 SqlSessionHolder 對象
  2. 如果相等,說明在 Spring 托管的事務中,則釋放 holder 計數
  3. 否則,不在 Spring 托管的事務中,直接關閉 SqlSession 對象

isSqlSessionTransactional方法

isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory)方法,判斷 SqlSession 對象是否被 Sping 的事務管理器管理,代碼如下:

public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    // 從 TransactionSynchronizationManager 中,獲得 SqlSessionHolder 對象
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    // 如果相等,說明在 Spring 托管的事務中
    return (holder != null) && (holder.getSqlSession() == session);
}

SqlSessionSynchronization

org.mybatis.spring.SqlSessionUtils的內部類,繼承了 TransactionSynchronizationAdapter 抽象類,SqlSession 的事務同步器,基於 Spring Transaction 體系

注釋如下:

Callback for cleaning up resources.

It cleans TransactionSynchronizationManager and also commits and closes the SqlSession.

It assumes that Connection life cycle will be managed by DataSourceTransactionManager or JtaTransactionManager

回調的時候清理資源

構造方法

private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {

    private final SqlSessionHolder holder;

    private final SqlSessionFactory sessionFactory;

    /**
     * 是否開啟
     */
    private boolean holderActive = true;

    public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) {
      notNull(holder, "Parameter 'holder' must be not null");
      notNull(sessionFactory, "Parameter 'sessionFactory' must be not null");

      this.holder = holder;
      this.sessionFactory = sessionFactory;
    }
}

getOrder方法

@Override
public int getOrder() {
  // order right before any Connection synchronization
  return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
}

suspend方法

當事務掛起時,取消當前線程的綁定的 SqlSessionHolder 對象,方法如下:

@Override
public void suspend() {
  if (this.holderActive) {
    LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]");
    TransactionSynchronizationManager.unbindResource(this.sessionFactory);
  }
}

resume方法

當事務恢復時,重新綁定當前線程的 SqlSessionHolder 對象,方法如下:

@Override
public void resume() {
  if (this.holderActive) {
    LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]");
    TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
  }
}

beforeCommit方法

在事務提交之前,調用 SqlSession#commit() 方法之前,提交事務。雖然說,Spring 自身也會調用 Connection#commit() 方法,進行事務的提交。但是,SqlSession#commit() 方法中,不僅僅有事務的提交,還有提交批量操作,刷新本地緩存等等,方法如下:

@Override
public void beforeCommit(boolean readOnly) {
  // Connection commit or rollback will be handled by ConnectionSynchronization or DataSourceTransactionManager.
  // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so they are actually executed.
  // SpringManagedTransaction will no-op the commit over the jdbc connection
  // TODO This updates 2nd level caches but the tx may be rolledback later on!
  if (TransactionSynchronizationManager.isActualTransactionActive()) {
    try {
      LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
      // 提交事務
      this.holder.getSqlSession().commit();
    } catch (PersistenceException p) {
      // 如果發生異常,則進行轉換,並拋出異常
      if (this.holder.getPersistenceExceptionTranslator() != null) {
        DataAccessException translated = this.holder.getPersistenceExceptionTranslator()
            .translateExceptionIfPossible(p);
        if (translated != null) {
          throw translated;
        }
      }
      throw p;
    }
  }
}

beforeCompletion方法

提交事務完成之前,關閉 SqlSession 對象,在 beforeCommit 之后調用,方法如下:

@Override
public void beforeCompletion() {
  // Issue #18 Close SqlSession and deregister it now
  // because afterCompletion may be called from a different thread
  if (!this.holder.isOpen()) {
    LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
    // 取消當前線程的綁定的 SqlSessionHolder 對象
    TransactionSynchronizationManager.unbindResource(sessionFactory);
    // 標記無效
    this.holderActive = false;
    LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
    // 關閉 SqlSession 對象
    this.holder.getSqlSession().close();
  }
}

afterCompletion方法

在事務完成之后,關閉 SqlSession 對象,解決可能出現的跨線程的情況,方法如下:

@Override
public void afterCompletion(int status) {
  if (this.holderActive) { // 處於有效狀態
    // afterCompletion may have been called from a different thread
    // so avoid failing if there is nothing in this one
    LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
    // 取消當前線程的綁定的 SqlSessionHolder 對象
    TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
    // 標記無效
    this.holderActive = false;
    LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
    // 關閉 SqlSession 對象
    this.holder.getSqlSession().close();
  }
  this.holder.reset();
}

總結

還有一部分內容在org.mybatis.spring.batch包路徑下,基於 Spring Batch 框架,Spring 和 MyBatis 的批處理進行集成,感興趣的小伙伴可以去閱讀一下

我通常都是通過配置示例中方式配置MyBatis的,因為我覺得配置文件易於維護,比較可觀,當然也通過注解(@MapperScan)的方式進行配置,原理相同

  1. 首先配置DataSource數據源的 Sping Bean,我們通常不會使用 MyBatis 自帶的數據源,因為其性能不好,都是通過Druid或者HikariCP等第三方組件來實現

  2. 配置SqlSessionFactoryBean的 Spring Bean,設置數據源屬性dataSource,還可以配置configLocation(mybatis-config.xml配置文件的路徑)、mapperLocations(XML映射文件的路徑)等屬性,這樣讓 Spring 和 MyBatis 完美的整合到一起了

  3. 配置MapperScannerConfigurer的 Spring Bean,設置basePackage(需要掃描的Mapper接口的路徑)、sqlSessionFactoryBeanName(上面定義的SqlSessionFactoryBean)等屬性

    因為實現了 BeanDefinitionRegistryPostProcessor 接口,在這些 Mapper 接口的 BeanDefinition 對象(Spring Bean 的前身)注冊完畢后,可以進行一些處理

    在這里會修改這些 BeanDefinition 對象為 MapperFactoryBean 類型,在初始化 Spring Bean 的過程中則創建的是 MapperFactoryBean 對象,注入該對象則會調用其 getObject() 方法,返回的該 Mapper 接口對應的動態代理對象

    這樣當你注入 Mapper 接口時,實際注入的是其動態代理對象

  4. SqlSessionTemplate對象中,承擔 SqlSessionFactory 和 SqlSession 的職責

到這里,相信大家對 MyBatis 集成到 Spring 的方案有了一定的了解,感謝大家的閱讀!!!😄😄😄

參考文章:芋道源碼《精盡 MyBatis 源碼分析》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM