MyBatis 源碼篇-MyBatis-Spring 剖析

本章通過分析 mybatis-spring-x.x.x.jar Jar 包中的源碼,瞭解 MyBatis 是如何與 Spring 進行集成的。

Spring 配置文件

MyBatis 與 Spring 集成,在 Spring 配置文件中配置了數據源、SqlSessionFactory、自動掃描 MyBatis 中的 Mapper 接口、事務管理等,這部分內容都交由 Spring 管理。部分配置內容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" >
  
  <!-- 配置數據源 -->
  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    lazy-init="true" init-method="init" destroy-method="close">
    <property name="url" value="${dataSource.url}" />
    <property name="username" value="${dataSource.username}" />
    <property name="password" value="${dataSource.password}" />
  </bean>
    
  <!-- 配置SqlSessionFactory -->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <property name="mapperLocations" value="classpath*:rabbit/template/mapper/*.xml" />
  </bean>

  <!-- 配置自動掃描所有Mapper接口 -->
  <bean id="mapperScan" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="rabbit.template.mapper" />
  </bean>
    
  <!--配置事務管理 -->
  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>

  <!-- 激活CGLIB代理功能 -->
  <aop:aspectj-autoproxy proxy-target-class="true" />
  <tx:annotation-driven transaction-manager="transactionManager" />
    
    ......
</beans>

從配置文件中可以看出,MyBatis 與 Spring 集成後,MyBatis 中的 SqlSessionFactory 對象是由 SqlSessionFactoryBean 創建的。

SqlSessionFactoryBean

SqlSessionFactoryBean 類作爲我們分析源碼的入口,該類實現了 InitializingBean 接口,在 Bean 初始化的時候,會執行 afterPropertiesSet() 方法,最後會調用 buildSqlSessionFactory() 方法創建 SqlSessionFactory。

SqlSessionFactoryBean.buildSqlSessionFactory() 方法的具體實現如下:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder = null;
    // 創建Configuration對象
    if (this.configuration != null) {
      configuration = this.configuration;
      // 省略配置相關代碼
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      configuration = new Configuration();
      // 省略配置相關代碼
    }
    // 配置objectFactory
    // 根據Spring配置文件,設置Configuration.objectWrapperFactory
    // 根據Spring配置文件,設置Configuration.objectFactory
    // 掃描typeAliasesPackage指定的包,併爲其中的類註冊別名
    // 爲typeAliases集合中指定的類註冊別名
    // 註冊plugins集合中指定的插件
    // 掃描typeHandlersPackage指定的包,並註冊其中的TypeHandler
    // 註冊指定的typeHandlers
    // 配置databaseIdProvider
    // 配置緩存
  
    // 調parse()方法解析配置文件
    if (xmlConfigBuilder != null) {
      xmlConfigBuilder.parse();
    }
    // 如果未配置transactionFactory,則默認使用SpringManagedTransactionFactory
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
    // 設置environment
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    // 根據mapperLocations配置,處理映射配置文件以及相應的Mapper接口
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
    // 調用SqlSessionFactoryBuilder.build()方法,創建SqlSessionFactory對象並返回
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

SqlSessionFactoryBuilder.build() 方法返回的對象類型是 DefaultSqlSessionFactory。

SqlSessionFactoryBean 同時實現了 FactoryBean 接口,重寫了 getObject() 方法,該方法返回 DefaultSqlSessionFactory 對象。

當配置文件中 <bean> 的 class 屬性配置的實現類是 FactoryBean 時,通過 getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的對象,相當於FactoryBean#getObject() 代理了 getBean() 方法。

SqlSessionFactoryBean#getObject() 方法的具體實現如下:

public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  return this.sqlSessionFactory;
}

在《MyBatis 技術內幕》這本書的4.2.4章詳細描述了 mybatis-spring Jar 包中的 SpringManagedTransaction、SqlSessionTemplate、SqlSessionDaoSupport、MapperFactoryBean、MapperScannerConfigurer 類的作用,有不明白的可以參考。

接下來繼續介紹 Spring 配置文件中的 MapperScannerConfigurer Bean 對象。

MapperScannerConfigurer

MapperScannerConfigurer 類實現了 BeanDefinitionRegistryPostProcessor 接口,該接口中的 postProcessBeanDefinitionRegistry() 方法會在系統初始化的過程中被調用,該方法掃描了配置文件中配置的basePackage 下的所有 Mapper 類,最終生成 Spring 的 Bean 對象,註冊到容器中。

postProcessBeanDefinitionRegistry() 方法具體實現如下:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
  // 創建ClassPathMapperScanner對象
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  // 根據上面的配置,生成相應的過濾器。這些過濾器在掃描過程中會過濾掉不符合添加的內容,例如,
  // annotationClass字段不爲null時,則會添加AnnotationTypeFilter過濾器,通過該過濾器
  // 實現只掃描annotationClass註解標識的接口的功能
  scanner.registerFilters();
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

