Spring事务@Transactional常见的8种失效场景(通俗易懂)

21 篇文章 4 订阅
16 篇文章 6 订阅
文章通过具体案例分析了Spring事务管理中可能失效的8种情况,包括未指定回滚异常、异常被捕获、方法内部调用、异步多线程、错误的事务传播机制、private或final修饰的方法、类未被Spring容器管理以及数据库不支持事务。这些场景可能导致事务无法正常回滚,影响数据一致性。
摘要由CSDN通过智能技术生成

前言:在日常的开发过程中,多多少少会遇到Spring事务失效导致的一些事故,本篇主要通过具体的案例分析来讲解常见的8种失效的场景,让阅读者通俗易懂的明白每一种事务失效的原因,知其然并知其所以然!

目录

一、未指定回滚异常

二、异常被捕获

三、方法内部直接调用

四、异步多线程

五、使用了错误的事务传播机制

六、方法被private或者final修饰 

七、当前类没有被Spring容器托管

八、数据库不支持事务

九、总结


一、未指定回滚异常

@Transactional注解默认的回滚异常类型是运行时异常(RuntimeException),如果我们自定义了一个异常直接继承了Exception,代码如下:

public class CustomException extends Exception{}

如果@Transactional未指定异常,当程序中抛出CustomException异常则不会回滚,测试代码如下:

    @Transactional
    public void insert() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }

运行结果如下:

虽然程序当中抛出了异常,但是数据库还是成功入库了,这样显然是不合理的!

所以我们需要在@Transactional指定回滚异常的类型,遇到异常就要回滚:@Transactional(rollbackFor = Exception.class)

二、异常被捕获

当抛出的异常被try-catch捕获时,事务也会失效,具体看代码:

    @Transactional(rollbackFor = Exception.class)
    public void insert(){
        try {
            Notice notice = new Notice();
            notice.setId(UUID.randomUUID().toString());
            notice.setTitle("《发布关于新版本更新的通知》");
            notice.setAuthor("管理员");
            notice.setContent("******");
            History history = new History();
            history.setId(UUID.randomUUID().toString());
            history.setContent(notice.toString());
            noticeMapper.insert(notice);
            historyMapper.insert(history);
            throw new CustomException();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

运行结果如下:

数据还是成功入库了,明显不合理!

所以我们需要主动将此异常抛出: throws CustomException

我们也可以修改catch包裹的代码,以此来达到回滚的目的。

        catch (Exception e){
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

三、方法内部直接调用

在Spring的Aop代理下,只有目标方法在外部进行调用,目标方法才会由Spring生成的代理对象来进行管理,如果是其他不包含@Transactional注解的方法中调用包含@Transactional注解的方法时候,有@Transactional注解的方法的事务会被忽略,则不会发生回滚。

    public void insert() throws CustomException {
        doSomething();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doSomething() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }

运行结果如下:

数据也成功入库了,明显不合理!

只要在insert方法上面加上@Transactional注解即可,doSomething的方法上面可以去掉了,因为同处于一个数据库链接当中。

    @Transactional(rollbackFor = Exception.class)    
    public void insert() throws CustomException {
        doSomething();
    }

    public void doSomething() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }

四、异步多线程

这里我撰写了一个新的myService2用于保存history对象,并在myService2的方法上加上了@Async的注解,并休眠了5s。

注:主启动类需要加上@EnableAsync

    @Async
    public void save(Notice notice) throws CustomException, InterruptedException {
        System.out.println("异步任务开始...");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
        Thread.sleep(5000);
        System.out.println("异步任务结束...");
    }

在2个插入操作都执行完毕以后,我主动抛出一个异常。 

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException, InterruptedException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");

        noticeMapper.insert(notice);

        myService2.save(notice);

        int a = 1/0;

    }

运行结果如下:

notice没有入库,history入库了

这是因为@Async注解使用的是独立线程和独立的事务,和notice的不处于同一个事务当中,所以notice回滚了,但是history入库了。

同一个事务:公用的同一个数据库链接。

对于这种失效的场景,将 @Transactional和@Async绑定在一起进行使用,事务才是生效的,请看如下代码:

    @Transactional(rollbackFor = Exception.class)
    @Async
    public void insert() throws CustomException, InterruptedException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");

        noticeMapper.insert(notice);

        myService2.save(notice);

        int a = 1/0;

    }

    public void save(Notice notice) throws CustomException, InterruptedException {
        System.out.println("异步任务开始...");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
        Thread.sleep(5000);
        System.out.println("异步任务结束...");
    }

