Spring+MyBatis源碼解析之SqlSessionTemplate

1、產生的背景

使用MyBatis就要保證SqlSession實例的線程安全,就必須要爲每一次的請求單獨創建一個SqlSession。但如果每一次請求都要用openSession()自己去創建,就比較麻煩了。

​ 所以在spring中,我們使用的mybatis-spring包中提供了一個線程安全的SqlSession的包裝類sqlSessionTemplate,用它來替代SqlSession.因爲他是線程安全的,所以可以在所有的Service層來共享一個實例(默認爲單例)。

​ 這個跟Spring封裝其他的組件是一樣的,比如JdbcTemplate, RedisTemplate 等等,也是 SpringMyBatis 整合的最關鍵的一個類。

2、創建SqlSessionTemplate

2.1 生成代理對象

SqlSessionTemplate 裏面有DefaultSqlSession 的所有的方法:select One()、select List()、insert()、update()、delete(),不過它都是通過一個代理對象實現的。這個代理對象在構造方法裏面通過一個代理類創建:

  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接口,代理類爲SqlSessionInterceptor
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }

所有的方法都會先走到內部代理類 SqlSessionInterceptorinvoke()方法:

 private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //獲取新的sqlSession,此處的爲線程安全的sqlSession(使用了threadLocal)
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        。。。
        return result;
      } catch (Throwable t) {
          。。。。。。。。。。。。。。。。
    }
  }

在使用SqlSessionTemplate調用方法的時候,先調用代理對象SqlSessionInterceptorinvoke()方法,通過getSqlSession()獲取一個新的sqlSession對象,保證了每個請求是生成一個sqlSession,確保了線程安全。

2.2、獲取SqlSession

​ 首先會使用工廠類、執行器類型、異常解析器創建一個 sqlSession,然後再調用
sqlSession 的實現類,實際上就是在這裏調用了 DefaultSqlSession 的方法。此處調用的getSqlSession()方法用來獲取一個SqlSession對象。

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    //獲取SqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

TransactionSynchronizationManager獲取當前線程threadLocal是否有SqlSessionHolder,如果有就從SqlSessionHolder取出當前SqlSession,如果當前線程threadLocal沒有SqlSessionHolder,就從sessionFactory中創建一個SqlSession,最後註冊會話到當前線程threadLocal中。

2.3 事務管理器

public abstract class TransactionSynchronizationManager {

	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
 	 // 存儲當前線程事務資源,比如Connection、session等
	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");
	// 存儲當前線程事務同步回調器
   // 當有事務,該字段會被初始化,即激活當前線程事務管理器
	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");
    
    /**
    *從當前線程的threadLocal中獲取SqlSessionHolder
    */
    @Nullable
	public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        //獲取
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
					Thread.currentThread().getName() + "]");
		}
		return value;
	}
    
    @Nullable
	private static Object doGetResource(Object actualKey) {
        //private static final ThreadLocal<Map<Object, Object>> resources =
		//	new NamedThreadLocal<>("Transactional resources");
		Map<Object, Object> map = resources.get();
		if (map == null) {
			return null;
		}
		Object value = map.get(actualKey);
		// Transparently remove ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			map.remove(actualKey);
			// Remove entire ThreadLocal if empty...
			if (map.isEmpty()) {
				resources.remove();
			}
			value = null;
		}
		return value;
	}
    
}

這是spring的一個當前線程事務管理器,它允許將當前資源存儲到當前線程ThreadLocal中,SqlSessionHolder是保存在resources中。

2.4 註冊SessionHolder

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  SqlSessionHolder holder;
  // 判斷當前是否有事務
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();
    // 判斷當前環境配置的事務管理工廠是否是SpringManagedTransactionFactory(默認)
    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
      }

      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      // 綁定當前SqlSessionHolder到線程ThreadLocal中
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      // 註冊SqlSession同步回調器
      TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      // 會話使用次數+1
      holder.requested();
    } else {
      if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
        }
      } else {
        throw new TransientDataAccessResourceException(
          "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
      }
    }
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
    }
  }
}

註冊SqlSession到當前線程事務管理器的條件首先是當前環境中有事務,否則不註冊,判斷是否有事務的條件是synchronizationsThreadLocal是否爲空:

public static boolean isSynchronizationActive() {
  return (synchronizations.get() != null);
}

每當我們開啓一個事務,會調用initSynchronization()方法進行初始化synchronizations,以激活當前線程事務管理器。

public static void initSynchronization() throws IllegalStateException {
  if (isSynchronizationActive()) {
    throw new IllegalStateException("Cannot activate transaction synchronization - already active");
  }
  logger.trace("Initializing transaction synchronization");
  synchronizations.set(new LinkedHashSet<TransactionSynchronization>());
}

所以當前有事務時,會註冊SqlSession到當前線程ThreadLocal中。

2.5 小結

mybatisSqlSession是非線程安全的,如下:

