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)


相关推荐

  • 手把手教你使用R语言做LASSO 回归

    手把手教你使用R语言做LASSO 回归LASSO回归也叫套索回归,是通过生成一个惩罚函数是回归模型中的变量系数进行压缩,达到防止过度拟合,解决严重共线性的问题,LASSO回归最先由英国人RobertTibshirani提出,目前在预测模型中应用非常广泛。在新格兰文献中,有大牛提出,对于变量过多而且变量数较少的模型拟合,首先要考虑使用LASSO惩罚函数。今天我们来讲讲怎么使用R语言通过LASSO回归构造预测模型。首先我们要下载R的glmnet包,由LASSO回归的发明人,斯坦福统计学家TrevorHastie领衔开发。加载

  • 取出字符串中数字的最大值

    取出字符串中数字的最大值取出字符串中数字的最大值

  • oracle insert select语句

    oracle insert select语句oracleinsertselect语句

  • 前端常见算法(js)「建议收藏」

    前端常见算法(js)「建议收藏」不管是在实际项目中还是在面试的时候我们大都会碰到算法问题,比如排序啊,比较大小啊之类的这些最基本的算法。我总结了一些,以后在碰到在慢慢补充。1.排序问题1.1冒泡排序冒泡排序算法就是依次比较大小,小的的大的进行位置上的交换。functionbubbleSort(arr){for(leti=0,l=arr.length;i&amp;amp;amp;amp;amp;lt;l-1;i++){…

  • 将DedeCMS从子目录移动到根目录的方法

    将DedeCMS从子目录移动到根目录的方法

    2021年10月10日
  • compound extremes_one是什么

    compound extremes_one是什么前言eXtremeComponents是一系列提供高级显示的开源JSP定制标签。当前的包含的组件为eXtremeTable,用于以表的形式显示数据。本文档处于更新中。大部分章节我将仅仅描述如何使用eXtremeTable。当然,为了使程序高效并具有更高的灵活性,源代码被再三重构。随后,我认为阐述一下如何做设计决定是值得的。我希望大家能知道使用extremeTable是多么容易,并且

发表回复

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

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