从配置文件研究MyBatis的运行过程

Posted 敲代码的小小酥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从配置文件研究MyBatis的运行过程相关的知识,希望对你有一定的参考价值。

一、Spring整合MyBatis配置文件

<!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描*/mapping/目录下的所有SQL映射的xml文件, 省掉Configuration.xml里的手工配置
   
         -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:xxx/*.xml" />
    </bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描*.dao这个包以及它的子包下的所有映射接口类 -->
        <property name="basePackage" value="*.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

由配置文件可以看到,往Spring容器中注入了SqlSessionFactoryBean和MapperScannerConfigurer两个类。分析这两个类源码,学习MyBaits运行流程。

二、SqlSessionFactoryBean源码

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

可以看到,SqlSessionFactoryBean类实现了三个接口,我们一个一个分析。
首先看FactoryBean接口的getObject()实现方法:

  @Override
  public SqlSessionFactory getObject() throws Exception 
    if (this.sqlSessionFactory == null) 
      afterPropertiesSet();
    

    return this.sqlSessionFactory;
  

该方法就是返回SqlSessionFactory 对象给Spring容器,如果SqlSessionFactory 是空,则调用InitializingBean的afterPropertiesSet()实现方法,看其源码:

 @Override
  public void afterPropertiesSet() throws Exception 
    notNull(dataSource, "Property 'dataSource' is required");
    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();
  

最终调用了buildSqlSessionFactory方法,创建了SqlSessionFactory对象,我们看buildSqlSession方法源码:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException 

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    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);
      
     else if (this.configLocation != null) 
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
     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);
      
    

    if (this.objectFactory != null) 
      configuration.setObjectFactory(this.objectFactory);
    

    if (this.objectWrapperFactory != null) 
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    

    if (this.vfs != null) 
      configuration.setVfsImpl(this.vfs);
    

    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");
        
      
    

    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 + "'");
        
      
    

    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");
        
      
    

    if (!isEmpty(this.typeHandlers)) 
      for (TypeHandler<?> typeHandler : this.typeHandlers) 
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) 
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        
      
    

    if (this.databaseIdProvider != null) //fix #64 set databaseId before parse mapper xmls
      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);
    

    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();
      
    

    if (this.transactionFactory == null) 
      this.transactionFactory = new SpringManagedTransactionFactory();
    

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    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");
      
    

    return this.sqlSessionFactoryBuilder.build(configuration);
  

可以看到,buildSqlSession方法中设置了Configuration 对象的各种属性,例如配置文件的读取,plugin插件的加入,xml映射关系的建立等等,总之Configuration对象是MyBaits的大杂烩,基本所有的配置都在这里完成,然后通过Configuration 对象生成SqlSessionFactory对象。
这样,MyBatis就把其组件加入到了Spring容器中。由此可知,MyBatis是通过FactoryBean的方式,将其组件加入到Spring容器中。
buildSqlSessionFactory方法的这段代码,需要注意:

if (this.transactionFactory == null) 
      this.transactionFactory = new SpringManagedTransactionFactory();
    

可以看到,这里创建了Spring的事务管理器,说明,跟Spring整合后,MyBatis的事务和连接,都由Spring进行管理。

三、MapperScannerConfigurer类研究

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

可以看到其实现了BeanDefinitionRegistryPostProcessor接口,说明可以注册BeanDefinition对象,看其注册方法的实现:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 
    if (this.processPropertyPlaceHolders) 
      processPropertyPlaceHolders();
    

    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);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  

可以看到注册进来了扫描类,重点看scan方法里的doScan方法:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) 
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) 
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) 
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) 
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				
				if (candidate instanceof AnnotatedBeanDefinition) 
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				
				if (checkCandidate(beanName, candidate)) 
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				
			
		
		return beanDefinitions;
	

这里注册进来的类,是MapperFactoryBean类型的对象,我们看MapperFactoryBean类的源码

四、MapperFactoryBean类源码

该类将MyBatis的Mapper接口封装进来,封装到如下属性中:

private Class<T> mapperInterface;

我们看这个类的实现和继承关系:

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

可知该类实现了FactoryBean接口,看其getObject方法:

@Override
  public T getObject() throws Exception 
    return getSqlSession().getMapper(this.mapperInterface);
  
  • getSqlSession方法研究:

很显然这个方法是通过接口,返回一个接口的实现类。也就是说,Mapper接口的实现类,在这里创建。我们看其是如何创建的。看getSqlsession()方法:
sqlSession的初始化在MapperFactoryBean类的父类SqlSessionDaoSupport中完成,SqlSessionDaoSupport源码如下:

/**
 *    Copyright 2010-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.spring.support;

import static org.springframework.util.Assert.notNull;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.以上是关于从配置文件研究MyBatis的运行过程的主要内容,如果未能解决你的问题,请参考以下文章

mybatis generator

springboot + mybatis + gradle项目构建过程

mybati之运行过程

MyBatis 源码分析 - 配置文件解析过程

MyBatis从入门到入土——使用详解

mybatis运行原理