2023.12.9 关于 Spring Boot 事务传播机制详解

2023-12-14 18:32:43

目录

事务传播机制

七大事务传播机制?

支持当前调用链上的事务

Propagation.REQUIRED

Propagation.SUPPORTS

Propagation.MANDATORY

不支持当前调用链上的事务

Propagation.REQUIRES_NEW

Propagation.NOT_SUPPORTED

Propagation.NEVER

嵌套事务

Propagation.NESTED

前置工作

初始化数据库

初始化 实体类

初始化 Mapper 接口

初始化 XML 文件

?重点理解部分

NESTED 和 REQUIRED_NEW 的区别

NESTED 和 REQUIRED 的区别


事务传播机制

  • 事务的传播机制是指在多个事务方法之间调用时,事务如何在这些方法之间传播

七大事务传播机制?

支持当前调用链上的事务

Propagation.REQUIRED

  • 默认的事务传播级别
  • 如果当前没有事务,则新建一个事务,如果当前存在事务,则加入该事务

实例理解


Propagation.SUPPORTS

  • 如果当前方法没有事务,就以非事务方式执行,如果已经存在一个事务中,则加入到这个事务中


Propagation.MANDATORY

  • 如果当前方法没有事务,则抛出异常,如果已经存在一个事务中,则加入到这个事务中

不支持当前调用链上的事务

Propagation.REQUIRES_NEW

  • 创建一个新事务,如果存在当前事务,则挂起当前事务

Propagation.NOT_SUPPORTED

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

Propagation.NEVER

  • 以非事务方式执行,如果当前事务存在,则抛出异常

嵌套事务

Propagation.NESTED

  • 如果当前存在事务,则在嵌套事务中执行,否则与 REQUIRED 的操作一样

前置工作

  • 此处为了方便下文进行代码测试理解
  • 我们先将准备工作做好

初始化数据库

  • 创建一个 user 表,并添加几条用户信息


初始化 实体类

  • 创建?User 实体类 与 数据库的 user 表字段名相对应
import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private int age;
    private String password;
    private int state;
}

初始化 Mapper 接口

  • 初始化?UserMapper 接口,此处我们添加一个 add?方法

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {
//    新增用户信息
    Integer add(User user);
}

初始化 XML 文件

  • 在与 UserMapper 接口相对应的 XML 文件中添加上与 add 方法 相对应的 sql 语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <insert id="add">
        insert into user(name,age,password) values(#{name},#{age},#{password})
    </insert>
</mapper>

?重点理解部分

NESTED 和 REQUIRED_NEW 的区别

  • REQUIRED_NEW 是新建一个事务并且新开始的这个事务与原有事务无关
  • 而 NESTED 是当前存在事务时会开启一个嵌套事务
  • 在?NESTED 情况下,父事务回滚时,子事务也会回滚
  • 而 REQUIRED_NEW 情况下,原有事务回滚,不会影响新开启的事务

实例理解

  • 我们创建一个 userController 类,并给 addUser?方法加上 REQUIRED 类型的事务
  • ?此时的 addUser 方法,将自动创建一个事务A
  • 注意我们在 addUser 中加入了一个算数异常
import com.example.demo.entity.User;
import com.example.demo.service.LoggerService;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user2")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public String addUser(User user) {
//        调用 userService 的 add 方法
        int result = userService.add(user);
        int c = 10/0;
        return "add 方法:" + (result == 1 ? "新增用户成功": "新增用户失败");
    }
}
  • 可以看到?userController 类中调用了 add 方法,该方法在 userService 中
  • 此处我们给 add 方法加上 NESTED 类型的事务
  • 因为 addUser 已经创建了一个事务A,所以此处的 add 方法将在 事务A中创建一个嵌套事务
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
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 UserService {

    @Autowired
    private UserMapper userMapper;

    //    新增用户信息
    @Transactional(propagation = Propagation.NESTED)
    public int add(User user) {
        int result = userMapper.add(user);
        return result;
    }
}

