欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring源码分析之事务transaction下篇

程序员文章站 2022-04-21 23:51:22
...

上一篇文章已经详细分析了spring中如何创建事务(spring源码分析之事务transaction上篇),今天这篇文章主要是介绍spring中事务的回滚、事务提交、以及使用事务时的注意事项。这篇文章与上一篇文章有强关联,建议先去看上篇

一、事务回滚

我们只分析常用的传播属性REQUIRED(默认)、REQUIRES_NEW、NESTED,其他的可以自行阅读,也比较简单。我们还是拿上篇文章的例子来分析事务回滚和事务提交

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public void testInsert() throws Exception {
    String sql = "insert into user (name,age,sex) values(?,?,?)";
    jdbcTemplate.update(sql,new Object[]{"taott11",12,"man"});
    //int a = 1/0;
    //throw new Exception();
    String sql2 = "insert into user (name,age,sex) values(?,?,?)";
    jdbcTemplate.update(sql2,new Object[]{"tao",18,"woman"});
    //这里又调用了另外一个有事务标记的方法(动态代理方法)
    productService.queryUesr();
}
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void queryUesr() throws Exception {
    query();
    throw new RuntimeException();
}

我们暂时先不要看方法上面的传播属性,因为我们要针对各种情况做详细的分析,我们暂时具体分析下面四种情况

1.第一个业务方法抛异常即testInsert方法

2.第二个传播属性为REQUIRED,第二个业务方法抛异常即queryUesr

3.第二个传播属性为REQUIRES_NEW,第二个业务方法抛异常即queryUesr

4.第二个传播属性为NESTED,第二个业务方法抛异常即queryUesr

第一个方法是UserService对象,第二个方法是ProductService对象

TransactionAspectSupport.invokeWithinTransaction()
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
      final InvocationCallback invocation) throws Throwable {

   	//省略...创建事务的代码之前分析过,不再分析
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
          //1.调用真是的业务逻辑
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         // target invocation exception
          //2.核心逻辑之一,事务回滚
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }

      if (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);
         }
      }
		//3.核心逻辑之一,事务提交
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }

 //...省略代码,暂时先不看
}
1.第一个方法抛异常分析

第一个方法抛异常–即注释1的地方抛异常,假如我们没有catch处理该异常的情况下,一定会走注释2的逻辑(被这个catch捕获),所以一定会走completeTransactionAfterThrowing(),同时第二个业务方法也不会调用到,所以只有一个事务分析起来比较简单

1-1.completeTransactionAfterThrowing()
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
   if (txInfo != null && txInfo.getTransactionStatus() != null) {
      if (logger.isTraceEnabled()) {
         logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
               "] after exception: " + ex);
      }
       //默认的回滚策略是RuntimeException或者其子类,可以通过rollbackFor配置
       //1.如果满足上面的回滚要求
      if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
         try {
            txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
         }
         catch (TransactionSystemException ex2) {
            logger.error("Application exception overridden by rollback exception", ex);
            ex2.initApplicationException(ex);
            throw ex2;
         }
         catch (RuntimeException | Error ex2) {
            logger.error("Application exception overridden by rollback exception", ex);
            throw ex2;
         }
      }
       //2.不满足回滚策略,比如抛出Exception时,可自行实验
      else {
         // We don't roll back on this exception.
         // Will still roll back if TransactionStatus.isRollbackOnly() is true.
         try {
            txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
         }
         catch (TransactionSystemException ex2) {
            logger.error("Application exception overridden by commit exception", ex);
            ex2.initApplicationException(ex);
            throw ex2;
         }
         catch (RuntimeException | Error ex2) {
            logger.error("Application exception overridden by commit exception", ex);
            throw ex2;
         }
      }
   }
}

比较简单,直接看注释把,接下来看txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus())

1-1-1.rollback()
@Override
public final void rollback(TransactionStatus status) throws TransactionException {
   if (status.isCompleted()) {
      throw new IllegalTransactionStateException(
            "Transaction is already completed - do not call commit or rollback more than once per transaction");
   }

   DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
   processRollback(defStatus, false);
}
1-1-1-1.processRollback
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
   try {
      boolean unexpectedRollback = unexpected;

      try {
         triggerBeforeCompletion(status);
		//1.判断是否有回滚点
         if (status.hasSavepoint()) {
            if (status.isDebug()) {
               logger.debug("Rolling back transaction to savepoint");
            }
            status.rollbackToHeldSavepoint();
         }
          //2.只有status的newTransaction=true才有资格回滚
         else if (status.isNewTransaction()) {
            if (status.isDebug()) {
               logger.debug("Initiating transaction rollback");
            }
            doRollback(status);
         }
          //3.其他,让外面的一层事务回滚处理
         else {
            // Participating in larger transaction
            if (status.hasTransaction()) {
               if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                  if (status.isDebug()) {
                     logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                  }
                  doSetRollbackOnly(status);
               }
               else {
                  if (status.isDebug()) {
                     logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                  }
               }
            }
            else {
               logger.debug("Should roll back transaction but cannot - no transaction available");
            }
            // Unexpected rollback only matters here if we're asked to fail early
            if (!isFailEarlyOnGlobalRollbackOnly()) {
               unexpectedRollback = false;
            }
         }
      }
      catch (RuntimeException | Error ex) {
         triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
         throw ex;
      }

      triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

      // Raise UnexpectedRollbackException if we had a global rollback-only marker
      if (unexpectedRollback) {
         throw new UnexpectedRollbackException(
               "Transaction rolled back because it has been marked as rollback-only");
      }
   }
   finally {
      cleanupAfterCompletion(status);
   }
}

