精盡MyBatis原始碼分析 - MyBatis-Spring 原始碼分析

月圓吖發表於2020-11-27

該系列文件是本人在學習 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 有了一個更加深入的瞭解。在使用它的過程中,需要自己建立 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 原始碼分析》

相關文章