侧边栏壁纸
博主头像
高大北博主等级

所有的再见中,我最喜欢明天见

  • 累计撰写 208 篇文章
  • 累计创建 151 个标签
  • 累计收到 20 条评论
标签搜索

目 录CONTENT

文章目录

Transactional事物注解(十一)

高大北
2022-04-28 / 0 评论 / 1 点赞 / 274 阅读 / 3,549 字 / 正在检测是否收录...
@Transactional(propagation = Propagation.REQUIRED)

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

Propagation propagation() default Propagation.REQUIRED;

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

REQUIRED

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

SUPPORTS

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

MANDATORY

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

REQUIRES_NEW

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

// REQUIRED  
parent {
  s1(); // REQUIRED
  s2(); // REQUIRES_NEW,并且无异常  
  // 模拟一个异常
}

方法调用之后:
○ 尽管 parent 方法抛出异常了,但是 s2 是不会产于 parent 的事物的,所以 s2 的保存生效了
○ s1 保存失效,原因是因为它支持 parent 的事物,parent 由于异常会触发当前事物回滚

// REQUIRED  
parent {
s1(); // REQUIRED
try{
	s2(); // REQUIRES_NEW, 模拟一个异常  
}cace(e)
}

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

// REQUIRED  
parent {
  s1(); // REQUIRED
  try{
  	s2(); // REQUIRED, 模拟一个异常  
  }cace(e)
}

这样的话,事物就会报错了,因为用的是同一个事物,s2 异常之后,就意味着该事物会回滚;
总结如下:
○ 如果当前有事物,则挂起该事物,并且创建一个新的事物给自己使用
○ 如果没有事物,则同 REQUIRED
○ 父事务有异常,不影响子

NOT_SUPPORTED

以非事务方式执行,如果存在当前事务,则挂起当前事务,挂起的意思和上面 暂停事物 类似

// REQUIRED  
parent {
  s1(); // REQUIRED
  s2(){
    sc1()
    // 模拟一个异常
    sc2()
  } // NOT_SUPPORTED 
}

方法调用后:
○ s2 中不会参加 parent 的事物,所以 sc1 保存成功,sc2 由于异常原因不会执行
○ s1 属于会参与 parent 的事物,但是在调用 s2 时抛出了异常,那么 s1 会回滚

NEVER

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

NESTED

如果当前事务存在,则在嵌套事务中执行,否则表现为 REQUIRED
嵌套事物和 REQUIRES_NEW 类似,如果当前存事物,他们都会新建一个事物执行

// REQUIRED  
parent {
  s1(); // REQUIRED
  s2(){
    sc1()
    sc2()
  } // NESTED 无异常
  // 模拟异常
}

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

// REQUIRED  
parent {
  s1(); // REQUIRED
  s2(){
    sc1()
    // 模拟异常
    sc2()
  } // NESTED
}

同理, s2 使用了 parent 的事物

// REQUIRED  
parent {
  s1(); // REQUIRED
  
  try{
    // 在数据库中有一个概念是 save point
    s2(){
      sc1()
      // 模拟异常
      sc2()
    } // NESTED
  }cace(){}
}

当 s2 异常后,s2 的执行会回滚,但是不会影响 parent 里面的 s1 保存。
总结如下:
○ 如果当前有事务:则开启子事务(嵌套事务),嵌套事务是独立提交或则回滚
○ 如果当前没有事物,则同 REQUIRED
○ 但是如果主事物提交,则会携带子事物一起提交
○ 如果主事物回滚,则子事物会一起回滚,相反,子事物异常,则父事务可以选择回滚还是提交
以上事物传播类型的含义,在源码中有写,翻译成中文就是如上所示的含义。

REQUIRES_NEW 和 NESTED

有争议的是 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>

测试类写法为

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();
    }

}

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

评论区