事务的传播机制(案例演示)
案例介绍:
数据库表:user表,两条数据:
Mapper逻辑:
两条sql
- update1:修改张三的pass字段为2。
- update2:修改李四的pass字段为2。
@Update("Update user set pass = 2 where id = 1")
void update1();
@Update("Update user set pass = 2 where id = 2")
void update2();
Service逻辑:
两个Service,两个方法:
- test01:对应update1
- test02:对应update2
(在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的)
@Service
public class shiwuServiceImpl {
@Autowired
TestMapper testMapper;
@Autowired
shiwuServiceImpl2 serviceImpl2;
public void test01(){
testMapper.update1();
}
}
@Service
public class shiwuServiceImpl2 {
@Autowired
TestMapper testMapper;
public void test02(){
testMapper.update2();
}
}
事务的传播机制机制:
简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。
举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
在Spring中对于事务的传播行为定义了七种类型分别是:
1,required:
Spring默认的事务传播类型,如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
Service1:
@Transactional(propagation = Propagation.REQUIRED)
public void test01(){
testMapper.update1();
serviceImpl2.test02();
}
Service2:
public void test02(){
testMapper.update2();
int a = 1/0;
}
- test01方法为程序的入口,被Transactional修饰,默认传播行为是REQUIRED。
- test01方法中试图将数据库中的张三的pass属性改为2。
- test01方法调用了Service2中的test02方法。
- test02方法试图将数据库中李四的属性改为2。
- test02方法报错!
- 全部回滚。
我们看看数据库结果:
Spring默认的事务传播类型,如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
符合了上面红字中的情况。
现在我们来看看另一种情况(被调用的方法加上Transactional注解):
Service1:
public void test01(){
testMapper.update1();
serviceImpl2.test02();
}
Service2:
@Transactional(propagation = Propagation.REQUIRED)
public void test02(){
testMapper.update2();
int a = 1/0;
}
- test01方法没有开启事务,将张三的pass属性改为2。
- test01方法调用了Service2中的test02方法
- test02方法被Transactional修饰,默认传播行为是REQUIRED。
- test02方法报错!
- test02方法回滚。
我们看看数据库结果:
Spring默认的事务传播类型,如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
符合了上面红字中的情况。
2,supports:
如果当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
Service1:
public void test01(){
testMapper.update1();
serviceImpl2.test02();
}
Service2:
@Transactional(propagation = Propagation.SUPPORTS)
public void test02(){
testMapper.update2();
int a = 1/0;
}
- test01方法没有开启事务,将张三的pass属性改为2。
- test01方法调用了Service2中的test02方法
- test02方法被Transactional修饰,传播行为是SUPPORTS。
- test02方法报错!
- 两个操作均成功
我们看看数据库结果:
如果当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
符合了上面红字中的情况。
SUPPORTS和REQUIRED的区别就在于上面红字中的情况。。所以另一种情况我们就不举例了。
3,mandatory:
当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
这个我们就不举例了,一眼就看懂了。这种属性的目的就是标注我当前这个方法必须要在事务中进行,否则我就直接报错,方法体我是一行都不会去执行的!
4,requires_new:
创建一个新事务,如果存在当前事务,则挂起该事务。
可以理解为被requires_new修饰的方法,在执行时,不论当前是否存在事务,总是会新建一个事务,如果调用方有事务,就将调用方事务挂起!
什么是挂起?
Service1:
@Transactional(propagation = Propagation.REQUIRED)
public void test01(){
testMapper.update1();
serviceImpl2.test02();
}
Service2:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test02(){
testMapper.update2();
int a = 1/0;
}
- test01有事务
- test02事务属性为REQUIRES_NEW
- test02报错
- 全部回滚。
这里可以知道,挂起的意思就是test01执行到serviceImpl2.test02();这个步骤的时候,这个时候test01中的修改并没有提交,也没有回滚,处于一个挂起的状态! 一旦test02()方法报错,而test01没有进行Try catch补获,test01也会进行回滚!
好,我们看看另一种情况!
Service1
@Transactional(propagation = Propagation.REQUIRED)
public void test01(){
testMapper.update1();
serviceImpl2.test02();
int a = 1/0;
}
Service2
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test02(){
testMapper.update2();
}
- test01()开启普通事务,第一步试图修改张三的数据
- 第二步调用的Service2的test02方法。
- 这个时候test01方法的事务挂起了,没有提交也没有回滚。
- test02方法开启了一个新的事务,把李四的数据改掉了,没有报错,提交了事务。
- 回到test01中
- test01报错,由于test02已经提交。只有test01回滚。
结果:
5,not-supported:
始终以非事务方式执行,如果当前存在事务,则挂起当前事务
这个就不写例子了,很好理解。重点在于:
- 由not-supported修饰的test02方法如果报错了。
- 而刚好调用处test01没有进行异常捕获。
- test01和test02都会回滚!
6,never:
不使用事务,如果当前事务存在,则抛出异常
这个也很简单。重点在于:
- test01方法开启了事务,调用test02方法
- test02是由never修饰的,发现我的调用方居然是开启了事务!我不同意!报错!方法体内一行代码都不执行
- 此时如果test01没有进行异常捕获。
- test01回滚!
7,nested:
如果当前事务存在,则在嵌套事务中执行。如果当前事务不存在事务,就和REQUIRED的操作一样(开启一个事务)
-
和REQUIRES_NEW的区别
REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。
在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。 -
和REQUIRED的区别
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚
而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响
Service1
@Transactional(propagation = Propagation.REQUIRED)
public void test01(){
testMapper.update1();
serviceImpl2.test02();
int a = 1/0;
}
Service2
@Transactional(propagation = Propagation.NESTED)
public void test02(){
testMapper.update2();
}
- test01开启了事务,试图修改张三的数据。
- test01调用了test02.
- test02被NESTED修饰,就开启了一个嵌套事务(这个事务属于外面的大事务)。并完成了自己的操作。
- 回到test01,test01报错。自己的事务连同嵌套的子事务一起回滚。
结果:
好,我们看看另一种情况!
Service1
@Transactional(propagation = Propagation.REQUIRED)
public void test01(){
testMapper.update1();
try {
serviceImpl2.test02();
}catch (Exception e){
System.out.println("test02报错了");
}
}
Service2
@Transactional(propagation = Propagation.NESTED)
public void test02(){
testMapper.update2();
int a = 1/0;
}
- test01开启了事务,并对test02方法进行了异常捕获。
- test02开启了子事务,报错了。test02回滚!
- test01捕获了test02的报错,不影响test01的任何操作。
- 所以最后,test01操作成功,test02回滚!
结果:
好了 基本已经讲完,欢迎大家评论区指出不足,一起学习进步!
大家看完了点个赞,码字不容易啊。。。