likes
comments
collection
share

Spring事务管理器如何管理Mybatis SqlSession

作者站长头像
站长
· 阅读数 68

背景

spring管理mybatis时要依赖mybatis-spring,这里面究竟做了些什么? Spring事务管理器如何管理Mybatis SqlSession

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

例如,在AbstractPlatformTransactionManager#processCommit里,能够回调triggerBeforeCommittriggerBeforeCompletion等方法

// AbstractPlatformTransactionManager.java
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    // 省略...
    try {
        boolean unexpectedRollback = false;
        prepareForCommit(status);
        triggerBeforeCommit(status); // 回调TransactionSynchronization#beforeCommit
        triggerBeforeCompletion(status); // 回调TransactionSynchronization#beforeCompletion
        beforeCompletionInvoked = true;
        // 省略...
    }
}
protected final void triggerBeforeCommit(DefaultTransactionStatus status) {
   if (status.isNewSynchronization()) {
      if (status.isDebug()) {
         logger.trace("Triggering beforeCommit synchronization");
      }
      TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly());
   }
}

// TransactionSynchronizationUtils.java
public static void triggerBeforeCommit(boolean readOnly) {
   for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
      // 例如,最终回调beforeCommit
      synchronization.beforeCommit(readOnly);
   }
}

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)

其他补充

如果要在spring事务下显式管理SqlSession,据我所知mapper没有SqlSession的接口.只能通过SqlSessionTemplate来实现. 如上所述,SqlSessionTemplate代理了SqlSession,而SqlSessionTemplate是线程安全的,可以管理事务下的SqlSession,如下所示

int age = sqlSessionTemplate.selectOne("xxx.UserMapper.getAgeByUserIdForUpdate", 1);
sqlSessionTemplate.clearCache();
int anotherAge = sqlSessionTemplate.selectOne("xxx.UserMapper.getAgeByUserIdForUpdate", 1);

通过sqlSessionTemplate.clearCache()清空了SqlSession的一级缓存,anotherAge无法读取一级缓存而再次查询数据库

转载自:https://juejin.cn/post/6999145665787854856
评论
请登录