嵌套transaction中的try catch

Ethereal Lv4

背景

  1. 产生异常
1
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  1. 结构

AOP切片在Service方法上(指定Order),切片方法加事务注解,Service方法加事务注解,Service方法中,使用try catch调用Dao方法,Dao方法调用JPA的save方法,save方法存在默认的事务注解,数据库建立联合独立索引。此时第二次保存相同的数据时会异常。

原因

分析此时存在三个方法存在事务注解,即切片方法,Service方法,save方法。而此时三者为同一事务(默认事务传播级别为REQUIRED)。

当save方法异常时,将此事务标记为回滚,并且向上抛出异常,然而上层方法catch异常,使得执行流达到事务方法末尾,使得transaction可以提交,提交时发现已经标记为回滚,抛出此异常。

需要注意的是,此时最多只有两个事务,即切片方法和Service方法是同一个事务。因为其没有通过动态代理实现,因此无法使用子事务,认为其是在同一方法内被调用。

因此,触发的真正条件是,使用JDK方式开启事务,下层事务与上层事务为同一事务,且在上层事务中catch异常。

失败探索

尝试在Service上更改事务传播级别,即改为REQUIRES_NEW,不可行。同理在Service中新建方法标记REQUIRES_NEW也不可行。这是因为事务传播机制导致的。因为Spring事务管理是通过JDK动态代理实现,故直接调用子方法无法触发事务,事务传播失效。事务传播失效

解决方式

  1. 真正开启新事务。只需要在下层方法使用JDK方式开启时加入开启新事务注解即可。此处即在DAO层加入@Transactional(propagation = REQUIQRES_NEW)
  2. catch后继续向上传播。可以使用throw直接抛出异常,也可以在上层事务中catch中使用方法继续传播
    1
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

参考

rollback-only异常令我对事务有了新的认识
Spring事务嵌套引发的血案—Transaction rolled back because it has been marked as rollback-only

  • Title: 嵌套transaction中的try catch
  • Author: Ethereal
  • Created at: 2023-12-17 22:41:27
  • Updated at: 2023-12-17 23:57:18
  • Link: https://ethereal-o.github.io/2023/12/17/嵌套transaction中的try-catch/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments
On this page
嵌套transaction中的try catch