侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Spring 事务传播机制

再见理想
2022-05-27 / 0 评论 / 0 点赞 / 310 阅读 / 2,115 字

一,什么是事务的传播

简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。

二,Spring 事务传播类型枚举

Propagation 事务传播枚举类:

在Spring中对于事务的传播行为定义了七种类型分别是:

REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED

它是为了结合 @Transactional 注解使用而设计的,这个枚举里面定义的事务传播行为类型与 TransactionDefinition 中定义的事务传播行为类型是对应的,所以在使用 @Transactional 注解时我们就要使用 Propagation 枚举类来指定传播行为类型,而不直接使用 TransactionDefinition 接口里定义的属性。

三,Propagation 事务传播行为

场景

先设计一个场景,帮助理解,场景描述如下:

//将传入参数a存入ATable
pubilc void A(a){
    insertIntoATable(a);    
}
//将传入参数b存入BTable
public void B(b){
    insertIntoBTable(b);
}

接下来,我们看看在如下场景下,没有事务,情况会怎样

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}

public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果:a1数据成功存入ATable表,b1数据成功存入BTable表,而在抛出异常后b2数据存储就不会执行,也就是b2数据不会存入数据库,这就是没有事务的场景。

重要提示:Spring中事务的默认实现使用的是AOP,也就是代理的方式,如果大家在使用代码测试时,同一个Service类中的方法相互调用需要使用注入的对象来调用,不要直接使用this.方法名来调用,this.方法名调用是对象内部方法调用,不会通过Spring代理,也就是事务不会起作用。

Required(默认)

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务。

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果:a1,b1回滚了,b2没执行。

再看看下面案例:

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果: 数据a1存储成功,数据b1和b2没有存储。 testB 有声明事务且传播行为是 REQUIRED,所以在执行testB时会自己新建一个事务(如果当前没有事务,则自己新建一个事务),testB抛出异常则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚,所以最终a1数据存储成功,b1和b2数据没有存储。

SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行。

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果: a1,b1存入数据库,b2没有存入数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),则在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。

MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果: a1存储成功,而b1和b2没有存储。b1和b2没有存储,并不是事务回滚的原因,而是因为testMain方法没有声明事务,在去执行testB方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以testB方法里的内容就没有执行。

REQUIRES_NEW

**创建一个新事务,如果存在当前事务,则挂起该事务。**可以理解为在执行时,不论当前是否存在事务,总是会新建一个事务。

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
    throw Exception;     //发生异常抛出
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

执行结果: a1 没有存储,而 b1 和 b2 存储成功,因为 testB 会开启一个新的事务,testMain 中发生的异常时在 testMain 所开启的事务中,所以这个异常不会影响 testB 的事务提交,所以最终a1就没有存储,而b1和b2就存储成功了。

NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务。

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果: a1和b2没有存储,而b1存储成功。testMain有事务,而testB不使用事务,所以执行中testB的存储b1成功,然后抛出异常,此时testMain检测到异常事务发生回滚,但是由于testB不在事务中,所以只有testMain的存储a1发生了回滚,最终只有b1存储成功,而a1和b1都没有存储。

NEVER

不使用事务,如果当前事务存在,则抛出异常。

很容易理解,就是我这个方法不使用事务,并且调用我的方法也不允许有事务,如果调用我的方法有事务则我直接抛出异常。

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.NEVER)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

执行结果:该场景执行,直接抛出事务异常,且不会有数据存储到数据库。由于testMain事务传播类型为REQUIRED,所以testMain是运行在事务中,而testB事务传播类型为NEVER,所以testB不会执行而是直接抛出事务异常,此时testMain检测到异常就发生了回滚,所以最终数据库不会有数据存入。

NESTED

如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
    throw Exception;     //发生异常抛出
}
@Transactional(propagation = Propagation.NESTED)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

执行结果:所有数据都不会存入数据库。

再看一个案例:

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    try{
        testB();    //调用testB
    }catch(Exception e){

    }
    A(a2);
}
@Transactional(propagation = Propagation.NESTED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

执行结果:这种场景下,结果是a1,a2存储成功,b1和b2存储失败,因为调用方catch了被调方的异常,所以只有子事务回滚了。

同样的代码,如果我们把testB的传播类型改为REQUIRED,结果也就变成了:没有数据存储成功。就算在调用方catch了异常,整个事务还是会回滚,因为,调用方和被调方共用的同一个事务

0

评论区