@transactional与 try catch的爱恨情仇

本文详细解析了如何在Java中使用@Transactional注解管理事务,包括设置传播行为、隔离级别、超时和只读模式。重点讨论了REQUIRED属性导致的事务嵌套现象,以及如何通过try-catch和rollbackFor属性处理不同类型的异常回滚。

如何开启@transactional

service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务管理,在每个业务方法执行时都会开启一个事务,不过这些事务采用相同的管理方式。
@Transactional 注解只能应用到 public 可见度的方法上。 如果应用在protected、private或者 package可见度的方法上,也不会报错,不过事务设置不会起作用。
默认情况下,Spring会对unchecked异常进行事务回滚;如果是checked异常则不回滚。

java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常,其他继承自java.lang.Exception得异常统称为Checked Exception,如IOException、TimeoutException等

只读事务:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)只读标志只在事务启动时应用,否则即使配置也会被忽略。启动事务会增加线程开销,数据库因共享读取而锁定(具体跟数据库类型和事务隔离级别有关)。通常情况下,仅是读取数据时,不必设置只读事务而增加额外的系统开销。
@transactional propagation 属性

事务的传播行为,默认值为 Propagation.REQUIRED。

可选的值有:

Propagation.REQUIRED

如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

Propagation.SUPPORTS

如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

Propagation.MANDATORY

如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

Propagation.REQUIRES_NEW

重新创建一个新的事务,如果当前存在事务,暂停当前的事务。

Propagation.NOT_SUPPORTED

以非事务的方式运行,如果当前存在事务,暂停当前的事务。

Propagation.NEVER

以非事务的方式运行,如果当前存在事务,则抛出异常。

Propagation.NESTED

和 Propagation.REQUIRED 效果一样。

isolation 属性

事务的隔离级别,默认值为 Isolation.DEFAULT。

可选的值有:

Isolation.DEFAULT

使用底层数据库默认的隔离级别。

Isolation.READ_UNCOMMITTED

Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
timeout 属性
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor 属性
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

@Transactional( rollbackFor = Exception.class)
    public void saveNormal0() throws Exception {
        int age = random.nextInt(100);
        User user = new User().setAge(age).setName("name:"+age);
        userService.save(user);
        throw new Exception();
    }
结论一:对于@Transactional可以保证RuntimeException错误的回滚,如果想保证非RuntimeException错误的回滚,需要加上rollbackFor = Exception.class 参数。
 @Transactional( rollbackFor = Exception.class)
    public void saveTryCatch() throws Exception{
        try{
            int age = random.nextInt(100);
            User user = new User().setAge(age).setName("name:"+age);
            userService.save(user);
            throw new Exception();
        }catch (Exception e){
            throw e;
        }
    }

结论二:try catch只是对异常是否可以被@Transactional 感知 到有影响。如果错误抛到切面可以感知到的地步,那就可以起作用。

   @GetMapping("/out")
    @Transactional( rollbackFor = Exception.class)
    public void out() throws Exception{
        innerService.inner();
        int age = random.nextInt(100);
        User user = new User().setAge(age).setName("name:" + age);
        userService.save(user);
        throw new Exception();
    }
    @Transactional
    public void inner() throws Exception{
        Role role = new Role();
        role.setRoleName("roleName:"+new Random().nextInt(100));
        roleService.save(role);
//        throw new Exception();
    }

情况一,外面事务加上rollbackFor = Exception.class,里面事务不加,测试内外分别报错的情况(为了简化代码量,只给出了外面报错的代码),都可以回滚。因为,无论如何,错误都抛给了外面那个事务进行处理,而外面那个加上了rollbackFor = Exception.class,具备处理非RuntimeException错误的能力,所以都可以让事务进行正常回滚。

里面的事务加上rollbackFor = Exception.class,外面不加,外面报错。
    @GetMapping("/out")
    @Transactional
    public void out() throws Exception{
        innerService.inner();
        int age = random.nextInt(100);
        User user = new User().setAge(age).setName("name:" + age);
        userService.save(user);
        throw new Exception();
    }
    
    @Transactional( rollbackFor = Exception.class)
    public void inner() throws Exception{
        Role role = new Role();
        role.setRoleName("roleName:"+new Random().nextInt(100));
        roleService.save(role);
    }

事务都无法回滚,这是我们有个疑问,里面的事务明明有很强的处理能力啊,为什么和外面一起回滚失败呢?

    @GetMapping("/out")
    @Transactional
    public void out() throws Exception{
        innerService.inner();
        int age = random.nextInt(100);
        User user = new User().setAge(age).setName("name:" + age);
        userService.save(user);
    }
     @Transactional( rollbackFor = Exception.class)
    public void inner() throws Exception{
        Role role = new Role();
        role.setRoleName("roleName:"+new Random().nextInt(100));
        roleService.save(role);
        throw new Exception();
    }

