Spring事务管理器如何管理MybatisSqlSess

背景

spring管理mybatis时要依赖mybatis-spring,这里面究竟做了些什么?
image.png

spring事务管理器

TransactionAspectSupport#invokeWithinTransaction()定时了spring事务的模板

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
      final InvocationCallback invocation) throws Throwable {
   // If the transaction attribute is null, the method is non-transactional.
   TransactionAttributeSource tas = getTransactionAttributeSource();
   final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
   final TransactionManager tm = determineTransactionManager(txAttr);
   // ...省略其他
   PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
   final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

   if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
      // Standard transaction demarcation with getTransaction and commit/rollback calls.
      // 创建事务
      TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

      Object retVal;
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
         // 调用代理方法
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         // target invocation exception
         // 出现异常,回滚事务后将异常继续往上抛
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }

      if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
         // Set rollback-only in case of Vavr failure matching our rollback rules...
         TransactionStatus status = txInfo.getTransactionStatus();
         if (status != null && txAttr != null) {
            retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
         }
      }
      // 提交事务
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }
}
复制代码

DataSourceTransactionManager为例,一个典型事务的生命周期经历以下阶段

  1. 创建事务(Creating new transaction)
  2. 获取连接(Acquired Connection)
  3. 将连接改成手动提交(Switching JDBC Connection to manual commit)
  4. 准备事务提交(Initiating transaction commit)
  5. 提交事务(Committing JDBC transaction)
  6. 释放连接(Releasing JDBC Connection)

mybatis通用用法

  SqlSession sqlSession = sqlSessionFactory.openSession();
  sqlSession.selectOne("xxx");
  sqlSession.commit();
  sqlSession.close();
复制代码

围绕着SqlSession,分为获取,使用,提交,关闭等几个步骤

spring管理mybatis

如果spring要管理mybatis,必须解决两个问题,代理SqlSession和在事务中管理SqlSession的生命周期

SqlSessionTemplate

SqlSessionTemplate代理了SqlSession,在spring管理下所有mapper都必须经过这个切面得到真正的SqlSession

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) {
      // 省略
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}
复制代码

管理SqlSession生命周期

在事务中必须管理SqlSession生命周期,包括SqlSession的提交和关闭等
spring事务在TransactionSynchronization提供了回调接口,供第三方拓展,包括beforeCommit,beforeCompletion,afterCommit,afterCompletion等方法,同时在AbstractPlatformTransactionManager适当时候调用上述几个回调接口

TransactionSynchronizationManager里管理里同步需要的资源

// 同步所需的事务资源,例如Connection, SqlSession
private static final ThreadLocal<Map<Object, Object>> resources =
      new NamedThreadLocal<>("Transactional resources");
// 同步所需回调的接口,例如mybatis的SqlSessionSynchronization
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
      new NamedThreadLocal<>("Transaction synchronizations");
复制代码

spring在getSqlSession会将SqlSessionSynchronization注册成回调接口,这样spring事务就能管理SqlSession的生命周期了.

总结

最终,一个完整事务的流程:

  1. 创建事务(Creating new transaction)
  2. 获取连接(Acquired Connection)
  3. 将连接改成手动提交(Switching JDBC Connection to manual commit)
  • Creating a new SqlSession
  • Registering transaction synchronization for SqlSession
  • 执行各种SQL
  • Releasing transactional SqlSession
  • Transaction synchronization committing SqlSession
  • Transaction synchronization deregistering SqlSession
  • Transaction synchronization closing SqlSession
  1. 准备事务提交(Initiating transaction commit)
  2. 提交事务(Committing JDBC transaction)
  3. 释放连接(Releasing JDBC Connection)