【MyBatis源码学习】Spring集成MyBatis的原理分析


前言

上一章中【MyBatis源码学习】MyBatis与Spring整合,我们介绍了Mybatis与Spring、Spring Boot的融合,其中我们提到了mybatis-spring中的几个关键类,本章我们来跟踪下这几个关键类的源码,看看它们干了些啥.

一、源码下载

和mybatis源码下载一样,从GitHub下载好后,本地打包部署一下,方便我们后面进行测试时方便查看。源码地址:这里是MyBatis-Spring源码地址.

二、核心类源码解析

先来回顾下核心类的功能:

  • SqlSessionFactoryBean:用于创建SqlSessionFactory;
  • MapperScannerConfigurer:自动扫描所有的 Mapper 接口,使用时可以直接注入接口。

1.SqlSessionFactoryBean

(1) UML

"SqlSessionFactoryBean"

(2) 配置和源码解析
通常我们在项目中整合Spring和mybatis时,Spring相关的配置文件会包含:

<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<property name="typeAliasesPackage" value="com.enjoylearning.mybatis.entity" />
	<property name="mapperLocations" value="classpath:sqlmapper/*.xml" />
</bean>

并且dataSource必须配置,我们可以从方法afterPropertiesSet()看出来:

/**
   * 在spring容器中创建全局唯一的sqlSessionFactory
   *
   * @throws Exception 异常
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    //数据源不能为空
    notNull(dataSource, "Property 'dataSource' is required");
    //sqlSessionFactoryBuilder不能为空,但初始化SqlSessionFactoryBean完,sqlSessionFactoryBuilder已经创建了
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

接下来,看看方法。主要功能构建和丰富Configuration,并创建sqlSessionFactory.

/**
   * Build a {@code SqlSessionFactory} instance.
   *
   * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
   * {@code SqlSessionFactory} instance based on an Reader.
   * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
   *
   * @return SqlSessionFactory
   * @throws IOException if loading the config file failed
   */
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder = null;
    //如果configuration不为空,则使用该对象,并对其进行配置
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
      //创建xmlConfigBuilder,读取mybatis的核心配置文件
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
      //如果configuration为空,实例化一个configuration对象
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
    //设置objectFactory
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }
    //设置objectWrapperFactory
    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }
    //设置vfs
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
    //扫描指定的包typeAliasesPackage,注册别名
    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }
    //为typeAliases指定的类注册别名
    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }
    //注册插件
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }
    //扫描指定的包typeHandlersPackage,注册类型解析器
    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }
    //为typeHandlers指定的类注册类型解析器
    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }
    //配置databaseIdProvider
    //fix #64 set databaseId before parse mapper xmls
    if (this.databaseIdProvider != null) {
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
    //配置缓存
    if (this.cache != null) {
      configuration.addCache(this.cache);
    }
    //使用xmlConfigBuilder读取mybatis的核心配置文件
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        if (LOGGER.isDebugEnabled()) {
          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();
      }
    }
    //默认使用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创建sqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

最后一句

return this.sqlSessionFactoryBuilder.build(configuration);

是不是又回到了熟悉的地方。

2.MapperScannerConfigurer

(1) UML

"MapperScannerConfigurer UML"

(2) 配置和源码解析
这里我们主要关注这个接口:BeanDefinitionRegistryPostProcessor
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor接口是一个可以修改spring工厂中已定义的bean的接口,该接口有个postProcessBeanDefinitionRegistry方法。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  //占位符处理
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
  //实例化ClassPathMapperScanner,并对scanner相关属性进行配置
  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);
  //根据上述配置,生成过滤器,只扫描合条件的class
  scanner.registerFilters();
  //扫描指定的包以及其子包
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

点进去,看看ClassPathMapperScanner.scan()方法:

/**
 * Calls the parent search that will search and register all the candidates.
 * Then the registered objects are post processed to set them as
 * MapperFactoryBeans
 */
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //通过父类ClassPathBeanDefinitionScanner的扫描,获取所有复合条件的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集合,将集合中的每一个mapper接口转换成MapperFactoryBean后,注册至spring容器
        processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
}

/**
 * 处理扫描得到的BeanDefinitionHolder集合,将集合中的每一个mapper接口转换成MapperFactoryBean后,注册至spring容器
 *
 * @param beanDefinitions
 */
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");
        }
        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        //增加一个构造方法,接口类型作为构造函数的入参
        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
        //将bean的类型转换成mapperFactoryBean
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        //增加addToConfig属性
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
        boolean explicitFactoryUsed = false;
        //增加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;
        }
        //增加sqlSessionTemplate属性
        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;
        }
        //修改自动注入的方式 bytype
        if (!explicitFactoryUsed) {
            if (logger.isDebugEnabled()) {
                logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            }
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }
}

注释已经写得很清楚了,这里再看看MapperFactoryBean.

MapperFactoryBean

其继承了SqlSessionDaoSupport类,SqlSessionDaoSupport类继承DaoSupport抽象类,DaoSupport抽象类实现了InitializingBean接口,因此实例一个MapperFactoryBean的时候,都会调用InitializingBean接口的afterPropertiesSet方法。而MapperFactoryBean也重写了:

/**
 * MapperFactoryBean在容器初始化时,要确保mapper接口被注册到mapperRegistry
 * {@inheritDoc}
 */
@Override
protected void checkDaoConfig() {
  super.checkDaoConfig();
  notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  //通过SqlSession从容器中拿到configuration
  Configuration configuration = getSqlSession().getConfiguration();
  if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    try {
  	//如果mapperRegistry中不包含当前接口的动态代理工厂,则添加一个
      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();
    }
  }
}

看到configuration.addMapper()方法,是不是又想起了configuration熟悉的味道了。

MapperScannerConfigurer在Spring配置文件中的应用如下:

<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!-- 可以使用分号或逗号作为分隔符设置多于一个的包路径 -->
	<property name="basePackage" value="com.enjoylearning.mybatis.mapper" />
	<!-- optional unless there are multiple session factories defined -->
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

作用很简单,扫描特定目录下的所有mapper.java,生成MapperFactoryBean,然后创建动态代理工厂类MapperProxyFactory,并注册至mapperRegistry当中。使用时依赖注入即可。
当然,如果只有一个mapper,我们也可以这样配置:

<bean id="tUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
	<property name="mapperInterface" value="com.enjoylearning.mybatis.mapper.TUserMapper" />
	<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

但是,一般项目中mapper肯定不止一个,一旦新增了一个新的mapper,我们就需要配置很多个这样子的bean,MapperScannerConfigurer帮我们解决了这样的问题。

sqlSessionFactoryBeanName非必填,但是如果是多数据源的环境,就必须配置好。注意 bean 的名称name必须的,而不是 bean 的引用,因此,value 属性在这里替代通常的ref。


文章作者: Kezade
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kezade !
评论
  目录