这里是第一个方法第一个注释,分下面两种情况

情况1:如果这个第一个事务就配置的传播属性是NESTED,那么是走到注释1,回滚到回滚点,也就是回滚到调用业务代码之前的回滚点,即回滚之后对数据库来说没有做任何事情,回滚代码其实就是jdbc的封装而已

public void rollbackToSavepoint(Object savepoint) throws TransactionException {
   ConnectionHolder conHolder = getConnectionHolderForSavepoint();
   try {
      conHolder.getConnection().rollback((Savepoint) savepoint);
      conHolder.resetRollbackOnly();
   }
   catch (Throwable ex) {
      throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
   }
}

情况2:没有回滚点,代码走到注释2,此时newTransaction=true(不清楚的可以看上篇),所以有资格执行执行doRollback

protected void doRollback(DefaultTransactionStatus status) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
   Connection con = txObject.getConnectionHolder().getConnection();
   if (status.isDebug()) {
      logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
   }
   try {
      con.rollback();
   }
   catch (SQLException ex) {
      throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
   }
}

代码也是非常的简单,直接就是jdbc的回滚代码。到这里第一种代码回滚的情况分析完成了

2.第二个方法抛异常-第二个方法是REQUIRED传播属性

重复的代码不做分析了,根据上篇文章的分析,这种情况第二个事务的状态的newTransaction=false,所以是没有资格做回滚操作,那么它会将异常抛出去,那么最外层的事务,也就是第一个方法的事务处理

try {
   txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
   logger.error("Application exception overridden by rollback exception", ex);
   ex2.initApplicationException(ex);
    //异常继续抛出
   throw ex2;
}

那么就到了第一个方法处理异常了,这种情况就变成了和情况1一样了,就不继续分析了

3.第二个方法抛异常-第二个方法是REQUIRES_NEW传播属性

上面文章已经分析过了,这种情况的事务创建时第二个事务会将第一个事务挂起,第二个事务的newTransaction=true,但是第二个事务的connection和第一个事务的connection不是同一个。它是有资格回滚,所以它回滚的只是自己的业务部分的逻辑,回滚的操作和上面的处理逻辑一样,不另外说明。但是还有个重要的地方,第二个方法回滚成功后会唤醒第一个被挂起的事务,唤醒的逻辑是在cleanAfterCompletion(),其实这个方法在finally中,回滚都会走这个逻辑。

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
   status.setCompleted();
   if (status.isNewSynchronization()) {
      TransactionSynchronizationManager.clear();
   }
   if (status.isNewTransaction()) {
      doCleanupAfterCompletion(status.getTransaction());
   }
    //1.判断这个对象是否为null
   if (status.getSuspendedResources() != null) {
      if (status.isDebug()) {
         logger.debug("Resuming suspended transaction after completion of inner transaction");
      }
      Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
      //2.唤醒
       resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
   }
}

在上篇中,我们看到了,创建第二个事务时如果将第一个事务挂起,它会被封装到SuspendedResources,这里就能拿到它,然后唤醒。唤醒的逻辑我们基本上都能猜得到,跟挂起的逻辑相反,上篇文章中已经分析了,挂起时会将当前事务(第二个事务)的connectionHolder设置成null,另外删除threadlocal中的map,那么唤醒就是相反的逻辑了,感兴趣自行分析(重新往threadlocal中增加)。另外就是第二个方法抛异常会继续往上面抛,那就是第一层的catch里面也会捕获到异常,也会做相应的回滚操作,逻辑通上面一样。

4.第二个方法抛异常-第二个方法是NESTED传播属性

这种情况上篇文章分析过,第二个创建时(在执行业务逻辑之前)会创建回滚点,那么当第二个方法抛异常时,会回滚掉这个回滚点上。前面已经分析过回滚点回滚了,不再继续,另外需要注意的也是异常会往上抛,导致第一个方法也会回滚。

二、事务提交

事务提交分下面几种情况分析,其实理解了事务的回滚,再看事务的提交会更加的简单

1.第二个传播属性为REQUIRED

2.第二个传播属性为REQUIRES_NEW

3.第二个传播属性为NESTED

