锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. Spring事务传播机制

Spring事务传播机制

0
  • 软件开发
  • 发布于 2024-09-19
  • 0 次阅读
黄健
黄健

目录

1.回顾事务的隔离级别

数据库的事务隔离级别

spring事务隔离级别

2.什么是Spring事务传播机制?

3.设置事务传播级别

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW

PROPAGATION_NESTED




1.回顾事务的隔离级别

数据库的事务隔离级别

数据库提供了四种隔离级别供用户选择,包括 READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)、SERIALIZABLE(串行化)。

spring事务隔离级别

Spring 框架的事务隔离级别一共有五个,包括:

  1. DEFAULT:表示使用底层数据源的默认隔离级别,一般为数据库的 READ_COMMITTED 隔离级别。

  2. READ_UNCOMMITTED:表示读未提交,最低的隔离级别,不能保证事务的可靠性。

  3. READ_COMMITTED:表示读已提交,能够保证一个事务只会读取到已经提交的数据。

  4. REPEATABLE_READ:表示可重复读,保证在同一个事务中多次读取同一数据时,得到的结果是一致的。

  5. SERIALIZABLE:表示串行化,所有的事务依次顺序执行,可以避免以上三种并发问题。

使用 @Transactional 注解时,可以通过设置 isolation 属性来指定事务的隔离级别,示例如下:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount(int userId, double money) {
    // 更新账户余额
}

2.什么是Spring事务传播机制?

Spring 框架中的事务传播机制是为了解决多个事务方法之间相互协作的问题,以确保事务操作的一致性和完整性。

在实际的开发过程中,不同的业务逻辑可能会涉及到多个方法的调用,而这些方法有可能会出现嵌套调用的情况。如果不对这些嵌套调用的方法进行事务处理,就有可能会导致数据的不一致或者错误。

假设我们有一个电商系统,涉及到两个业务逻辑:创建订单和扣减库存。其中,创建订单的方法为 createOrder(),扣减库存的方法为 reduceStock()。现在,我们需要在一个事务中同时执行这两个方法,以确保订单和库存的一致性。这时就可以使用 Spring 框架中的事务传播机制来管理事务。

假设这两个方法都在 Service 层中实现,且使用注解方式开启事务。我们可以对 createOrder() 方法和 reduceStock() 方法分别设置不同的传播级别,例如:

@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {

  @Autowired
  private OrderDao orderDao;

  @Autowired
  private StockDao stockDao;

  // REQUIRED 传播级别
  @Transactional(propagation = Propagation.REQUIRED)
  public void createOrder(Order order) throws Exception {
    // 创建订单
    orderDao.insert(order);

    // 扣减库存
    reduceStock(order.getProductId(), order.getCount());
  }

  // REQUIRES_NEW 传播级别
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void reduceStock(Long productId, int count) throws Exception {
    // 扣减库存
    stockDao.reduce(productId, count);
  }
}

createOrder() 方法使用了 REQUIRED 传播级别,表示如果当前存在事务,则加入该事务进行处理;如果不存在事务,则开启一个新的事务。而 reduceStock() 方法则使用了 REQUIRES_NEW 传播级别,表示每次都开启一个新的事务进行处理。

假设在 createOrder() 方法中出现异常,导致事务回滚,那么 reduceStock() 方法执行的事务也会被回滚;而如果 reduceStock() 方法出现异常,只会回滚该方法中的事务,不会影响到 createOrder() 方法的事务。这样,就可以确保订单和库存的一致性。

Spring 框架中的事务传播机制可以帮助我们管理多个业务逻辑之间的事务,并根据需求设置不同的传播级别,从而确保事务操作的正确性和完整性。

Spring 框架中提供了多种事务传播级别,包括:

  1. PROPAGATION_REQUIRED 默认状态,如果当前没有事务,就新开启一个事务;否则使用当前事务。

  2. PROPAGATION_SUPPORTS 如果当前有事务,就使用该事务;否则不使用事务。

  3. PROPAGATION_MANDATORY 使用当前事务,如果当前不存在事务,就会抛出异常。

  4. PROPAGATION_REQUIRES_NEW 每次都新开启一个事务,且中断当前已经存在的事务。

  5. PROPAGATION_NOT_SUPPORTED 不支持事务,每次都在非事务状态下执行。

  6. PROPAGATION_NEVER 禁止事务,如果当前存在事务,就会抛出异常。

  7. PROPAGATION_NESTED 嵌套事务,如果当前存在事务,则在嵌套事务内执行。如果当前不存在事务,则开启一个新事务。

3.设置事务传播级别

PROPAGATION_REQUIRED

再看一个关于PROPAGATION_REQUIRED的示例:
当用户表中新增了用户信息时,创建一个日志表记录新增用户的id,time,message

日志表:

mysql> CREATE TABLE `log` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    ->   `message` text COLLATE utf8mb4_unicode_ci NOT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
    -> ;
ERROR 1050 (42S01): Table 'log' already exists
mysql> select*from log;
Empty set (0.00 sec)

