文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring中使用事务嵌套时需要警惕的问题分享

2023-05-17 08:46

关注

前言

最近项目上有一个使用事务相对复杂的业务场景报错了。在绝大多数情况下,都是风平浪静,没有问题。其实内在暗流涌动,在有些异常情况下就会报错,这种偶然性的问题很有可能就会在暴露到生产上造成事故,那究竟是怎么回事呢?

问题描述

我们用一个简单的例子模拟下,大家也可以看看下面这段代码输出的结果是什么。

1.在类SecondTransactionService定义一个简单接口transaction2,插入一个用户,同时必然会抛出错误

@Override
@Transactional(rollbackFor = Exception.class)
public void transaction2() {
    System.out.println("do transaction2.....");
    User user = new User("tx2", "111", 18);
    // 插入一个用户
    userService.insertUser(user);
    // 跑错了
    throw new RuntimeException();
}

2.在另外一个类FirstTransactionService定义一个接口transaction1,它调用transaction2方法,同时做了try catch处理

@Override
@Transactional(rollbackFor = Exception.class)
public void transaction1() {
    System.out.println("do transaction1 .......");
    try {
        // 调用另外一个事务,try catch住
        secondTransactionService.transaction2();
    } catch (Exception e) {
        e.printStackTrace();
    }

    // 插入当前用户tx1
    User user = new User("tx1", "111", 18);
    userService.insertUser(user);
}

3.定义一个controller,调用transaction1方法

@GetMapping("/testNestedTx")
public String testNestedTx() {
    firstTransactionService.transaction1();
    return "success";
}

大家觉得调用这个http接口,最终数据库插入的是几条数据呢?

问题结果

正确答案是数据库插入了0条数据。

同时控制台也报错了,报错原因是:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

是否和你预想的一样呢?你知道是为什么吗?

原因追溯

其实原因很简单,我们都知道,一个事务要么全成功提交事务,要么失败全部回滚。如果出现在一个事务中部分SQL要回滚,部分SQL要提交,这不就主打的一个”前后矛盾,精神分裂“吗?

controller.testNestedTx() 
  || 
  / 
FirstTransactionService.transaction1()   REQUIRED隔离级别
       || 
       || 
       || 捕获异常,提交事务,出错啦
       / || 
FirstTransactionService.transaction2()   REQUIRED隔离级别
       || || 
       || 抛出异常,标记事务为rollback only
       =======================

1.事务的隔离级别为REQUIRED,那么发现没有事务开启一个事务操作,有的话,就合并到这个事务中,所以transaction1()transaction2()是在同一个事务中。

2.transaction2()抛出异常,那么事务会被标记为rollback only, 源码如下所示:

3.transaction1()由于try catch 异常,正常运行,想必就要可以提交事务了,在提交事务的时候,会检查rollback标记,如果是true, 这时候就会抛出上面的异常了。源码如下图所示:

这下,是不是很清楚知道报错的原因了,那想想该怎么处理呢?

解决之道

知道了根本原因之后,是不是解决的方案就很明朗了,我们可以通过调整事务的传播方式分拆多个事务管理,或者让一个事务"前后一致",做一个诚信的好事务。

try catch放到内层事务中,也就是transaction2()方法中,这样内层事务会跟着外部事务进行提交或者回滚。

@Override
    @Transactional(rollbackFor = Exception.class)
    public void transaction2() {
        try {
            System.out.println("do transaction2.....");
            User user = new User("tx2", "111", 18);
            userService.insertUser2(user);
            throw new RuntimeException();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如果希望内层事务抛出异常时中断程序执行,直接在外层事务的catch代码块中抛出e,这样同一个事务就都会回滚。

如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为PROPAGATION_NESTEDPROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。

事务的传播机制

前面提到了事务的传播机制,我们再看都有哪几种。

如何理解PROPAGATION_NESTED的传播机制呢,和PROPAGATION_REQUIRES_NEW又有什么区别呢?我们用一个例子说明白。

总结

在我的项目中之所以会报“rollback-only”异常的根本原因是代码风格不一致的原因。外层事务对错误的处理方式是返回true或false来告诉上游执行结果,而内层事务是通过抛出异常来告诉上游(这里指外层事务)执行结果,这种差异就导致了“rollback-only”异常。大家也可以去review自己项目中的代码,是不是也偷偷犯下同样的错误了。

到此这篇关于Spring中使用事务嵌套时需要警惕的问题分享的文章就介绍到这了,更多相关Spring事务嵌套内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