暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一文搞懂Spring事务的传播行为

原创 sg1234 2023-10-27
189


事务的传播行为,描述了在一个事务方法中调用另一个事务方法时,事务是如何传播的。

图片

只从这个定义上看,很多同学不太好理解什么叫事务的传播,接下来我们来举一个例子。

1.快速理解事务传播的场景

以银行转账的例子为例,需要对两方进行操作,分别是小红转出钱和小明转入钱两个步骤。我们在每个步骤上再加上一个动作,账户变动后需要做一个日志记录的操作。也就是把这次转入或转出的动作记录到数据库的日志表中。此时,我们的代码内容如下:

  • 小红转出钱

   @Transactional    @Override    public void reduce() {        //小红转出500        accountDao.reduce();        //记录此行为        logDao.log(2,-500);    }
  • 小明转入钱

   @Transactional    @Override    public void add() {        //小明转入500        accountDao.add();        //记录此行为        logDao.log(1,500);    }

小伙们有没有发现,这两个方法上面都打上了@Transactional注解。也就是说这两个方法都需要事务来控制,因为每个方法里面都有多个操作数据库的SQL。

接下来我们来看下转账方法:

  • 转账方法

   @Transactional    public void transfer() {        System.out.println("小红转出");        this.reduce();        System.out.println("小明转入");        this.add();    }

转账方法调用了转出和转入的两个方法。此时问题来了,如果this.add()方法内部出现了异常导致add方法的事务发生了回滚,那么transfer()方法是否会回滚,this.reduce()方法中的多个SQL操作是否会回滚?我们的直觉它们都应该回滚。默认情况下确实如此,但也可以进行自定义设置。也就是说,这个过程我们称为事务的传播,并且我们可以配置不同的传播行为。

2.事务的传播行为

Spring提供了7种不同的传播行为:

事务传播类型外部不存在事务外部存在事务使用方式使用场景
REQUIRED(默认)开启新的事务当前事务加入到外部事务中Propagation.REQUIRED适用于增删改查
SUPPORTS不开启新的事务当前事务加入到外部事务中Propagation.SUPPORTS适用于查询
REQUIRES_NEW开启新的事务把外部事务挂起,创建新的事务Propagation.REQUIRES_NEW适用于内外事务不关联的场景
NOT_SUPPORTED不开启新的事务把外部事务挂起Propagation.NOT_SUPPORTED不常用
NEVER不开启新的事务抛出异常Propagation.NEVER不常用
MANDATORY抛出异常当前事务加入到外部事务中Propagation.MANDATORY不常用


3.REQUIRED

@Transactional(propagation = Propagation.REQUIRED)

这种行为是用的最多的,也是Spring默认的事务传播行为。首先我们先来弄清楚什么是“外部存在事务”。

  • 站在reduce方法的立场

外部指的的transfer方法。当transfer方法的事务产生回滚时,reduce的事务就会加入到外部transfer事务中,进行了回滚。

  • 站在transfer方法的立场

外部指的是调用transfer方法的上下文,此时我们使用单元测试来调用transfer方法,因为单元测试并没有加事务,所以会为transfer方法开启一个事务。

 
   @Test    public void test1(){        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");        AccountService accountService = context.getBean(AccountService.class);        accountService.transfer();    }

REQUIRED适用于增删改查所有场景。

4.SUPPORTS

SUPPORTS类型适用于查询操作,也就是对数据库只执行读的操作,而非增删改这种改变数据的操作。

比如我们在转账的时候,不做记录日志,而是做查询余额的动作,那么查询余额这种操作就可以使用SUPPORTS类型,如果外部有事务,则跟随外部的事务,如果外部没有事务,则不会创建新的事务。

  @Transactional(propagation = Propagation.SUPPORTS)    public void show(){        accountDao.add();        int i = 10/0;        System.out.println("查询余额");    }

为了测试效果,索尔老师在show方法中调用了改变数据的add方法。如果我们直接调用了show方法,那么外部并不存在事务,于是并不会创建事务,导致add方法成功修改数据而不会回滚。因此SUPPORTS类型适用于查询操作,而不适用于增删改的操作。

5.REQUIRES_NEW

REQUIRES_NEW行为略微复杂,用一句话来说就是外部事务回滚了,但不影响内部事务的正常提交。这种行为适用于外部事务和内部事务不存在业务关联。

举一个简单例子,外部事务做的是转账,而内部事务做的日志的记录。现在的要求是,无论外部事务是否执行成功,内部事务都需要提交,因为日志需要被记录各种操作,即使是失败的操作。

  • 设计日志服务接口

public interface LogService {    void log(Long id,Integer money);}
  • 设计日志服务实现类

@Servicepublic class LogServiceImpl implements LogService {    @Autowired    private LogDao logDao;    @Transactional(propagation = Propagation.REQUIRES_NEW)    public void log(Long id,Integer money) {        logDao.log(id,money);    }}
  • 转出钱的方法

   @Transactional    @Override    public void reduce() {        accountDao.reduce();        logService.log(2,-500);    }
  • 转入钱的方法

 @Transactional    @Override    public void add() {        accountDao.add();        logService.log(1,500);    }
  • 转账的方法

  @Transactional    @Override    public void transfer() {        System.out.println("小红转出");        this.reduce();        int i = 10/0;        System.out.println("小明转入");        this.add();    }

此时,当外部transfer方法出现事务回滚时,并不会让reduce方法中的logService.log(1,500)事务进行回滚。log事务依然会提交。

因此,REQUIRES_NEW适用于内外事务业务不关联的场景。

6.其他传播行为

其他三种传播行为并不常用,或者说基本没有应用场景。

  • NOT_SUPPORTED:外部存在事务,则把外部事务挂起。这可以说是很无脑的行为,为什么要把外部事务给关掉呢?没有应用场景。

  • NEVER:外部存在事务,则抛出异常。为什么要因为外部存在事务就抛异常呢?没有应用场景。

  • MANDATORY:外部不存在事务,则抛出异常。这会让程序因调用修饰了MANDATORY事务方法而被终止。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论