运行结果:

  • 在浏览器中输入对应的 URL 来访问 addUser 方法
  • 此时我们关注的是 在?NESTED 情况下,父事务回滚时,子事务也会回滚

  • 此处正好为 addUser 方法发生了 算数异常,从而进行了回滚操作
  • 且在抛出算数异常前,就已经调用了 userService.add 方法
  • 如果此处数据库中插入了新用户信息,则代表子事务未进行回滚操作

  • 此时我们查看 user 表,发现新用户未插入,即子事务进行了回滚操作,与预期结果一致

调整 add 方法的事务传播机制

  • 我们将 add 方法改为 REQUIRES_NEW?类型的事务
  • 因为 addUser 已经创建了一个事务A,所以此处的 add 方法将会把 事务A 挂起,另外创建一个 事务B
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
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 UserService {

    @Autowired
    private UserMapper userMapper;

    //    新增用户信息
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(User user) {
        int result = userMapper.add(user);
        return result;
    }
}

运行结果:

  • 在浏览器中输入对应的 URL 来访问 addUser 方法
  • 此时我们关注的是 REQUIRED_NEW 情况下,原有事务回滚,不会影响新开启的事务

  • 查看 user 表发现新增了一条用户信息,即新开启的事务B 未进行回滚,与预期结果一致


NESTED 和 REQUIRED 的区别

  • REQUIRED 的情况下,调用方存在事务时,则被调用发和调用方使用同一个事务
  • 那么被调用方出现异常时,由于共用同一个事务,所以无论是否 catch 异常,事务都会回滚
  • 而在 NESTTED 情况下,被调用方发生异常时,调用方可以 catch 其异常,这样只有子事务回滚,父事务不回滚

实例理解

  • 我们创建一个 userController 类,并给 addUser?方法加上 REQUIRED 类型的事务
  • 此时的 addUser 方法,将自动创建一个事务A
  • 我们将在 userService 的 add 方法中加入一个 算数异常
  • 为了验证 REQUIRED 的情况下,被调用方出现异常时,由于共用同一个事务,所以无论是否 catch 异常,事务都会回滚
  • 如果不捕获?add 方法抛出的异常 事务A 肯定会进行回滚操作
  • 所以我们此处对算数异常进行捕获,由此来看 事务A 是否还会进行回滚操作
import com.example.demo.entity.User;
import com.example.demo.service.LoggerService;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user2")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public String addUser(User user) {
        int result = 0;
        try {
//        调用 userService 的 add 方法
            result = userService.add(user);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "add 方法:" + (result == 1 ? "新增用户成功": "新增用户失败");
    }
}
  • 此处我们给 add 方法加上?REQUIRED 类型的事务
  • 因为 addUser 已经创建了一个事务A,所以此处的 add 方法将会直接加入?事务A 中
  • 此处我们在 add 方法中加入了一个算数异常
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
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 注解 代表该类会伴随着 项目的启动而注入到容器中
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    //    新增用户信息
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(User user) {
        int result = userMapper.add(user);
        int a = 1/0;
        return result;
    }
}

运行结果:

  • 在浏览器中输入对应的 URL 来访问 addUser 方法

  • 查看 user 表发现没有新增新用户信息,即事务A进行了回滚操作,与预期结果一致

调整 add 方法的事务传播机制

  • 我们将 add 方法改为? NESTED?类型的事务
  • 因为 addUser 已经创建了一个事务A,所以此处的 add 方法将会在 事务A 中创建一个嵌套事务
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
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 注解 代表该类会伴随着 项目的启动而注入到容器中
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    //    新增用户信息
    @Transactional(propagation = Propagation.NESTED)
    public int add(User user) {
        int result = userMapper.add(user);
        int a = 1/0;
        return result;
    }
}

运行结果:

  • 在浏览器中输入对应的 URL 来访问 addUser 方法
  • 此处为了验证 NESTTED 情况下,被调用方发生异常时,调用方可以 catch 其异常,这样只有子事务回滚,父事务不回滚
  • 在浏览器中输入对应的 URL 来访问 addUser 方法

  • 我们可以看到 事务A 未进行回滚操作

  • 查看 user 表发现没有新增新用户信息,即子事务进行了回滚操作,与预期结果一致

文章来源:https://blog.csdn.net/weixin_63888301/article/details/134911067
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。