Mybatis source (six) Mybatis-Spring framework implementation principle

I use mybatis-spring process has been a doubt in Mybatis source (a) Overview I mentioned, SqlSessionand Mapper object lifecycle is a method level, that is, each request SqlSessionand Mapper objects are not the same , Bean is a non-single embodiment. But after Spring integration, why we can directly inject Mapper objects, by direct injection, then if Mapper has become the object of a singleton?

With questions we look at how Mybatis-Spring is achieved.

Initialization SqlSessionFactory

We are through SqlSessionFactoryBeanto complete integration with Spring Mybatis, class diagram as follows:

SqlSessionFactoryBean.png

We have found through the class diagram SqlSessionFactoryBeanimplements FactoryBeanan interface, then the Spring Bean is instantiated when calls FactoryBeanthe getObject()method. Therefore Mybatis with Spring inlet is integrated org.mybatis.spring.SqlSessionFactoryBean#getObject()method, the following source code:

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

    return this.sqlSessionFactory;
}

By discovering with source code, in the afterPropertiesSet();completion of the process sqlSessionFactoryinitialization.

And Mybatis source (b) Mybatis initialization described in the same, or through XMLConfigBuilder, XMLMapperBuilderand XMLStatementBuilderthree builders to complete the parsing of Mybatis XML file.

Spring loaded into the container mapper

After Mybatis and Spring integration, there are three ways to Examples Mapper Spring loaded into the container, as follows:

  • Use <mybatis:scan/>elements
  • Use @MapperScanannotations
  • Spring XML configuration file a registration MapperScannerConfigurer

Here we introduce MapperScannerConfigurer.

MapperScannerConfigurer

MapperScannerConfigurer.png

We have found through the class diagram, MapperScannerConfigurerrealized BeanDefinitionRegistryPostProcessor, it executes BeanDefinitionRegistryPostProcessorthe postProcessBeanDefinitionRegistrymethod to complete the loading of Bean.

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	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));
}

We can see by scanning package, it will scan all the Mapper class, and then register to define the Bean Spring container.

Mapper is an interface class, however, can not be directly instantiated, so ClassPathMapperScanner, it will all Mapper object BeanDefinitionto change, and the interfaces to all object points Mapper MapperFactoryBeanplant Bean, so the interface in Spring Mybatis corresponding to all Mapper class is MapperFactoryBean, source code is as follows:

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

	for (BeanDefinitionHolder holder : beanDefinitions) {
		GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

		definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
		definition.setBeanClass(MapperFactoryBean.class);

		definition.getPropertyValues().add("addToConfig", this.addToConfig);
		...
        // 设置按类型注入属性,这里主要是注入sqlSessionFactory和sqlSessionTemplate
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
	}

	return beanDefinitions;
}

From source we see ClassPathMapperScannermajor following BeanDefinition:

  1. The Class pointMapperFactoryBean
  2. Modify the attribute value to be injected, such addToConfigas: sqlSessionFactory, ,sqlSessionFactory
  3. Modified injection methodAbstractBeanDefinition.AUTOWIRE_BY_TYPE

By the above-described examples may be modified so that the interface Mapper object and placed into Spring container.

Folders Factory Bean

MapperFactoryBean.png

We can see from FIG class that it is a FactoryBeanso instantiated call back to its getObject()method completes loading the Bean, the following source code:

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

It is worth to say about that, getSqlSession()get to that SqlSessionTemplateobject, Mapper is a case of a single example of how to ensure that each access to the database Sqlsessionis not the same, that is SqlSessionTemplateimplemented.

MapperFactoryBeanIt also implements InitializingBeanthe interface, using the InitializingBeanfeature that the interface will Mapper Mybatis into the Configurationsubject, the following source code:

@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    // Let abstract subclasses check their configuration.
    checkDaoConfig();
    ...
}

protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            // 将Mapper放到Mybatis的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();
        }
    }
}

Mapper Spring container may be implanted into an interface equivalent to a configuration, this looks better understood:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
  1. Through the above, we can find the source to load objects Mapper Spring container fact, Mapper proxy object corresponding to the interface MapperProxy, and it is a single vessel embodiment.
  2. Mapper is a singleton really is no problem, because Mapper itself is not shared variable, it is a thread-safe class, we only need to ensure that the database used for each request Sqlsessionis not a single example on the line. In order to achieve this, MapperProxyit is SqlSessionnot used directly DefaultSqlSession, but the use SqlSessionTemplate.

SqlSessionTemplate

SqlSessionTemplate+ Proxy mode using dynamic static proxy mode, SqlSession enhanced, the new database for each request SqlSessionput enhancer SqlSessionInterceptorwhich is achieved.

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  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;
    // 使用动态代理模式,对SqlSession进行增强
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
      new Class[]{SqlSession.class}, new SqlSessionInterceptor());
  }

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

SqlSessionInterceptor

This is one of the core principles to achieve Mybatis-Spring, it will create a new process for each request in the database SqlSession, source code as follows:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 每次获取新的SqlSession
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      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
        // 资源释放
        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) {
        // 资源释放
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

In this enhancer which implements the newly created each request Sqlsession, and also made uniform resource release, transaction processing, which makes us in the Spring and integration, without concern for resource release and other operations, will focus on their business .

to sum up

Achieve Mybatis-Spring has two core points:

  1. By MapperFactoryBeanclever Mapper proxy object corresponding to the interface MapperProxyis loaded into the Spring container.
  2. By SqlSessionTemplateusing a static dynamic proxy + proxy mode, cleverly realized are used to access the database each time a new Sqlsessionobject.

Mybatis source Chinese comments

https://github.com/xiaolyuh/mybatis

Experience

Here Mybatis source series is finished, compared with the Spring source code, I suggest that you go to the next Mybatis source, for the following reasons:

  1. Mybatis source code is very neat, packet structure, the structure of the code are worth learning.
  2. Mybatis use many design patterns, most of the design patterns can be found in its shadow here, it can be seen as best practice design patterns.
  3. Mybatis overall design is very clever, scalability is very strong.
Published 203 original articles · won praise 145 · views 850 000 +

Guess you like

Origin blog.csdn.net/xiaolyuh123/article/details/103290572