先看下面的提交的代码

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
		try {
			boolean beforeCompletionInvoked = false;

			try {
                //提交之前的准备工作
				boolean unexpectedRollback = false;
				prepareForCommit(status);
				triggerBeforeCommit(status);
				triggerBeforeCompletion(status);
				beforeCompletionInvoked = true;
				//1.是否有回滚点
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Releasing transaction savepoint");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					status.releaseHeldSavepoint();
				}
                //2.newTransaction=true才能提交
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction commit");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					doCommit(status);
				}
				else if (isFailEarlyOnGlobalRollbackOnly()) {
					unexpectedRollback = status.isGlobalRollbackOnly();
				}

				// Throw UnexpectedRollbackException if we have a global rollback-only
				// marker but still didn't get a corresponding exception from commit.
				if (unexpectedRollback) {
					throw new UnexpectedRollbackException(
							"Transaction silently rolled back because it has been marked as rollback-only");
				}
			}
			catch (UnexpectedRollbackException ex) {
				// can only be caused by doCommit
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
				throw ex;
			}
			catch (TransactionException ex) {
				// can only be caused by doCommit
				if (isRollbackOnCommitFailure()) {
					doRollbackOnCommitException(status, ex);
				}
				else {
					triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				}
				throw ex;
			}
			catch (RuntimeException | Error ex) {
				if (!beforeCompletionInvoked) {
					triggerBeforeCompletion(status);
				}
				doRollbackOnCommitException(status, ex);
				throw ex;
			}

			// Trigger afterCommit callbacks, with an exception thrown there
			// propagated to callers but the transaction still considered as committed.
			try {
				triggerAfterCommit(status);
			}
			finally {
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
			}

		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

这个代码和上面回滚的很像,下面逐个分析

1.第二个传播属性为REQUIRED

这种情况由于第二个事务的newTransaction=false,所以没有提交的资格,必须由外层的事务提交,由于和外层的事务时同一个连接(不同的事务,但是是同一个连接),所以当外层由异常时,第二个方法做的sql操作也会被回滚掉。

2.第二个传播属性为REQUIRES_NEW

这种情况第一个事务和第二个事务的连接不同,并且第一个事务被挂起了,第二个事务的newTransaction=true所以第二个事务提交完成后,不管第一个事务是否抛异常都不影响第二个事务的结果。还有一个需要注意的是,这个时候第二个事务提交完成后,也会将第一个挂起的事务进行恢复,逻辑同上面抛异常的恢复逻辑一致。所以第一个事务后面也会进行事务提交,提交的逻辑同上

3.第二个传播属性为NESTED

这种情况下,我们看下面的回滚的逻辑

if (status.hasSavepoint()) {
   if (status.isDebug()) {
      logger.debug("Releasing transaction savepoint");
   }
   unexpectedRollback = status.isGlobalRollbackOnly();
   status.releaseHeldSavepoint();
}

第二个事务会清除回滚点,说明第二个业务操作没有问题。同时第一个事务和第二个事务的连接使用的是统一个连接,所以当第二个事务回滚完成后(清除了回滚点),假如第一个事务有异常,并且回滚时,第二个事务做的sql操作也会被回滚。

到这里事务的提交也算分析完成了,这里面只是分析了几种常见的情况。其真实的情况远不止这几种情况,但是分析的方法是一样的,大家可以自行分析。

三、事务的注意事项

如果没有彻底弄清事务的原理,有时候编写的事务并不能满足我们我们的需求,可能经常会遇到意想不到的情况,下面对常见的问题做个小的总结

1.嵌套调用本对象的方法,第二个方法事务不生效

伪代码如下:

@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void afun(){
    bfun();
}

@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void bfun(){

}

此时bfun()的事务是不生效的,因为这里使用的cglib动态代理,当从afun()里面调用到bfun时就没有增强了,其调用的时真是的对象的bfun(),这种情况再spring aop也会遇到,解决方式可以通过自己注入自己,或者使用AopContext

2.务必注意只有事务状态newTransaction=true时才有资格提交或者回滚事务

这一点对我们分析事务时非常的有用

3.一定要注意异常会往上抛

这一点刚才在上面的分析中也有体现出来,当我们里层的事务抛出异常时,spring不会吞掉我们的异常,它会继续往上抛,这就导致外层的事务也会catch到异常,做相应的回滚操作,这一点尤其要小心。比如下面伪代码

@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void cfun(){
    //该方法的传播属为required
    bservice.afun();
    cservice.afun();
}

此时如果cservice.afun()方法抛出异常了,那么bservice.afun()执行的sql也不会生效

4.可以根据业务需求,使用catch处理异常不网上抛,来达到特定的业务需求

这种情况就是不让里层的事务影响到外层的事务,可以使用catch捕获异常,然后吞掉不往外抛出。如果有这种业务需求,可以考虑使用这种方法。

好了到这里就算简单的把注解式事务介绍完毕了,希望对大家有帮助。同时如果有错误的地方也欢迎大家指正!!!谢谢