# 详解事务的传播

在前面演示中,我们使用了这个注解

@Transactional(propagation = Propagation.REQUIRED)
1

查看 Transactional 的源码可以看到它有一个默认的事物传播类型

Propagation propagation() default Propagation.REQUIRED;
1

Propagation 就是支持的事物传播类型了,定义了 7 个枚举,下面分别来讲解

  • REQUIRED:支持当前事务,如果不存在则创建一个新事务 这是事务注释的默认设置

  • SUPPORTS:支持当前事务,如果不存在则非事务执行

    主要用于查询操作,因为查询也有可能异常,所以如果调用它的方法已经存在一个事物,那么我们就加入这个事物,如果不存在事物,则以非事物方式执行。

  • MANDATORY:支持当前事务,如果不存在则抛出异常

  • REQUIRES_NEW:创建一个新的事务,并暂停当前事务(如果存在)

    // REQUIRED  
    parent {
      s1(); // REQUIRED
      s2(); // REQUIRES_NEW,并且无异常  
      // 模拟一个异常
    }
    
    1
    2
    3
    4
    5
    6

    方法调用之后:

    • 尽管 parent 方法抛出异常了,但是 s2 是不会产于 parent 的事物的,所以 s2 的保存生效了
    • s1 保存失效,原因是因为它支持 parent 的事物,parent 由于异常会触发当前事物回滚
    // REQUIRED  
    parent {
      s1(); // REQUIRED
      try{
      	s2(); // REQUIRES_NEW, 模拟一个异常  
      }cace(e)
    }
    
    1
    2
    3
    4
    5
    6
    7

    s1 保存成功,s2 保存失败,由于 REQUIRES_NEW 是新创建的事物,不是和 parent 使用的同一个事物,所以只会影响它自己;

    // REQUIRED  
    parent {
      s1(); // REQUIRED
      try{
      	s2(); // REQUIRED, 模拟一个异常  
      }cace(e)
    }
    
    1
    2
    3
    4
    5
    6
    7

    这样的话,事物就会报错了,因为用的是同一个事物,s2 异常之后,就意味着该事物会回滚;

    总结如下:

    • 如果当前有事物,则挂起该事物,并且创建一个新的事物给自己使用
    • 如果没有事物,则同 REQUIRED
    • 父事务有异常,不影响子
  • NOT_SUPPORTED:以非事务方式执行,如果存在当前事务,则挂起当前事务

    挂起的意思和上面 暂停事物 类似

    // REQUIRED  
    parent {
      s1(); // REQUIRED
      s2(){
        sc1()
        // 模拟一个异常
        sc2()
      } // NOT_SUPPORTED 
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    方法调用后:

    • s2 中不会参加 parent 的事物,所以 sc1 保存成功,sc2 由于异常原因不会执行

    • s1 属于会参与 parent 的事物,但是在调用 s2 时抛出了异常,那么 s1 会回滚

  • NEVER:非事务执行,如果存在事务,则引发异常

  • NESTED:如果当前事务存在,则在嵌套事务中执行,否则表现为 REQUIRED

    嵌套事物和 REQUIRES_NEW 类似,如果当前存事物,他们都会新建一个事物执行

    // REQUIRED  
    parent {
      s1(); // REQUIRED
      s2(){
        sc1()
        sc2()
      } // NESTED 无异常
      // 模拟异常
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    这个表现就是 s2 使用了 parent 的事物

    // REQUIRED  
    parent {
      s1(); // REQUIRED
      s2(){
        sc1()
        // 模拟异常
        sc2()
      } // NESTED
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    同理, s2 使用了 parent 的事物

    // REQUIRED  
    parent {
      s1(); // REQUIRED
      
      try{
        // 在数据库中有一个概念是 save point
        s2(){
          sc1()
          // 模拟异常
          sc2()
        } // NESTED
      }cace(){}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    当 s2 异常后,s2 的执行会回滚,但是不会影响 parent 里面的 s1 保存。

    总结如下:

    • 如果当前有事务:则开启子事务(嵌套事务),嵌套事务是独立提交或则回滚
    • 如果当前没有事物,则同 REQUIRED
    • 但是如果主事物提交,则会携带子事物一起提交
    • 如果主事物回滚,则子事物会一起回滚,相反,子事物异常,则父事务可以选择回滚还是提交

以上事物传播类型的含义,在源码中有写,翻译成中文就是如上所示的含义。

有争议的是 REQUIRES_NEW 和 NESTED:

REQUIRES_NEW,始终启动一个新事物

  • 两个事物没有关联
  • 外部事物回滚,里面的事物不受影响

NESTED,在原事物内启动一个内嵌事物

  • 两个事物有关联
  • 外部事物回滚,内嵌事物也会回滚

所以他们的不同点的表现是:外部事物是否会影响内部事物。其他的事物表现都差不多。

如果要测试事物传播类型的话,可以使用 Junit 来测试,当然是要在之前的 stu 测试服务去写测试代码.

在 foodie-dev-api 项目中加入 spring boot 的 Junit 依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
1
2
3
4
5

测试类写法为

package cn.mrcode.foodiedev.api;

import cn.mrcode.foodiedev.Application;
import cn.mrcode.foodiedev.service.StuService;
import cn.mrcode.foodiedev.service.impl.TestTransServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class TransTest {
    @Autowired
    private TestTransServiceImpl testTransService;

    @Test
    public void myTest() {
        testTransService.testPropagationTrans();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.mrcode.foodiedev.service.impl;

import cn.mrcode.foodiedev.pojo.Stu;
import cn.mrcode.foodiedev.service.StuService;
import cn.mrcode.foodiedev.service.TestTransService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 测试事物
 */
@Service
public class TestTransServiceImpl implements TestTransService {
    @Autowired
    private StuService stuService;

//    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void testPropagationTrans() {
        stuService.saveParent();
        stuService.saveChildren();

        // 模拟一个异常
        int a = 1 / 0;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

我们要来测试最简单的办法就是,执行创建、更新等操作,然后在里面抛出异常,之类的手段进行查看事物是否按照设置的事物传播类型的表现生效。

比如上面这一个,正常调用了新增的两个对象,然后模拟一个异常,在 不加/加 事物注解的情况下执行,观察数据库结果是否一致。正常情况下如下描述:

  • 未使用事物注解:执行测试后,数据库中会出现两条数据
  • 使用事物注解:执行测试后,数据库中不会出现数据