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捕获异常,然后吞掉不往外抛出。如果有这种业务需求,可以考虑使用这种方法。

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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 海量数据存储技术与解决方案[通俗易懂]

    海量数据存储难点:数据量过大,数据中什么情况都可能存在;软硬件要求高,系统资源占用率高;要求很高的处理方法和技巧。海量数据存储处理经验:一、选用优秀的数据库工具    现在的数据库工具厂家比较多,对海量数据的处理对所使用的数据库工具要求比较高,一般使用Oracle或者DB2,微软公司最近发布的SQLServer2005性能也不错。另外在BI领域:数据库,数据仓库,多维数据库,数据挖

  • stm32实用技巧:JLINK接口定义和使用JTAG或SW下载程序「建议收藏」

    stm32实用技巧:JLINK接口定义和使用JTAG或SW下载程序「建议收藏」需求    stm32下载程序,实用JLink的JTAG下载座,分为实现JTAG的下载和SW的下载功能JTAG:可仿真,可下载SW:可仿真,可下载JLINK接口开发板接口JTAG模式20pins10pinsSWD模式Keil软件使用1.点开配置2.选择Debug3.选择J-LINK4.点击Settings5.选择方式(JTAGorSW,笔者强烈建议使用SW,包括后期开发调试程序下载4…

  • java的filter方法(过滤器的功能)

    一.什么是Filter?Filter译为过滤器。 由于Servlet规范是开放的,借助于公众与开源社区的力量,Servlet规范越来越科学,功能也越来越强大。2000年,Sun公司在Servlet2.3规范中添加了Filter功能,并在Servlet2.4中对Filter进行了细节上的补充。二.运行原理:当客户端向服务器端发送一个请求时,如果有对应的过滤器进行拦截,过滤器可以改变请求的内容、或者重…

  • 使用Iocomp工控图表工具绘制实时曲线

    使用Iocomp工控图表工具绘制实时曲线概述:本文为使用Iocomp工控图表工具绘制实时曲线探索及研究教程,为大家介绍了Iocomp控件、实时曲线绘制方法、Iocomp界面操作,属性分类等。帮助学习者更好的运用Iocomp。

  • C++使用函数模板

    C++使用函数模板

  • 使用一个运放滤三次谐波 二阶有源带通滤波器的电路设计及波形效果

    使用一个运放滤三次谐波 二阶有源带通滤波器的电路设计及波形效果本文主要讲无限增益多路反馈有源带通滤波器的实现,工程实作,非理论知识,关于其他方法简略提,不做细究

发表回复

您的电子邮箱地址不会被公开。

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