京东二面:声明式事务Transactional注解,方法内有Error默认会回滚吗?

112 阅读3分钟

@Transactional默认捕获运行时异常, 那Error会回滚吗?

先说结论,会,为什么?直接看源码

首先,添加了事务注解的代码如下,打了断点,跟断点看代码

@Transactional
public void changeInfo(){
    // TODO 事务默认回滚的异常
    studentDao.updateAgeById(88,1);
    int i = 1/0;
    System.out.println("-----------");
}

这个方法肯定要被AOP代理的,然后外面会有一个环绕通知try捕获目标方法抛出的异常,看代码, 打断点定位源码

可以验证,invocation代理的目标方法就是changeInfo。因为StudentService类没有实现接口,所以采用的CGlib动态代理

源码往下走,可以看到,先通过Throwable 顶级父类捕获 算数异常(运行时异常

再把捕获的异常 传递给 completeTransactionAfterThrowing 方法处理回滚。

往下走,进入completeTransactionAfterThrowing。

内部有个transactionAttribute属性调用rollbackOn 方法进行回滚判断,返回一个bool值判断是否需要回滚。

进入 transactionAttribute.rollbackOn 回滚判断方法。

transactionAttribute是一个事务属性接口,它有好几种是实现。

  • RuleBasedTransactionAttribute
  • DefaultTransactionAttribute
  • DelegatingTransactionAttribute

最终会走到DefaultTransactionAttribute这个实现类的rollbackOn 方法进行判断。

最终找到答案,关键来了,如果是运行时异常 或者 Error本身或子类,那么rollBackOn方法返回True

返回True之后,在外部completeTransactionAfterThrowing方法中,通过事务管理器执行回滚

如果自己通过事务注解属性配置回滚异常为Exception,那么Error还会回滚吗?

先说结论,也会回滚

@Transactional(rollbackFor = Exception.class)
public void changeInfo(){
    // TODO 事务默认回滚的异常
    studentDao.updateAgeById(88,1);
    throw new Error();
}

还是跟源码, 目标方法还是抛出了一个算数异常(运行时异常)

还是跟源码, 目标方法还是抛出了一个算数异常(运行时异常)

txInfoTransactionInfo对象,TransactionInfo是一个内部类,用来保持当前事务的一些信息,保存什么信息?保存我们通过transactional注解配置的事务属性信息例如事务传播行为,隔离级别,回滚异常,只读等)。

  • 在这个例子中,我们配置的@Transactional(rollbackFor = Exception.class),所以可以在txInfo中看到我们配置的事务属性信息

  • 可以看到,我们配置的回滚异常被保存在了txInfo中的transactionAttribute中的targetAttribute中的rollBackRules这个集合中

txInfo被传入了completeTransactionAfterThrowing这个方法,进行事务处理,先往下看。

跟之前一样,调用txInfo.transactionAttributerollbackOn方法判断是否需要回滚。

transactionAttribute是一个事务属性接口,它有好几种是实现。

  • RuleBasedTransactionAttribute
  • DefaultTransactionAttribute
  • DelegatingTransactionAttribute

跟之前一样,先调用DelegatingTransactionAttributerollbackOn方法。

然后调用RuleBasedTransactionAttributerollbackOn方法。

RuleBasedTransactionAttributerollbackOn方法,非常有意思。

  • 在这个方法中遍历了配置的回滚规则rollBackRules,示例中只有一个是Exception.class
  • getDepth:获取配置的回滚规则 与 实际发生的异常深度差(深度差是子类和父类的深度差) 不为父子关系则返回depth=-1.(这里我点进去源码看的,其实也就一个递归函数判断)

然后示例中目标方法内发生的异常是Error,也就是变量ex是Error,Error和Exception没有父子关系,最终depth=-1,所以不会进行if判断,所以winner == null。

  • 所以还是会调用super.rollbackOn 方法。super就是DefaultTransactionAttribute,所以最终和之前一样还是会返回true,所以Error还是会回滚。