这波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提交的时候会坚持是否有回滚标志,有了就全部回滚了。
推荐文章