mysql传播行为_MYSQL事务的传播行为看这一篇就够了

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

那有没有放大去解决这个问题呢?

答案是肯定有的,我们可以对类进行改造,可以参考网上的例子,这里不做说明


版权声明:本文为weixin_39734987原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。