vlambda博客
学习文章列表

这波spring事务的<关于捕获异常还回滚>的总结爽到自己了

这波spring事务的<关于捕获异常还回滚>的总结爽到自己了

声明:网上很多的像是事务传播机制以,就不重复,来点自己有问题之后搜罗很长时间加上看源码才弄清楚的总结吧!

The First Problem

同一个类中方法调用事务失效

spring 提供了事物管理这个功能,直接在方法上或类上加@Transactional,也可以使用XML配置事务。

测试中发现当一个方法在同一个类被其它方法调用的时候,导致事务的传播行为不生效。

代码示例

@Service
@Transactional  //在类上加Transactional默认就是该类中的方法都默认使用事务,且传播机制都是REQUIRED
public class Demo {

 public void A() {
  B();  //其实总会忘记同类中方法之间的调用会有this的  就是this.B();
 }
 public void B() {
  
 }
}

解释

如果A与B都加上@Transactional,也就上边的情况,那么调用A,A的事物会生效,本该以为A中调用B的时候,也是由于B的事务会加入到A中,但是,其实A中通过this.B()的调用是没有触发spring 的事务的,其实涉及到AOP的一些知识,看总结即可。

总结

spring的事物管理通过AOP代理来实现, 根据aop的思想,不可能在具体类Demo上直接处理事物,而是通过代理类来处理,代理类在调用具体类的方法来实现,根据上面的情景A通过this调用B,那么此时相当于调用B时是没有经过代理类的调用,因此spring无法对事物的传播行为做处理。

The Second Problem

try-catch处理抛出异常,事务还会回滚?

在Spring的声明式事务中手动捕获异常,居然判定回滚了,这是什么操作?话不多说直接上代码?

代码

@Service
public class A {
 
    @Autowired
    private B b;
 
    @Autowired
    private C c;
 
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void operate() {
        try {
            b.insertB();
            c.insertC();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 
@Service
public class B {
 
    @Autowired
    private BM bm;
 
    @Transactional(propagation = Propagation.REQUIRED)
    public int insertB() {
        return bm.insert("B");
    }
}
 
@Service
public class C {
 
    @Autowired
    private CM cm;
 
    @Transactional(propagation = Propagation.REQUIRED)
    public int insertC() {
        return cm.insert("C");
    }
}

上面这段代码,在正常的情况的我们会往B表和C表中各插入一条数据,那么当代码出现异常时又会怎么样呢?

我们现在假设B插入数据成功,但是C插入数据失败了,此时异常会上抛到A,被A中operate方法的try - cache所捕获,正常来说此时数据库中B能插入一条记录,而C表插入失败,这是我们期望的情况,但事实却不是,实际情况是B表没有插入数据,C表也没有插入数据,也就是说整个操作被Spring给回滚了

解决方法如下:

如果代码稍稍变动一下,将try-catch放在insertC的代码块中,在同样的场景下,B中会成功插入一条记录

解释:其实很底层的解释也有,但是简单点说,提出来重要的代码块(毕竟很多我也没看懂,就不胡扯了,就捡重要的说)

注意:rollbackOnly 这个boolean变量,当为true的时候表示是要回滚的。

当C.operate执行的时候,抛出异常,执行下边方法status

rivate void processRollback(DefaultTransactionStatus status, boolean unexpected) {
 try {
  boolean unexpectedRollback = unexpected;
 
  try {
   triggerBeforeCompletion(status);
 
   if (status.hasSavepoint()) {
    if (status.isDebug()) {
     logger.debug("Rolling back transaction to savepoint");
    }
    status.rollbackToHeldSavepoint();
   } else if (status.isNewTransaction()) {
        if (status.isDebug()) {
     logger.debug("Initiating transaction rollback");
    }
    doRollback(status);
   } 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);
 }
}

上边方法中 有个判断 status.isNewTransaction() 判断当前是不是新创建的事务,我们的insertC方法同样它的newTransaction(这个表示是不是新事物,因为insertC是加入之前的事务所以不是true),所以最终会走到doSetRollbackOnly,这个方法重中之重,最后会调用这样一段代码setRollbackOnly

public void setRollbackOnly() {
 this.rollbackOnly = true;
}
  • 第二点:结合上边例子A提交的时候,检查出来需要回滚
public final void commit(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;
 if (defStatus.isLocalRollbackOnly()) {
  if (defStatus.isDebug()) {
   logger.debug("Transactional code has requested rollback");
  }
  processRollback(defStatus, false);
  return;
 }
 
 if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
  if (defStatus.isDebug()) {
   logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
  }
  processRollback(defStatus, true);
  return;
 }
 
 //执行事务提交
 processCommit(defStatus);
}

好了,看到这大家都明白了吧,在commit中,Spring会去判断defStatus.isGlobalRollbackOnly有没有抛出过异常被Spring所拦截,如果有,那么就不会执行commit操作,转而执行processRollback回滚操作。

总结

为什么在A中try-catch包裹住C的方法,还会被spring拦截过呢,因为事务是通过AOP代理实现的,所以调用方法的时候是调用的代理类的方法,执行代理类方法的时候抛出了异常没有在代理方法内用try-catch捕获,所以就是向上抛出了,抛出之后就会被spring拦截过的痕迹,虽然上层有try-catch捕获这个抛出,但是捕获之前已经有了spring的拦截,那个回滚变量变为true,所以在A.operate提交的时候会坚持是否有回滚标志,有了就全部回滚了。

推荐文章