spring源码分析之事务transaction下篇

spring源码分析之事务transaction下篇

上一篇文章已经详细分析了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捕获异常,然后吞掉不往外抛出。如果有这种业务需求,可以考虑使用这种方法。

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111197.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • eureka底层原理「建议收藏」

    eureka底层原理「建议收藏」什么是注册中心全称为:服务注册与发现,rpc远程调用框架核心思想,在于注册中心,使用注册中心管理每个服务与服务之间的依赖关系,这种关系被称为服务治理概念;任何rpc远程调用框架都至少有一个注册中心;服务注册将服务信息注册到注册中心,相当于告诉公司的人,我已经打卡上班了,可以分配工作任务给我了,比如现在我们有一个服务消费者服务A,和两个节点的服务提供者,服务B。服务A和服务B在启动的时候都会向注册中心进行服务注册。服务发现从注册中心获取已注册的服务信息,发现有哪些可以调用的服务可供我使用;相

    2022年10月7日
    0
  • Spring boot Value注入 未整理 待完善

    Spring boot Value注入 未整理 待完善Spring boot Value注入 未整理 待完善

    2022年4月21日
    52
  • ETH挖矿显卡算力大全[通俗易懂]

    ETH挖矿显卡算力大全[通俗易懂]大家买显卡挖ETH,肯定最关心算力了,这里我整理一版,供大家参考,目前只有主流的整理上了,后期会完善更多的供大家参考!欢迎大家加入大力矿工群:621159725  软件下载:百度云盘链接:https://pan.baidu.com/s/1o9tw41k密码:vkyi…

    2022年6月13日
    36
  • js取整数四舍五入「建议收藏」

    js取整数四舍五入「建议收藏」1.丢弃小数部分,保留整数部分parseInt(5/2)2.向上取整,有小数就整数部分加1 Math.ceil(5/2)3,四舍五入.Math.round(5/2)4,向下取整 Math.floor(5/2)Math对象的方法FF:Firefox,N:Netscape,IE:InternetExplorer方法描述FF

    2022年6月18日
    41
  • 计算机网络ip地址分类的范围,ip地址分类及范围_ip地址由什么组成

    计算机网络ip地址分类的范围,ip地址分类及范围_ip地址由什么组成ip地址分类及范围1、A类IP地址一个A类IP地址是指,在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码。A类IP地址中网络的标识长度为8位,主机标识的长度为24位,A类网络地址数量较少,有126个网络,每个网络可以容纳主机数达1600多万台。A类IP地址地址范围1.0.0.1到127.255.255.254。2、B类IP地址一个B类IP地址是指,在IP地址的四段号码…

    2022年6月4日
    47
  • 和黑客斗争的 6 天![通俗易懂]

    和黑客斗争的 6 天![通俗易懂]互联网公司工作,很难避免不和黑客们打交道,我呆过的两家互联网公司,几乎每月每天每分钟都有黑客在公司网站上扫描。有的是寻找Sql注入的缺口,有的是寻找线上服务器可能存在的漏洞,大部分都…

    2022年6月11日
    29

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号