事务嵌套和捕获异常的关系以及问题解决方案

本文深入探讨Spring框架中事务管理的细节,包括@Transactional注解的正确使用、异常捕获原则,以及如何确保事务在特定条件下正确回滚。通过实例说明了在不同场景下(如同一类或不同类的方法调用)事务的生效条件,强调了try-catch块内异常抛出的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

捕捉异常使用的一些规则笔记如下:(拓展)
    
    异常配套使用一:
    throw new RuntimeException();
    throws RuntimeException
    
    异常配套使用二:
    try {
        testTwoMapper.updateStatus(userCode, userName, id, status);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    对于配套使用一和二外层/上层只需要用try catch 捕捉就可以了

 

 

 

特别场景一:A 和 B 2个方法,B中用@Transactional管理事务,A调用B
如果在同一个类C中,B事务将失效。

eg:
public class C{
    public void a(){
        b();
    }
    @Transactional
    public void b(){
         
    }
}


其实是spring aop动态代理机制给当前类C生成代理类PolicyC,然后A调用B还是在C类中调用,代理类PolicyC中的B方法并没有被调用到,
只有在不同的类C和C1中,C类中的A方法调用C1类中的B方法,这样B中的事务就会生效。

eg:
public class C{
    @Autowired 
    private C1 c1;

    //注入C1类调用c1类中的B()方法
    public void A(){
       c1.B();
    }
}
public class C1{
    @Transactional
    public void B(){
    }
}




特别场景二:针对处事务中的方法,加入try catch后,事务的回滚和不会滚情况处理。

@Transactional
//todo 本地事务已处理(无自己类调用)
@Override
public UserLoginResEntity tokenValidate(String token) {
    User user = null;
    String usercode = "";
    try {
         usercode = tokenService.validate(token);
    }catch (Exception e){
        System.out.println("ddddddd" + e);
        /**
         * 如果必须捕捉异常,就扔出runtime异常,不然aop捕捉不到异常,事务会失效。
         * 或者扔出TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
         * 手动回滚事务
         */
        throw new RuntimeException();
    }

 

以下是各个博客找的概念觉得对事务理解比较清楚的:

默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚。  
spring aop  异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚, 默认情况下aop只捕获runtimeexception的异常,但可以通过配置来捕获特定的异常并回滚 。

换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),这样程序异常时才能被aop捕获进而回滚
解决方案: 
方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理。
方案2:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法提现)。

让事务起作用,遇到错误进行回滚,应该注意的事项

第一种情况:同一个类中 一个方法无嵌套方法

 1、如果方法名上加上@Transactional注解,方法内不要用try catch ;如果必须要用try catch ,则catch中必须用throw new RuntimeException()。否则事务不起作用。

第二种情况:同一个类中 方法A嵌套方法B

1、方法A有@Transactional,方法内都没有try catch,事务起作用。

2、方法A有@Transactional和try catch,并且catch中用throw new RuntimeException(),事务起作用。

第三种情况:不同类中,方法C嵌套方法B

1、方法B上加上@Transactional注解,方法内不要用try catch ;如果必须要用try catch ,则catch中必须用throw new RuntimeException()。否则方法B的事务不起作用。

2、方法C上加上@Transactional注解,方法内不要用try catch ;如果必须要用try catch ,则catch中必须用throw new RuntimeException(),此时方法B怎么写都行。否则方法C的事务不起作用。

 

总结:
1、要想事务起作用,必须是主方法名上有@Transactional注解,方法体内不能用try catch;如果用try catch,则catch中必须用throw new RuntimeException();

2、@Transactional注解应该只被应用到public方法上,不要用在protected、private等方法上,即使用了也将被忽略,不起作用。这是由Spring AOP决定的。

3、只有来自外部的方法调用才会呗AOP代理捕捉,类内部方法调用类内部的其他方法,子方法并会不引起事务行为,即使被调用的方法上使用有@Transactional注解。

4、类内部方法调用内部的其他方法,被调用的方法体中如果有try catch,则catch中必须用throw new RuntimeException(),否则即使主方法上加上@Transactional注解,如果被调用的子方法出错也不会抛出异常,不会引起事务起作用。

下篇研究事务的传播机制,并且举例证明。

 

### 关于嵌套事务中内部事务使用 `try-catch` 后的回滚行为 在 Java 中,嵌套事务的行为主要取决于事务传播机制以及异常处理的方式。以下是关于嵌套事务中内部事务使用 `try-catch` 后是否会回滚的具体分析: #### 默认传播行为的影响 Java 的 Spring 框架中,默认的事务传播行为是 `Propagation.REQUIRED`[^3]。这意味着如果当前存在一个事务,则新事务将加入该事务;如果没有现有事务,则会创建一个新的事务。在这种情况下,方法 A 方法 B 实际上共享的是同一个事务上下文。 当方法 B 抛出异常时,尽管其可能被 `try-catch` 块捕获,但由于事务管理器会在方法返回之前检测到异常的存在并触发回滚逻辑,因此整个事务(包括方法 A 方法 B 的操作)都会被回滚。 #### 使用 `try-catch` 的影响 即使在方法 B 内部使用了 `try-catch` 来捕获异常,这并不会阻止事务管理器检测到异常的发生。这是因为事务的实际提交或回滚决策是在方法退出时由框架完成的,而不是依赖于开发者手动控制。具体来说: - 如果方法 B 抛出了未被捕获异常,或者抛出了被捕获但重新声明的异常,Spring 事务管理器会将其视为需要回滚的情况。 - 即使通过 `try-catch` 防止了程序崩溃,但如果异常对象本身并未被完全忽略,事务仍会被标记为只读状态,并最终导致回滚[^1]。 #### 如何避免不必要的回滚? 为了防止因内部事务引发的整体回滚,可以采取以下措施之一: 1. **更改事务传播行为** 将方法 B 的事务传播行为设置为 `Propagation.REQUIRES_NEW`,这样它将在独立的事务中运行,不会影响外部事务的状态。 2. **调整异常处理逻辑** 在方法 B 内部彻底捕获并处理异常,而不让任何受检或不受检异常逃逸至调用方。例如,可以通过日志记录或其他补偿手段替代简单的 `throw` 或 `rethrow` 操作[^4]。 3. **利用保存点功能** 显式定义事务保存点 (Savepoint),以便仅部分撤销某些特定的操作而非整体回退整个事务链路[^2]。 ```java @Transactional(propagation = Propagation.REQUIRED) public void methodA() { try { methodB(); } catch (Exception e) { log.error("Error occurred in MethodB", e); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() throws Exception { // Some database operations here... throw new RuntimeException("Simulated exception"); } ``` 上述代码片段展示了如何分离两个方法之间的事务关系,从而减少相互干扰的可能性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值