【原創】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對象來執行,這樣就做到了事務之間的隔離。

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