ClassPathMapperScanner.scan() 方法會調用父類的 scan() 方法,核心邏輯在 doScan() 方法中實現,具體實現如下:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // 調用父類的doScan()方法,遍歷basePackages中指定的所有包,掃描每個包下的Java文件並進行解析。
  // 使用之前註冊的過濾器進行過濾,得到符合條件的BeanDefinitionHolder對象
  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 {
    // 處理掃描得到的BeanDefinitionHolder集合
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

ClassPathMapperScanner.processBeanDefinitions() 方法會對 doScan() 方法中掃描到的 BeanDefinition 集合進行修改,主要是將其中記錄的接口類型改造爲 MapperFactoryBean 類型,並填充 MapperFactoryBean 所需的相關信息。

ClassPathMapperScanner.processBeanDefinitions() 方法具體實現如下:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();

    if (logger.isDebugEnabled()) {
      logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
        + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    }

    // 將掃描到的接口類型作爲構造方法的參數
    definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
    // 將BeanDefinition中記錄的Bean類型修改爲MapperFactoryBean
    definition.setBeanClass(this.mapperFactoryBean.getClass());
    // 構造MapperFactoryBean的屬性,將sqlSessionFactory、sqlSessionTemplate
    // 等信息填充到BeanDefinition中
    // 修改自動注入方式
  }
}

ClassPathMapperScanner 在處理 Mapper 接口的時候用到了 MapperFactoryBean 類,它是 MyBatis-Spring 提供的一個動態代理的實現,可以直接將 Mapper 接口注入到 Service 層的 Bean 中,這樣就不需要編寫任何 DAO 實現的代碼。

MapperFactoryBean 的繼承關係如圖所示:

image.png

MapperFactoryBean 類的動態代理功能是通過實現了 Spring 提供的 FactoryBean 接口實現的,該接口是一個用於創建 Bean 對象的工廠接口,通過 geObject() 方法獲取真實的對象。

MapperFactoryBean.geObject() 方法的實現如下:

public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

通過 SqlSession 對象獲取 Mapper 文件,《MyBatis 源碼篇-SQL 執行的流程》介紹過 Mapper 接口的代理對象的獲取過程。

getSqlSession() 是 SqlSessionDaoSupport 中的方法。該類的 sqlSession 屬性是通過 Spring 容器自動注入 sqlSessionFactory 屬性實現的。源碼實現如下:

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  if (!this.externalSqlSession) {
    this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
  }
}

這樣就不難理解,在 Spring 容器中,Mapper 接口對應的實現類還是 MyBatis 提供的 MapperProxy 代理對象。MapperFactoryBean 類只不過是包裝了一下,讓真正的對象能夠注入到 Spring 容器中。所以 Mapper 接口對應的實現類是作爲單例一直存在 Spring 容器中的。

因爲引入了 Spring 框架,增加了這塊的源碼的理解難度,下面通過一個簡單的時序圖總結 Mapper 實現類的生成過程。

MyBatis-集成Spring-Mapper.png

SqlSessionTemplate

SqlSessionTemplate 實現了 SqlSession 接口,在 MyBatis 與 Spring 集成開發時,用來代替 MyBatis 中的 DefaultSqlSession 的功能。

SqlSessionTemplate 是線程安全的,可以在 DAO 之間共享使用,比如上面生成的 Mapper 對象會持有一個 SqlSessionTemplate 對象,每次請求都會共用該對象。在 MyBatis 中 SqlSession 的 Scope 是會話級別,請求結束後需要關閉本次會話,怎麼集成了 Spring 後,可以共用了?

首先,在集成 Spring 後,Mapper 對象是單例,由 Spring 容器管理,供 Service 層使用,SqlSessionTemplate 在設計的時候,功能分成了如下兩部分:

  1. 獲取 MapperProxy 代理對象;
  2. 執行 SQL 操作,該部分功能通過代理對象 SqlSessionInterceptor 實現;

兩類方法的具體實現如下:

@Override
public <T> T getMapper(Class<T> type) {
  return getConfiguration().getMapper(type, this);
}

@Override
public <T> T selectOne(String statement) {
  return this.sqlSessionProxy.<T> selectOne(statement);
}

getMapper 方法是可以共享的,SQL 操作的相關方法是會話級別的不能共享,所以在 SqlSessionTemplate 類中通過動態代理的方式來區分這兩部分功能的實現。

接下來看看 SqlSessionTemplate 類中的動態代理部分的實現:

// SqlSession 的代理類
private final SqlSession sqlSessionProxy;

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的代理類,給sqlSessionProxy屬性賦值
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class },
      new SqlSessionInterceptor());
}
// 代理類
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 通過SqlSessionUtils.getSqlSession()獲取SqlSession對象,同一個事務共享SqlSession
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      // 調用SqlSession對象的相應方法
      Object result = method.invoke(sqlSession, args);
      // 檢測事務是否由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;
    }
  }

 

MyBatis 源碼篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章