1. 程式人生 > >MyBatis整合Spring原理分析

MyBatis整合Spring原理分析

目錄

  • MyBatis整合Spring原理分析
  • MapperScan的祕密
  • 簡單總結



假如不結合Spring框架,我們使用MyBatis時的一個典型使用方式如下:

public class UserDaoTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void setUp() throws Exception{
        ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
        InputStream inputStream = resource.getInputStream();
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void selectUserTest(){
        String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";
        SqlSession sqlSession = sqlSessionFactory.openSession();
        CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);
        Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);
        System.out.println(cbondissuer);
        sqlSession.close();
    }

}

我們首先需要SqlSessionFactory,然後通過SqlSessionFactory的openSession方法獲得SqlSession。通過SqlSession獲得我們定義的介面的動態代理類(MapperProxy)。當我們整合Spring框架時,我們使用MyBatis的方式簡單的“難以想象”:

@Autowore
private CbondissuerMapper cbondissuerMapper;

非常Spring形式的使用方式,直接注入就可以了。那這個是怎麼實現的呢?Spring是怎麼把上面略顯複雜的模板程式碼省略的呢?我的第一直覺是Spring在啟動的時候做了Mybatis的初始化工作,然後一次性獲取了所有Mapper介面的動態代理實現類並將其放入Spring容器中進行管理。

下面來驗證下自己的猜想對不對。

MyBatis整合Spring原理分析

下面以Spring Boot中的MyBatisAutoConfigration為列子做下簡單分析:

  //SqlSessionFactoryBean的最終作用就是解析MyBati配置檔案,並最終生成Configration物件,然後生成DefaultSqlSessionFactory,並加入Spring的容器管理。可以看出SqlSessionFactoryBean的作用和SqlSessionFactoryBeanBuilder的作用很像。
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
  }

  //建立SqlSessionTemplate這個Bean,這個類動態代理了DefaultSqlSession,所以最後還是呼叫了DefaultSqlSession,下面會重點分析下SqlSessionTemplate
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

以下是SqlSessionTemplate的原始碼,由於原始碼較多,只貼出重點部分:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;
  //SqlSessionTemplate動態代理了DefaultSqlSession,所用對sqlSessionProxy的呼叫都會經過代理物件
  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;
    
  ....
  
  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;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
    
  ....
  //對sqlSessionProxy的CRUD呼叫都會先呼叫到這裡
  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //這步相當於呼叫sqlSessionFactory的openSesion方法獲得SqlSession
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //呼叫DefaultSqlSession的CRUD方法
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          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
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          //最後強制關閉SqlSession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
}

到此為止,整個Spring中獲取SqlSessionFactory、獲得SqlSession、執行CRUD、以及關閉SqlSession的流程都進行分析了。還有一個有疑問的地方就是Mapper介面的實現類是在什麼時候動態生成的。

MapperScan的祕密

我們知道MapperScan是用來掃描Mapper介面的,所以很自然的想到去MapperScan這個類裡面一探究竟。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//祕密在MapperScannerRegistrar裡面
@Import(MapperScannerRegistrar.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 "";
  //指定自定義的MapperFactoryBean
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}

下面就看下MapperScannerRegistrar裡面做了什麼:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //建立了一個ClassPathMapperScanner,並根據@MapperScan裡面的配置設定屬性
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

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

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    //開始掃描
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

以上建立了一個ClassPathMapperScanner,並根據@MapperScan裡面的配置設定屬性,開始掃描。下面再看ClassPathMapperScanner。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //對掃描到的BeanDefinition做進一步處理
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }
 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //這邊是重點,將掃描到的Mapper介面的BeanClass設定為MapperFactoryBrean
      //MapperFactoryBrean是工廠Bean,用於生成MapperProxy;
      //Spring在自動注入Mapper的時候會自動呼叫這個工廠Bean的getObject方法,生成MapperProxy並放入Spring容器。
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      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;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        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;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

到此Spring自動生成Mapper介面實現類的過程也分析完了。

簡單總結

通過以上分析,印證了一開始的猜想:Spring在啟動的時候做了Mybatis的初始化工作,然後一次性獲取了所有Mapper介面的動態代理實現類並將其放入Spring容器中進行管理。大致流程如下:

  • 配置SqlSessionFactoryBean,這個Bean的最終作用是建立DefaultSqlSessionFactory,並將DefaultSqlSessionFactory加入Spring容器管理;
  • 建立SqlSessionTemplate,這個物件代理了MyBatis中的DefaultSqlSession,通過SqlSessionTemplate呼叫任何CRUD方法都會經歷openSession、呼叫DefaultSqlSession的CRUD方法和關閉Session的過程;

Mapper介面生成的流程大致如下:

  • @MapperScan註解掃描到所有的Mapper介面生成相應的BeanDefinition;
  • ClassPathMapperScanner的處理方法對這些BeanDefinition做進一步配置,其中最終要的一步就是將這些BeanDefinition的Beanclass屬性設定為MapperFactoryBean(這是一個工廠Bean,專門用於生成Mapper的動態代理實現MapperProxy,一個Dao介面會對應一個MapperFactoryBean);
  • Spring在檢測到需要自動注入Mapper時通過層層呼叫,最終會呼叫到MapperFactoryBean這個工廠Bean的getObject方法生成對應的MapperProxy,並將這個物件納入Spring管理。

以上大致就是MyBatis整合進Spring的原理。我們發現其實質和傳統的Mybatis使用是一樣的,只不過是通過Spring做了一些自定配置等。

分析的有點亂,先這樣