B、事务的传播行为
1. propagation_required(默认)
需要事务,默认的传播行为,如果当前存在事务,就沿用当前事务
否则新建一个事务运行子方法
即只要大方法里面有事务,小方法即使没有事务,也会用大事务的
说明都在沿用当前存在的事务,
2. propagation_required_new(用于子方法)
无论当前事务是否存在,都会创建新事物运行方法
这样新事务就会拥有新的锁和新的事务隔离级别,与当前事务互相独立
2. propagation_nested(用户子方法)
在当前方法调用子方法时,如果子方法发生异常,并且被捕获的情况下
只回滚子方法执行的SQL,而不回滚当前方法执行的事务
C、 测试传播行为
1. 首先调整Springboot日志级别,调到Debug模式
在yml文件中
logging:
config: classpath:conf/logback-dev.xml
level:
root: debug
2. 在说事务的传播行为的时候,我们先看这个
class B(){
void methodB(){
doSomething..........
}
}
class A(){
void methodA(){
dosomething.....
void methodB(){
dosomething.......
}
}
}
Class B 的 methodB()方法如下:
public MyRedisDO insert(MyRedisDO myRedisDO) {
myRedisMapper.insert(myRedisDO);
return myRedisDO;
}
Class A 的methodA()方法
public int insert(ListmyRedisDOList) {
int count = 0;
MyRedisDO jihe = new MyRedisDO();
jihe.setName("这是集合插入的");
jihe = myRedisService.insert(jihe);
for (MyRedisDO myRedisDO : myRedisDOList) {
MyRedisDO myRedisDO1 = myRedisService.insert(myRedisDO);
if (myRedisDO1 != null) {
count++;
}
}
return count;
}
测试例子:
我想要在批量插入语句里面,插入5条记录,但是第三条出现了问题,插入不进去
@Test
public void co() {
MyRedisDO mapperDO = new MyRedisDO();
mapperDO.setName("2a");
MyRedisDO mapperDO1 = new MyRedisDO();
mapperDO1.setName("2b");
MyRedisDO mapperDO2 = new MyRedisDO();
MyRedisDO mapperDO3 = new MyRedisDO();
mapperDO3.setName("2d");
MyRedisDO mapperDO4 = new MyRedisDO();
mapperDO4.setName("2e");
ListmapperDOList = new ArrayList<>();
mapperDOList.add(mapperDO);
mapperDOList.add(mapperDO1);
mapperDOList.add(mapperDO2);
mapperDOList.add(mapperDO3);
mapperDOList.add(mapperDO4);
int count = myRedisBatchService.insert(mapperDOList);
System.out.println(count);
}
3. 1 情况一:
单个的插入方法和批量方法都不设置事务
结果是:每插入一条,数据库出现一条
最后插入了1+2=3条记录
批量插入第二条之后的都没有插入
因为插入了,所以ID自增了3
3.2 情况二:
单个的insert方法不设置传播行为,集合insertBatch方法设置传播行为为: Propagation.REQUIRED
发现日志中每一个子方法都会有**Participating in existing transaction** 这个
说明都在沿用当前存在的事务,即只要大方法里面有事务,小方法即使没有事务,也会用大事务的
如果ID自增,会出现之前由于异常的数据也算插入进去了,只不过给删掉了,也就是ID自增了
比如原有ID最大是33,一次集合插入前10个都没有问题,此时ID自增到了43,而44出现异常,回滚了所有数据,
这个时候下次插入的时候,会从44开始
结果是:每插入一条即使成功,数据库也不会出现一条
最后插入了0条记录
插入了但是回滚了,但是ID也自增了3
3.3 情况三
单个插入方法时propagation_new级别,大事务是默认Propagation.REQUIRED
则小事务不会管大事务的事务,它自己独立
既然是独立,那我们猜想那前两个数据会不会被插入呢?
结果是:每插入一条,数据库出现一条
最后插入了1+2=3条记录
批量插入第二条之后的都没有插入
因为插入了,所以ID自增了3
3.4 情况四
单个插入方法是propagaion_nested 默认,大的事务是propagaion_required
nested当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL
而不回滚当前事务,一般用于大的方法
比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,
ServiceB.methodB的事务级别为PROPAGATION_NESTED,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,
ServiceB.methodB会起一个新的子事务并设置savepoint,
等待ServiceB.methodB的事务完成以后,他才继续执行。
因为ServiceB.methodB是外部事务的子事务,那么
1、如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB也将回滚
2、如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA的try..catch捕获并处理
ServiceA.methodA事务仍然可能提交;如果他抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚。
3.4.1 第一种情况:不捕获异常
单个插入方法是propagaion_nested 默认,大的事务是propagaion_required
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public int insert(ListmyRedisDOList) {
MyRedisDO my1 = new MyRedisDO();
my1.setName("大方法里面的");
my1 =myRedisService.insert(my1);
int count=0;
for(MyRedisDO myRedisDO:myRedisDOList){
MyRedisDO myRedisDO1 =myRedisService.insert(myRedisDO);
if(myRedisDO1!=null){
count++;
}
}
return count;
}
这样没有捕获异常的,所有执行过的都要回滚的,结果就说数据库没有删除,也没有插入
结果是:每插入一条即使成功,数据库也不会出现一条
最后插入了0条记录
插入了但是回滚了,但是ID也自增了3
3.4.2 第二种情况:捕获异常
单个插入方法是propagaion_nested 默认,大的事务是propagaion_required
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public int insert(ListmyRedisDOList) {
int count = 0;
int a =myRedisService.delById(1);
try {
for (MyRedisDO myRedisDO : myRedisDOList) {
MyRedisDO myRedisDO1 = myRedisService.insert(myRedisDO);
if (myRedisDO1 != null) {
count++;
}
}
}catch (Exception e){
System.out.println("子方法发生异常,但是不影响当前方法里面的事务");
}
return count;
}
这样是先删除了ID为1的,然后插入2条记录,第三条记录之后的没有插入
只是在插入的时候,数据不是插入一条就显示一条
这样我们就当保证当一个方法是批量执行的时候,比如执行了80%,但是不希望全部回滚,我们就可以这样做
propagaion_nested 和propagation_new的区别
在刚才3.4.2捕获异常的情况下我们发现,除了数据是最后一起出现的,和propagation_new达到的效果很像
但是他们还是有区别的:
NESTED传播行为会沿用当前事务的事务的隔离级别和锁特性
REQUIRES_NEW 则可以拥有自己独立的隔离级别和锁特性
?问题,为什么
既然我们可以在 propagaion_nested里面捕获异常,为什么默认的那种方法就不可以捕获异常呢?
情况五:
批量方法捕获异常,单个方法不设置事务,批量方法 也不设置事务
结果是:
每插入一条出现一条数据,ID自增3
插入了1+2两条记录
情况六:
批量方法捕获异常,单个方法不设置事务,批量方法事务为REQUIRED
结果是:
插入了1+2两条记录
批量里面第二条之后的没有插入
ID自增了3
发现子方法不设置NESTED也能实现相同的结果,那为什么子方法还要设置NESTED呢?
这个问题我们一起来想想
@Transactional自调失效的问题
刚才我们所以的代码都是一个类的方法去调用领用另一个类的代码,但是如果是相同类呢?
把单个的方法和批量的方法弄到一个类里面
@Service
public class MyRedisBatch2ServiceImpl implements MyRedisBatch2Service {
@Autowired
private MyRedisMapper myRedisMapper;
@Autowired
private MyRedisBatch2Service myRedisBatch2Service;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public int insertList2(ListmyRedisDOList) {
int count = 0;
MyRedisDO jihe = new MyRedisDO();
jihe.setName("这是集合插入的");
jihe = myRedisBatch2Service.insert2(jihe);
for (MyRedisDO myRedisDO : myRedisDOList) {
MyRedisDO myRedisDO1 = myRedisBatch2Service.insert2(myRedisDO);
if (myRedisDO1 != null) {
count++;
}
}
return count;
}
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public MyRedisDO insert2(MyRedisDO myRedisDO) {
myRedisMapper.insert(myRedisDO);
return myRedisDO;
}
}
结果发现大的方法的事务生效了,但是小方法里面的没有生效,默认给它REQUIRED的事务了,
并没有新起一个REQUIRES_NEW事务
WHY?
因为对于@Transaction本身其实是一种事务的约定,其实现原理是AOP,而AOP的原理是动态代理
在自调的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP
那有没有放大去解决这个问题呢?
答案是肯定有的,我们可以对类进行改造,可以参考网上的例子,这里不做说明