事务的传播行为,描述了在一个事务方法中调用另一个事务方法时,事务是如何传播的。
只从这个定义上看,很多同学不太好理解什么叫事务的传播,接下来我们来举一个例子。
1.快速理解事务传播的场景
以银行转账的例子为例,需要对两方进行操作,分别是小红转出钱和小明转入钱两个步骤。我们在每个步骤上再加上一个动作,账户变动后需要做一个日志记录的操作。也就是把这次转入或转出的动作记录到数据库的日志表中。此时,我们的代码内容如下:
小红转出钱
@Transactional@Overridepublic void reduce() {//小红转出500accountDao.reduce();//记录此行为logDao.log(2,-500);}
小明转入钱
@Transactional@Overridepublic void add() {//小明转入500accountDao.add();//记录此行为logDao.log(1,500);}
小伙们有没有发现,这两个方法上面都打上了@Transactional注解。也就是说这两个方法都需要事务来控制,因为每个方法里面都有多个操作数据库的SQL。
接下来我们来看下转账方法:
转账方法
@Transactionalpublic 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方法开启一个事务。
@Testpublic 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 {@Autowiredprivate LogDao logDao;@Transactional(propagation = Propagation.REQUIRES_NEW)public void log(Long id,Integer money) {logDao.log(id,money);}}
转出钱的方法
@Transactional@Overridepublic void reduce() {accountDao.reduce();logService.log(2,-500);}
转入钱的方法
@Transactional@Overridepublic void add() {accountDao.add();logService.log(1,500);}
转账的方法
@Transactional@Overridepublic 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事务方法而被终止。




