spring 事务源码分析(五)传播机制---REQUIRED

871 阅读4分钟

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

前言

上篇博客之后,本文主要是详解 REQUIRED这个事务传播机制

最主要是回答如下问题

  • 子事务提交,父事务会提交么?

  • 父事务提交,子事务会提交么?

  • 子事务回滚,父事务会回滚么?

  • 父事务会回滚,子事务会回滚么?

答案可能大家都知道,但是本文主要是从源码的细节来解析

伪代码

最外层的事务

@Transactional
method_a(){
  
  method_b();
}

调用的子事务

@Transactional
method_b(){
  
  
}

源码分析

从头到尾在梳理一下,如下是TransactionAspectSupport#invokeWithinTransaction

也就是事务注解切面大致的逻辑

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
   // 获取事务信息
   TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
   Object retVal = null;
   try {
      // 执行dbio
      retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
      // 处理异常
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
   }
   finally {
     // 清除当前线程上下文的事务信息
      cleanupTransactionInfo(txInfo);
   }
  // 提交事务(这里的提交事务包含了线程中spring 事务信息的处理,不一定是db层次的commit)
   commitTransactionAfterReturning(txInfo);
   return retVal;
}

获取事务信息

createTransactionIfNecessary

这方法也很简单,核心就是调用两个方法getTransactionprepareTransactionInfo

image-20211102145123803

getTransaction

AbstractPlatformTransactionManager#getTransaction

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
  // 获取当前线程上下文中的事务
   Object transaction = doGetTransaction();

   if (isExistingTransaction(transaction)) {
     //  如果是method_b 方法的切面会执行到这里
      // 如果当前线程上下文已经存在事务,则根据事务注解的隔离级别处理
      return handleExistingTransaction(definition, transaction, debugEnabled);
   }
 ......
   if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
			.....
		}
		else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
      // 如果是method_a 方法的切面会执行到这里
			SuspendedResourcesHolder suspendedResources = suspend(null);
			
			try {
				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
				DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		else {
			....
		}
}

handleExistingTransaction

private TransactionStatus handleExistingTransaction(
      TransactionDefinition definition, Object transaction, boolean debugEnabled)
      throws TransactionException {
   .....
   boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
   return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

prepareTransactionStatus

其实就是封装一个TransactionStatus对象

image-20211102144710287

prepareTransactionInfo

protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
      @Nullable TransactionAttribute txAttr, String joinpointIdentification,
      @Nullable TransactionStatus status) {

   TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
   if (txAttr != null) {
      
      // 将上文中创建的TransactionStatus 赋值到事务信息中
      txInfo.newTransactionStatus(status);
   }
   else {
     
   }

   // 绑定 TransactionInfo 到当前线程上下文
   txInfo.bindToThread();
   return txInfo;
}

image-20211102145459244

所以这里实际上是有两个TransactionInfo,当时我还是很疑惑,不是说 REQUIRED实际上只有一个事务么

现在这里明显是不同的TransactionInfo对象

后来再看获取sqlsession的代码时才发现自己概念搞混了

执行业务sql

之前的文章我也详细说过这个方法,任何一个mybatis业务层 在执行sql之前都会获取SqlSession

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

  // 当前线程上下文 sqlsession 持有者
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  // sqlsession 
  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    // 如果已经存在则直接返回
    return session;
  }

  // 创建session
  session = sessionFactory.openSession(executorType);
  // 将session 注册到当前线程上下文
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

这里就回答了上面的问题,虽然TransactionInfo在两个@Transactional注解的方法中是不一样的,但是只要是在同一个线程上下文中,他们实际和db进行io通讯的session是一样的,那对于db来说就是一个事务

换句话说,这里实际上是有两个概念,一个数据库的事务,一个是spring 的事务,一个可以看做是实际的,一个看做是逻辑上的。

从代码来看只要方法是被Transactional注解了,并且切面是解析到了,那么spring 就会创建TransactionStatusTransactionInfo这算是spring 自己的机制

提交事务

AbstractPlatformTransactionManager#commit

image-20211102151313157

processCommit

省略与本文无关的代码部分

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
   try {
     .....
      try {
     .....
         if (status.hasSavepoint()) {
            ......
         }
        
         else if (status.isNewTransaction()) {
           //大家打个断点就知道了,如果是 method_b() 提交 这个判断是是进不来的
          ....
           // 执行事务提交
            doCommit(status);
         }
         .....

      }
      catch (UnexpectedRollbackException ex) {
         .....
      }
      catch (TransactionException ex) {
        .....
      }
      catch (RuntimeException | Error ex) {
         ....
      }
    
   }
   finally {
      cleanupAfterCompletion(status);
   }
}

其中关键的判断点进不去是因为handleExistingTransactionnewTransaction这个属性为空

image-20211102151924421

而当method_a方法执行结束需要提交时,他的TransactionInfonewTransaction属性是true,所以可以提交

回滚

回滚代码最核心的部分是

AbstractPlatformTransactionManager#rollback

image-20211102152937834

又调用了 processRollback

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
   try {


      try {


         if (status.hasSavepoint()) {
            ...
         }
         else if (status.isNewTransaction()) {
           
           // 如果是method_a 方法的切面会执行到这里,在上文中的getTransaction 有介绍
            doRollback(status);
         }
         else {
            if (status.hasTransaction()) {
              // 如果是method_b 方法的切面会执行到这里
              // globalRollbackOnParticipationFailure 默认是true
               if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                  ....
                  doSetRollbackOnly(status);
               }
               else {
                 ....
               }
            }
            else {
               ....
            }
           ...
         }
      }
      catch (RuntimeException | Error ex) {
         ....
      }

     ...
   }
   finally {
      cleanupAfterCompletion(status);
   }
}

这只是逻辑上回滚,也就是method_b 方法事务最终回滚的逻辑

image-20211102153706362

那么method_a 呢

上文中 AbstractPlatformTransactionManager#commit

public final void commit(TransactionStatus status) throws TransactionException {
	
		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		........

     // 如果当前线程中 connect holder 设置了回滚标记
		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
			// 执行回滚
			processRollback(defStatus, true);
			return;
		}
		// 执行提交
		processCommit(defStatus);
	}

这是实际上的db回滚最终调用方法

image-20211102153801570

回答前言问题

业务代码大致其实就两种

@Transactional(rollbackFor = Exception.class)
method_a(){
  
  method_b();
}

@Transactional(rollbackFor = Exception.class)
method_b(){
  
}
@Transactional(rollbackFor = Exception.class)
method_a(){
  
  try{
    method_b();
  }catch(){
    
  }
}

@Transactional(rollbackFor = Exception.class)
method_b(){
  
}

那么其实无论是哪种情况只要任何一个事务回滚,最终都会全部一起回滚

如果是这样

@Transactional(rollbackFor = Exception.class)
method_a(){
  method_b();
  
}

@Transactional(rollbackFor = Exception.class)
method_b(){
  try{
    
  }catch(){
    
  }
}

那么在b中就算异常也不会

总结

简单说这种事务传播机制的话,spring 级别的事务不是同一个,但是他们的db级别的事务是同一个