mysql> desc log;
+-----------+-----------+------+-----+-------------------+----------------+
| Field     | Type      | Null | Key | Default           | Extra          |
+-----------+-----------+------+-----+-------------------+----------------+
| id        | int(11)   | NO   | PRI | NULL              | auto_increment |
| timestamp | timestamp | NO   |     | CURRENT_TIMESTAMP |                |
| message   | text      | NO   |     | NULL              |                |
+-----------+-----------+------+-----+-------------------+----------------+
3 rows in set (0.01 sec)

用户表:

mysql> select *from userinfo
    -> ;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi3    | 456789   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.01 sec)

接下来用到的方法会进行嵌套调用,默认情况下时PROPAGATION_REQUIRED对这些嵌套调用的方法进行事务处理

Spring 事务传播机制的默认值是 REQUIRED。

加入事务:如果当前没有事务,那么被调用的事务方法会开启一个新的事务;如果当前已经有了事务,那么被调用的事务方法会加入到当前事务中,与当前事务同步提交或回滚。

@Data
public class Log {
    private int id;
    private LocalDateTime timestamp;
    private String message;
}
@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}

@RestController
@RequestMapping("/user")
public class Controller3 {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username,String password){
        if(null == username || null == password
            || username.equals("") || password.equals("")) return 0;
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(password);
        int result = userService.add(userInfo);
        return result;
    }
}


@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public  int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: "+ result);
        int num = 100/0;
        return result;
    }

}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private LogService logService;

    public int del(Integer id) {
        return userMapper.del(id);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo){
        //给用户添加信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: "+ addUserResult);
        //给用户添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);
        return  addUserResult;
    }

}

<?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.LogMapper">

    <insert id="add">
        insert into log(`message`) values(#{message})
    </insert>
</mapper>


<?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">
    <delete id="del">
        delete from userinfo where id = #{id}
    </delete>

    <insert id="add">
        insert into userinfo(username,password) values
        (#{username},#{password})
    </insert>
</mapper>

执行:

sql执行日志:

数据库中结果:

分析一下原因:

整个执行过程的方法调用链:

异常在logservice出现后进行了事物的回滚。由于使用的是PROPAGATION_REQUIRED对这些嵌套调用的方法进行事务处理

调用链上的方法都加入到了同一个事务中,因此一处出现了异常。整个调用链上的所有方法都会进行回滚。

从而即使添加用户行为没发生异常,但由于其调用了出现异常的日志添加方法,他也跟着回滚了

我们的预期执行结果:在一个调用链上的事务,各自的执行结果相互不干扰

PROPAGATION_REQUIRES_NEW

也即在上述例子中,用户添加事务和日志添加事务相互不影响,一个出现异常了,另一个不会跟随着回滚

使用到的传播机制:REQUIRES_NEW

表示当前方法必须开启一个新的事务运行,如果当前已经有事务,则挂起当前事务

改动代码:

对日志方法手动回滚,否则出现异常代码,不处理异常,整个调用链都会报错,感知到后都进行回滚了

将其他方法事务传播机制修改为:
@Transactional(propagation = Propagation.REQUIRES_NEW)

对日志方法手动回滚
@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public  int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: "+ result);
        //回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        //int num = 100/0;
        return result;
    }

}

用户添加成功,日志进行回滚了

mysql> select*from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi3    | 456789   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
| 20 | zhaoliu  | 123456   |       | 2023-05-31 11:31:51 | 2023-05-31 11:31:51 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
4 rows in set (0.00 sec)

mysql> select*from log;
Empty set (0.00 sec)

将REQUIRES_NEW换为REQUIRED 默认状态执行又会出现报错且全部回滚的情况

报错: Transaction rolled back because it has been marked as rollback-only

内部事务回滚,外部事务也会回滚,但会报异常

如果外部事务回滚,内部事务会跟着回滚,不会报异常

数据库表还是没有任何改变,全部回滚

PROPAGATION_NESTED


嵌套事务:PROPAGATION_NESTED ,如果当前存在事务,则在嵌套事务内执行。如果当前不存在事务,则开启一个新事务。

@Transactional(propagation = Propagation.NESTED)

将日志手动回滚

mysql> select*from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi3    | 456789   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
| 20 | zhaoliu  | 123456   |       | 2023-05-31 11:31:51 | 2023-05-31 11:31:51 |     1 |
| 23 | zhaoliu2 | 123456   |       | 2023-05-31 12:33:36 | 2023-05-31 12:33:36 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
5 rows in set (0.00 sec)

mysql> select*from log;
Empty set (0.00 sec)

结果:只回滚日志事务,用户添加事务没有回滚

嵌套事务和加入事务的区别

两种方式的区别主要在于事务的隔离级别和提交方式:

  • 隔离级别:在嵌套事务中,子事务具有独立的隔离级别,并且可以根据需要进行回滚或提交;而在加入事务中,所有事务共享同一个隔离级别,且只有一个总事务进行提交或回滚。
  • 提交方式:在嵌套事务中,子事务的提交或回滚操作不会对外部事务产生影响;而在加入事务中,所有事务共享同一个提交或回滚操作,即只有总事务提交或回滚。






原文链接: https://blog.csdn.net/chenchenchencl/article/details/130962196

标签: #软件开发 1171 #Spring 84
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.