/**
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {
    
}

在spring中,通過ThreadLocal爲每一個請求創建一個SqlSession,這樣保證了線程的安全,從前面的背景中瞭解到在spring中是使用了SqlSession的包裝類SqlSessionTemplate,用它來替代SqlSession。接下里就重點介紹SqlSessionTemplate

3、獲取SqlSessionTemplate

​ 接着我們來講解怎麼獲取這個SqlSessionTemplate,在 Service 層可以使用@Autowired自動注入的 Mapper 接口,需要保存在
BeanFactory(比如 XmlWebApplicationContext)中。也就是說接口肯定是在 Spring
啓動的時候被掃描了,註冊過的。我們先來看一下spring的配置文件applicationContext.xml

<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.org.nci.henry.dao"/>
</bean>

3.1 MapperScannerConfigurer

MapperScannerConfigurer 實現了 BeanDefinitionRegistryPostProcessor 接口,
BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的子類,可以通過編碼的方式修改、新增或者刪除某些 Bean 的定義。類圖如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Y1G6wflY-1590221543779)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200523123506426.png)]

spring初始化ioc容器的時候,會調用postProcessBeanDefinitionRegistry()方法;

//MapperScannerConfigurer.java
    MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
    
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}    
    

scanner.scan() 方 法 是 ClassPathBeanDefinitionScanner 中 的 , 而 它 的 子 類
ClassPathMapperScanner 覆 蓋 了 doScan()方 法 , 在 doScan()中 調 用 了
processBeanDefinitions()

public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

調用子類doScan()方法,把接口全部注添加到beanDefinitions中。

   public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { 
.............
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 {
     //對生成的beanDefinition做處理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
   }

3.2 MapperFactoryBean

調用processBeanDefinitions(),在註冊前將beanClass設置爲MapperFactoryBean,也就是說所有的Mapper接口在容器裏都被註冊成了一個MapperFactoryBean.

 public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { 
     
    private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
          String beanClassName = definition.getBeanClassName();
    ................
            /**核心在此處,爲definition設置beanClass,也就是對bd做後置處理,改變bd  		  *的屬性,設置成mapperFactoryBeanClass,此爲MapperFactoryBean對象
            */
          definition.setBeanClass(this.mapperFactoryBeanClass);

          definition.getPropertyValues().add("addToConfig", this.addToConfig);

  }
}

來看下MapperFactoryBean這個類

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

這個類繼承了SqlSessionDaoSupport,我們知道這個類中包含有spring操作MyBatis的sqlSessionTemplate的對象。

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;
}

​ 因爲MapperFactoryBean實現了FactoryBean接口,所以在實例化的時候會調用getObject()方法,瞭解過MyBatis源碼的同學應該就能知道,此處調用getMapper()是爲了生成一個代理對象。這個我會在後續的文章中講解MyBatis源碼的時候將調用getMapper()方法生成代理對象的流程做詳細介紹,此處不再贅述。

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

3.3 SqlSessionTemplate

先通過調用父類SqlSessionDaoSupportgetSqlSession()拿到sqlSessionTemplate對象,

public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

繼續調用SqlSessionTemplategetMapper()

 public class SqlSessionTemplate implements SqlSession, DisposableBean {

    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
      }
     
      @Override
      public Configuration getConfiguration() {
          //獲取Configuration對象
        return this.sqlSessionFactory.getConfiguration();
      } 
          
 }

3.4 Configuration

調用Configuration類中getMapper

public class Configuration {
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
}
public class MapperRegistry {
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
      }
      try {
          //生成動態代理
        return mapperProxyFactory.newInstance(sqlSession);
      } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
      }
    }
}

​ 後面就是MyBatis中生成mapper代理對象的流程了,後續會在MyBatis源碼篇中詳細講解,此處不再詳細贅述。

通 過 工 廠 類MapperProxyFactory獲得一個MapperProxy 代理對象。也就是說,我們注入到Service層的接口,實際上還是一個 MapperProxy代理對象。所以最後調用 Mapper 接口的方法,也是執行 MapperProxyinvoke()方法.

3.5 小結

簡單總結一下上面的流程:

1)MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor類,spring初始化IoC容器bean名字爲mapperScannerConfigurer的時候,會調用postProcessBeanDefinitionRegistry()方法;

2)通過MapperScannerConfigurer調用postProcessBeanDefinitionRegistry,來進行指定mapper包的掃描,從而獲取到beanDefinition;

3)對beanDefinition進行處理,通過ClassPathMapperScanner類中的processBeanDefinitions()的對bean進行重新處理:

//這個definition就變成了mapperFactoryBeanClass對象(MapperFactoryBean)
definition.setBeanClass(this.mapperFactoryBeanClass);

MapperFactoryBean是一個繼承了SqlSessionDaoSupport的類,它可以對sqlSessionTemplate進行一系列的操作,SqlSessionTemplate實現了SqlSession的接口,爲了方便理解,我們可以簡單的把SqlSessionTemplate當成是MyBatis中的sqlSession來使用,而spring爲了更加便捷的使用MyBatis,所以將sqlSession,封裝成了SqlSessionTemplate來使用。

4)在初始化掃描到的mapper對應的bean時,獲取到的是MapperFactoryBean的bean,此時需要調用該類的getObject()方法,才能獲取真正的bean.

5)先調用getSqlSession()方法獲取到sqlSessionTemplate,調用getMapper()方法生成mapper相關的代理對象,這樣我們就可以直接在業務代碼中加入@Autowired進行依賴注入,然後輕鬆使用就可以了。

    @Autowired
    private UserMapper userMapper;

4、應用:

經過上面的分析我們基本瞭解獲取sqlSessionTemplate的基本原理,所以我們使用 Mapper 的時候,只需要在加了 Service 註解的類裏面使用@Autowired注入 Mapper 接口就好了。

@Service
public class User UserService {
	@Autowired
    private UserMapper userMapper;
    
    public List<User> getAll() {
        return userMapper.selectByAll();
	}
}

通過每一個注入Mapper的地方,都可以拿到sqlSessionTemplate

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