咦,这回都进行了正常的回滚。我的天,这回外面没有处理能力,为什么接受里面抛出来的错误,也进行了回滚!!!看上去,就好像里外事务总是同生共死的对不对?原来,@Transactional还有个参数,看下源码,这个注解还有默认值:
REQUIRED的意思是说,事务嵌套的时候,如果发现已经有事务存在了,就加入这个事务,而不是新建一个事务,所以根本就不存在两个事务,一直只有一个!至于,此参数其他值,本文不进行测试。回到上面的问题,当外面报错的时候,此时查看事务,没有增加rollbackFor = Exception.class参数,即没有处理非RuntimeException能力,所以代码走完,貌似“两个事务”,都回滚失败了。当里面报错的时候,事务已经添加上了处理非RuntimeException能力,所以,代码走完就回滚成功了。

结论三:由于REQUIRED属性,“两个事务”其实是一个事务,处理能力看报错时刻,是否添加了处理非RuntimeException的能力。

try catch和事务嵌套 共同影响
在结论一二三成立的条件下,探索共同影响的问题就简单多了,由于情况太多,就不进行过多的代码展示了。

结论
结论一:对于@Transactional可以保证RuntimeException错误的回滚,如果想保证非RuntimeException错误的回滚,需要加上rollbackFor = Exception.class 参数。
结论二:try catch只是对异常是否可以被@Transactional 感知 到有影响。如果错误抛到切面可以感知到的地步,那就可以起作用。
结论三:由于REQUIRED属性,“两个事务”其实是一个事务,处理能力看报错时刻,是否添加了处理非RuntimeException的能力。

转自https://blog.youkuaiyun.com/Abysscarry/article/details/80189232
转自https://blog.youkuaiyun.com/Abysscarry/article/details/80189232

### ### Spring @Transactional 注解 try-catch 一起使用的最佳实践 在 Spring 框架中,`@Transactional` 注解提供了声明式事务管理的便捷方式,通过 AOP 技术对方法进行事务增强,确保方法执行过程中发生异常时能够自动回滚。然而,当在 `@Transactional` 注解的方法中使用 `try-catch` 块处理异常时,若使用不当,可能导致事务失效或回滚失败。 在默认配置下,Spring 仅对未检查异常(即继承自 `RuntimeException` 的异常) `Error` 进行事务回滚,而对检查异常(checked exceptions)则默认不会回滚。因此,如果在 `try-catch` 中捕获了异常而没有重新抛出,事务将不会回滚,从而导致数据不一致问题[^1]。 为了确保事务的完整性,推荐的做法是:在 `catch` 块中重新抛出捕获的异常,或者显式调用 `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()` 来标记事务为回滚状态。这种方式可以确保 Spring 框架识别到异常并执行回滚操作。例如: ```java @Transactional public void performBusinessOperation() { try { // 业务逻辑 } catch (SpecificException e) { // 处理异常并标记事务回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw e; } } ``` 此外,有建议指出,在 `@Transactional` 注解方法中使用 `try-catch` 时,可以将需要在异常发生后执行的清理或资源释放代码放在 `finally` 块中,以确保无论是否发生异常,这些代码都能被执行,从而避免资源泄漏或状态不一致的问题[^2]。 需要注意的是,`@Transactional` 注解的事务边界由 Spring 框架自动管理,因此在方法内部使用 `try-catch` 时,应避免过早捕获并吞掉异常,否则事务管理器将无法感知到异常的发生,进而导致事务无法回滚。 ### ### 相关问题 1. 在 Spring 中如何正确使用 `@Transactional` 注解管理事务? 2. `@Transactional` 注解方法中使用 `try-catch` 是否会影响事务的回滚? 3. Spring 事务管理中如何处理检查异常未检查异常? 4. 如何在 `@Transactional` 方法中正确释放资源而不影响事务行为? 5. Spring 中事务传播机制对 `try-catch` 使用有何影响? ### ### 参考资料 - Spring 通过 `@Transaction` 注解提供了声明式事务管理的便捷方式,极大地简化了事务处理的复杂性[^3]。 - 在 `@Transactional` 注解的业务方法中,如果想在 `try-catch` 语句中正确处理异常,而又不会导致事务失效,应该将 `catch` 块中的代码放在 `finally` 块中。这样,即使在 `catch` 块中发生了异常,`finally` 块中的代码仍然会执行,保证事务能够正常提交或回滚[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值