【原创】Mybaitis生命周期源码解析-SpringBoot启动--转载请注明出处

注:本文中的一切内容是基于Spring-boot项目进行的研究,使用其他的方式可能会有一些不同。

建议在阅读本博客前,先阅读前一篇:【原创】Mybaitis生命周期源码解析-XML配置启动--转载请注明出处

一、Spring-Mybatis准备测试代码

由于基本代码过多,此处不再细述。仅贴出用于进行测试的部分代码:

package com.zhou.controller;

import com.zhou.mapper.BlogMapper;
import com.zhou.pojo.Blog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;

@Controller
@RequestMapping("/")
public class AppController {

    @Autowired
    private BlogMapper blogMapper;

    @RequestMapping("hello/{key}")
    @ResponseBody
    @Transactional
    public String hello(@PathVariable("key") String key){
        Blog blog = blogMapper.selectBlog(10, new ArrayList<>());
        blogMapper.updateBlog(12345, key);
        throw new RuntimeException("123123123");
    }

}

在一个已经添加mybatis的项目中,添加上方代码即可进行测试。其中的update方法,和select方法可以随意填写。

二、服务启动Mybatis初始化

1.在mybatis进行嵌入到Spring中时,其核心类为MapperScannerConfigurer类,这个类实现了Spring的BeanDefinitionRegistryPostProcessor接口,会让Spring容器在加载时执行postProcessBeanDefinitionRegistry方法。在BeanDefinitionRegistryPostProcessor中对scanner对象设置了很多的属性,这些属性,其中有SqlSessionFactory与SqlSessionTemplete的bean的名称信息,这些信息比较重要。之后使用了Scanner进行包扫描操作。

2.ClassPathMapperScanner是mybatis所使用的Mapper类扫描工具。这个类是继承于Spring的ClassPathDefinitionScanner的子类,该类重写了原方法中的doScan方法。doScan方法会在类ClassPathDefinitionScanner中由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) {
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

在子类ClassPathMapperScanner中doScan方法除了调用原本的super.doScan外,还执行了processBeanDefinitions方法。在该方法中,通过循环的方式,对找到的每一个mapper的接口类描述信息进行了修改,最主要的部分,在与下方带注解的两行代码,这里将beanClass设置为了MapperFactoryBean。并且,添加了SqlSessioinTemplete以及SqlSessionFactory的相关信息到bean的属性描述当中。

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

      // mapper 接口是 bean 的原始类
      // 但是这些bean的实际上的类是 MapperFactoryBean,下方的两行代码很重要
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      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);
      }
    }
  }

3.MapperFactoryBean,是mybatis对于FactoryBean接口的实现,该接口具有一个getObject方法,该方法进行调用时,便会获取到一个相应的bean对象,这样就为使用特定的工厂类进行bean的创建提供了一个路径。在这之后,就实现了通过统一的一个SqlSessionTemplete来生成所有mapper的实现类。

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

4.在MapperFactoryBean进行构造时,实际上的构造器是setSqlSessionFactory。这里在setSqlSessionFactory后,会创建一个新的SqlSessionTemplete到MapperFactoryBean中。而setSqlSessionFactory方法,则会在Spring进行构造MapperFactoryBean后,进行setValues时进行调用,这里则不细讲。

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

三、Sql执行

1.在sql执行时,我们首先忽略掉很长的aop代码,直接进入执行方法。sql进行执行时,使用的是MapperProxy的invoke方法,该方法在执行时使用了成员属性中的SqlSession对象来执行sql,此处需要注意的是,这里的SqlSession是SqlSessionTemplete而不是DefaultSqlSession,区别在于DefaultSqlSession是直接进行Sql执行操作的session,而SqlSessionTemplete则不是。

2.执行Sql时,实际上所使用的,是SqlSessionTemplete类中的SqlSessionProxy对象。这个对象是由内部类SqlSessionInterceptor所映射出的DefaultSqlSession对象。在执行DefaultSqlSession中的方法时,并不会利用本身的proxy对象进行方法的调用,而是重新获取一个SqlSession对象来执行sql操作。在无事务切面的情况下,会直接对SqlSession进行commit操作。

   /**
   * Proxy needed to route MyBatis method calls to the proper SqlSession got
   * from Spring's Transaction Manager
   * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
   * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
   */
  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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);
        }
      }
    }
  }

四、总结

1.在进行初始化时,mybatis利用MapperScannerConfigurer类,来将自己的mapper注册到了Spring中,在Spring将所有的bean描述信息收集完成后,进行初始化bean的信息,此时调用了SqlSessionTemplate类来进行Mapper对象的初始化,使每一个MapperProxy对象中,都持有的是SqlSessionTemplate对象的引用。

2.在进行Sql执行时,Mapper对象调用SqlSessionTemplete来执行Sql,而SqlSessionTemplete又将这个任务交给了利用java反射在初始化信息时生成的DefaultSqlSession对象进行处理。DefaultSqlSession的反射对象又与普通的DefaultSqlSession有所不同,他在执行每一个方法时,是重新获取的SqlSession来进行执行的,而不是每一次都使用原本的SqlSession对象来执行,这样就做到了事务之间的隔离。

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