五、使用了错误的事务传播机制

先简单介绍一下Spring事务的7种传播机制:

PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
PROPAGATION_SUPPORTS如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式继续运行
PROPAGATION_MANDATORY必须运行在已存在的事务中,否则抛出异常
PROPAGATION_REQUIRES_NEW创建一个新事务,如果已经存在一个事务,则把当前事务挂起
PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起
PROPAGATION_NEVER以非事务方式运行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等同于PROPAGATION_REQUIRED

详细讲解看我这篇文章:一文搞通Spring事务的七种传播机制(通俗易懂) 

这边我使用的是PROPAGATION_REQUIRES_NEW的传播机制。

MyService2代码如下:

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void save(Notice notice){
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
    }

依旧在2个插入操作后抛出异常:

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");

        noticeMapper.insert(notice);
        
        myService2.save(notice);

        throw new CustomException();
    }

运行结果如下: 

notice的信息入库失败,但是history成功入库了。

这是因为PROPAGATION_REQUIRES_NEW使得notice和history公用的不是同一个数据库链接,事务都是独立开来的。

六、方法被private或者final修饰 

这种情况下,事务也是会失效的。

    @Transactional(rollbackFor = Exception.class)
    private void insert() throws CustomException {

    }

    @Transactional(rollbackFor = Exception.class)
    public final void insert() throws CustomException {

    }

七、当前类没有被Spring容器托管

在当前实体类上面要打上@Service注解,否则项目启动时也会报错,不多做阐述。

//@Service
public class MyService {

    @Resource
    private HistoryMapper historyMapper;

    @Resource
    private NoticeMapper noticeMapper;

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException {

    }

}

八、数据库不支持事务

比如Mysql的Myisam存储引擎是不支持事务的,只有innodb存储引擎才支持。 这个问题出现的概率极其小,了解一下。

九、总结

这就是目前日常开发当中我总结的Spring事务常见的8种失效场景,如有遗漏,欢迎评论区补充!

  • 22
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
当谈到事务失效时,有几典型的场景案例可以考虑: 1. 网络故障:如果事务涉及到多个分布式系统或数据库,网络故障可能导致事务无法正确执行。例如,在分布式系统中,如果某个节点与其他节点之间的网络连接中断,那么事务可能无法在所有节点上同时执行,从而导致部分节点上的操作无法回滚或提交。 2. 硬件故障:硬件故障是另一个常见事务失效场景。例如,如果数据库服务器发生故障,可能无法完成事务的执行。这情况下,可以使用备份和恢复策略来确保数据的完整性。 3. 并发控制问题:事务的并发执行可能引发一些问题,例如脏读、不可重复读和幻读等。这些问题会导致事务结果不一致或无法完成。为了解决这些问题,通常使用锁机制和隔离级别来确保事务的正确执行。 4. 超时或死锁:当事务执行时间过长或发生死锁时,事务可能会失败。超时可能是由于资源竞争、长时间运行的查询等原因引起的。死锁是指两个或多个事务彼此等待对方释放资源,从而导致无法继续执行。 5. 系统错误或异常:系统错误或异常可能导致事务无法正常完成。例如,如果事务依赖的存储过程或函数出现错误,可能会导致事务失败。这时候需要进行适当的错误处理和回滚操作。 这些是一些常见事务失效场景案例,但并不是详尽无遗的。具体的失效场景可能因系统架构、应用需求和环境等因素而有所不同。在实际应用中,需要根据具体情况来分析和解决事务失效问题。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄团团

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值