什么是事务传播
事务传播行为是为了解决业务层方法之间互相调用的事务问题,当一个事务方法被另一个事务方法调用时,事务该以何种状态存在?例如新方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行,等等,这些规则就涉及到事务
的传播性。
大白话就是,多个业务相互调用,业务如果开启了业务,业务会变成什么样?(新建一个业务还是共用同一个业务)
Spring的事务传播
Spring定义了七种事务传播行为:
传播性 | 描述 |
---|---|
REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务 |
SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED |
那么这七种传播性是什么意思呢,下面通过一个例子解释其含义
举例理解
前期准备
1、建立数据库
2、建立一个springboot项目,连接数据库
可以使用其他方式建立项目和连接数据库,这里就不想详细展示,重点在业务流程
3、编写两个业务流程
@Service
public class Service1 {
@Resource
private AccountMapper accountMapper;
@Resource
private Service2 service2;
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
service2.handle2();
}
}
@Service
public class Service2 {
@Resource
private AccountMapper accountMapper;
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
}
调用handle1方法
@SpringBootTest
class TranscationApplicationTests {
@Resource
private Service1 service1;
@Test
void test(){
service1.handle1();
}
}
ps:此时两个事务都没有开启事务,开启事务使用@Transactional
1、REQUIRED
REQUIRED是默认的传播事务,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- handle1,handle2均开启事务时:
.....
@Transactional // 默认是REQUIRED
public void handle1()
....
@Transactional
public void handle2()
这个时候调用handle1,会开启一个事务,handle1执行到service2.handle2()方法时,因为handle2是REQUIRED,所以handle2()会加入刚刚handle1的事务中,即二者共用一个事务。
此时调用handle1方法,查询数据库
发现更改成功。
因为二者是共用一个事务,所以如果,这两个方法任意一个发生错误,回滚,两个都会回滚。如在handle2手动添加错误,其他不变(将balance重置):
@Transactional
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
int i = 1 / 0;
}
发现数据库没有更新!
- handle2开启事务,handle1没有开启事务
按上面的说明,那应该在执行service2.handle1()时是没有事务的,就不存在回滚之类什么的,执行到service2.handle2()就新建一个事务,执行完之后就关闭事务。
按这个逻辑,那如果handle2报错,需要回滚,handle1就不会受影响。更改代码查看结果:
@Service
public class Service1 {
@Resource
private AccountMapper accountMapper;
@Resource
private Service2 service2;
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
service2.handle2();
}
}
@Service
public class Service2 {
@Resource
private AccountMapper accountMapper;
@Transactional
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
int i = 1/0; //报错 事务回滚
}
发现只有id为1的balance更新了,id为2的没有更新。这样就验证了上面的推断了!
2、REQUIRES_NEW
与REQUIRES_NEW区别在于,如果已经存在,就将外部的事务挂起,再建新的事务,会有两个事务。
- 如果外部(handle1)没有事务,内部事务(handle2)为REQUIRES_NEW,就新建一个事务(和REQUIRES的情况一样)
- 如果外部存在事务,就新建一个事务,并将外部事务挂起。(此时有两个事务)
@Service
public class Service1 {
....
@Transactional
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
service2.handle2();
}
}
......
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
}
此时
handle2发生错误回滚时,handle1不影响,因为它是独立的一个新事务。
在handle2加入int i =1/0;执行查看数据库
因为handle1和handle2是不同的事务,handle2报错回滚并不影响handle1事务的提交,故handle1可以更改成功,handle2更改失败。
3、NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED
外部事务回滚会导致内部事务回滚
更改代码如下:
@Service
public class Service1 {
....
@Transactional
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
service2.handle2();
int i = 1/0;
}
}
......
@Transactional(propagation = Propagation.NESTED)
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
}
执行发现数据库没有更改
内部事务回滚,外部事务不受影响
更改代码如下:
@Service
public class Service1 {
....
@Transactional
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
service2.handle2();
}
}
......
@Transactional(propagation = Propagation.NESTED)
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
int i = 1/0;
}
执行发现数据库没有更改???
这是为什么呢?子事务回滚,外部事务也会回滚吗?
其实不是这样的,我们仔细看下代码会发现,因为handle2发生了异常,即我们在handle1 执行service2.handle2()语句是会报错的,所以这时候handle1也是会报错,所以才回滚的,我们只需要将这个异常抛出即可验证。
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
try{
service2.handle2();
}catch (Exception e){
e.printStackTrace();
}
}
再次执行,查看数据
这样就符合上面所说的了!
4、MANDATORY
handle1事务传播是MANDATORY:
● handel2有事务,handle1加入handle2事务
● handle2没有事务,handle1抛出异常
@Service
public class Service1 {
....
public void handle1(){
Account account = new Account();
account.setId(1);
account.setBalance(100D);
accountMapper.update(account);
service2.handle2();
}
}
......
@Transactional(propagation = Propagation.MANDATORY)
public void handle2() {
Account account = new Account();
account.setId(2);
account.setBalance(100D);
accountMapper.update(account);
}
从事handle1没有事务,就会抛出异常报错
小结
其他几个就不一一举例了,大家可以按照这个方法一一验证。