深入理解Spring事务机制

news2024/12/23 13:50:16

Spring事务机制

  • 一:故事背景
  • 二:核心知识
    • 2.1 Spring事务种类
      • 2.2.1 编程式事务
      • 2.2.2 声明式事务
    • 2.2 Spring事务隔离级别
    • 2.3 Spring事务传播机制
      • 2.3.1 概念
      • 2.3.2 七种事务传播机制
    • 2.4 Spring声明式事务实现原理
      • 2.4.1 Bean初始化创建代理对象
      • 2.4.2 执行目标方法时进行事务增强
    • 2.5 Spring声明式事务失效场景
      • 2.5.1 应用在非Public的方法上
      • 2.5.2 属性propagation设置错误
      • 2.5.3 rollbackFor设置错误
      • 2.5.4 同一个类中方法调用
  • 三:总结提升

一:故事背景

本文将重点分享Spring事务相关知识,通过这篇文章,了解Spring事务的实现原理,让你以后在开发中,使用的有底气,有依据。

二:核心知识

2.1 Spring事务种类

2.2.1 编程式事务

  1. 编程式事务是一种在代码中显式地编写事务管理逻辑的方法,相对于声明式事务来说,更加灵活但也更加繁琐。在编程式事务中,开发者需要通过编写代码来控制事务的开始、提交和回滚。
  2. Spring框架同样提供了编程式事务管理的支持,通常通过编程式事务管理接口来实现,例如PlatformTransactionManager。

例子:

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Component
public class BankService {

    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void transferFunds(String fromAccount, String toAccount, double amount) {
        TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            // 执行转账操作,更新账户余额
            // 省略具体的转账业务逻辑,假设 updateAccountBalance 方法用于更新账户余额

            updateAccountBalance(fromAccount, -amount);
            updateAccountBalance(toAccount, amount);

            transactionManager.commit(txStatus);
        } catch (Exception e) {
            transactionManager.rollback(txStatus);
            throw e;
        }
    }

    private void updateAccountBalance(String account, double amount) {
        // 更新账户余额的具体实现
    }
}

2.2.2 声明式事务

我们最常用的就是声明式事务,这里列出3点使用的注意事项:

  1. 声明式事务使用@Transactional 注解的方式
  2. 声明式事务的管理是建立在AOP上的,本质是通过AOP功能,对方法的前后进行拦截,将事务的处理编织到拦截的方法中去。
  3. 通过@Transaction最细的粒度只能做到方法的级别,在目标方法执行前开启事务,执行完成后提交事务,出现错误回滚事务。

例子:

import org.springframework.transaction.annotation.Transactional;

@Component
public class BankService {
	
	@Transactional
    public void transferFunds(String fromAccount, String toAccount, double amount) {
        // 执行转账操作,更新账户余额
        // 省略具体的转账业务逻辑,假设 updateAccountBalance 方法用于更新账户余额

        updateAccountBalance(fromAccount, -amount);
        updateAccountBalance(toAccount, amount);
    }

    private void updateAccountBalance(String account, double amount) {
        // 更新账户余额的具体实现
    }
}

注意如果@Transactional加到方法上的话,会对指定方法进行事务管理、如果加到类上的话,会对这个类所有的public都进行事务管理。

2.2 Spring事务隔离级别

TransactionDefinition 接口定义了事务的各种属性,如传播行为、隔离级别、超时时间等,其实主要还是对应数据库的事务隔离级别。
我们来看一下TransactionDefinition 对应源码

/**
 * 定义了事务的属性,包括传播行为、隔离级别、超时时间等。
 */
public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;            // 当前方法必须在一个事务中执行,如果没有事务则创建一个
    int PROPAGATION_SUPPORTS = 1;            // 当前方法支持在一个事务中执行,如果没有事务也可以
    int PROPAGATION_MANDATORY = 2;           // 当前方法必须在一个事务中执行,如果没有事务则抛出异常
    int PROPAGATION_REQUIRES_NEW = 3;        // 当前方法必须在一个新的事务中执行,如果已存在事务则挂起它
    int PROPAGATION_NOT_SUPPORTED = 4;       // 当前方法不应该在事务中执行,如果存在事务则挂起它
    int PROPAGATION_NEVER = 5;               // 当前方法不应该在事务中执行,如果存在事务则抛出异常
    int PROPAGATION_NESTED = 6;              // 当前方法必须在一个嵌套事务中执行

    int ISOLATION_DEFAULT = -1;              // 使用默认的隔离级别
    int ISOLATION_READ_UNCOMMITTED = 1;      // 读未提交的数据,可能导致脏读、不可重复读、幻读
    int ISOLATION_READ_COMMITTED = 2;        // 读已提交的数据,可以避免脏读,但仍可能有不可重复读、幻读
    int ISOLATION_REPEATABLE_READ = 4;       // 可重复读,可以避免脏读、不可重复读,但仍可能有幻读
    int ISOLATION_SERIALIZABLE = 8;          // 序列化,最高隔离级别,可以避免脏读、不可重复读、幻读

    int TIMEOUT_DEFAULT = -1;                // 使用默认的超时时间

    /**
     * 获取事务的传播行为。
     *
     * @return 事务传播行为
     */
    default int getPropagationBehavior() {
        return 0;
    }

    /**
     * 获取事务的隔离级别。
     *
     * @return 事务隔离级别
     */
    default int getIsolationLevel() {
        return -1;
    }

    /**
     * 获取事务的超时时间。
     *
     * @return 事务超时时间
     */
    default int getTimeout() {
        return -1;
    }

    /**
     * 获取事务是否为只读。
     *
     * @return 是否为只读事务
     */
    default boolean isReadOnly() {
        return false;
    }

    /**
     * 获取事务的名称。
     *
     * @return 事务名称
     */
    @Nullable
    default String getName() {
        return null;
    }

    /**
     * 创建一个具有默认属性的事务定义。
     *
     * @return 默认的事务定义
     */
    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }
}

通过上面的TransactionDefinition类对应的属性可以看出主要还是对应数据库的事务隔离级别

2.3 Spring事务传播机制

2.3.1 概念

Spring 事务的传播机制说的是,当多个事务同时存在的时候— —⼀般指的是多个事务⽅法相互调⽤
时,Spring 如何处理这些事务的⾏为。
事务传播机制是使⽤简单的 ThreadLocal 实现的,所以,如果调⽤的⽅法是在新线程调⽤的,事务传
播实际上是会失效的。

2.3.2 七种事务传播机制

事务传播机制通过指定@Transactional的propagation属性进行指定

    @Transactional(propagation = Propagation.REQUIRED)
    public void  test() {
    
    }

Propagation枚举里面的7个可选项就是7种对应的传播机制

/**
 * 定义了事务的传播行为,描述了在多个事务性方法相互调用时,事务的行为方式。
 */
public enum Propagation {
    REQUIRED(0),         // 默认 如果当前没有事务,创建一个新事务;如果已存在事务,则加入到当前事务中
    SUPPORTS(1),         // 如果当前存在事务,就在事务中执行;否则以非事务方式执行
    MANDATORY(2),        // 必须在一个已存在的事务中执行,否则抛出异常
    REQUIRES_NEW(3),     // 每次都创建一个新的事务,挂起当前事务(如果存在)
    NOT_SUPPORTED(4),    // 以非事务方式执行,挂起当前事务(如果存在)
    NEVER(5),            // 以非事务方式执行,如果存在事务则抛出异常
    NESTED(6);           // 如果当前存在事务,就在嵌套事务中执行;否则创建一个新事务

    private final int value;

    /**
     * 构造函数,用于设置传播行为对应的数值。
     *
     * @param value 传播行为的数值表示
     */
    private Propagation(int value) {
        this.value = value;
    }

    /**
     * 获取传播行为对应的数值表示。
     *
     * @return 传播行为的数值表示
     */
    public int value() {
        return this.value;
    }
}

2.4 Spring声明式事务实现原理

Spring声明式事务的实现原理是通过 AOP+动态代理进行实现的,主要分为以下两个部分:

2.4.1 Bean初始化创建代理对象

Spring 容器在初始化每个单例 bean 的时候:

  1. 遍历容器中的所有 BeanPostProcessor 实现类,并执⾏其 postProcessAfterInitialization ⽅法
  2. 在执⾏AbstractAutoProxyCreator 类的 postProcessAfterInitialization ⽅法时会遍历容器中所有的切⾯,查找与当前实例化 bean 匹配的切⾯,这⾥会获取事务属性切⾯,查找@Transactional 注解及其属性值,然后根据得到的切⾯创建⼀个代理对象,默认是使⽤ JDK 动态代理创建代理,如果⽬标类是接口,则使⽤ JDK 动态代理,否则使⽤ Cglib。

2.4.2 执行目标方法时进行事务增强

在执⾏⽬标⽅法时进⾏事务增强操作:当通过代理对象调⽤ Bean ⽅法的时候,会触发对应的

  1. AOP 增强拦截器,声明式事务是⼀种环绕增强,对应接⼜为 MethodInterceptor ,事务增强对该
    接⼜的实现为 TransactionInterceptor
  2. 事务拦截器 TransactionInterceptor 在 invoke ⽅法中,通过调⽤⽗类 TransactionAspectSupport
    的 invokeWithinTransaction ⽅法进⾏事务处理,包括开启事务、事务提交、异常回滚。

2.5 Spring声明式事务失效场景

2.5.1 应用在非Public的方法上

Spring AOP 代理时,TransactionInterceptor (事务拦截器)在⽬标⽅法执⾏前后进⾏拦
截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept ⽅法 或
JdkDynamicAopProxy 的 invoke ⽅法会间接调⽤ AbstractFallbackTransactionAttributeSource
的 computeTransactionAttribute⽅法,获取 Transactional 注解的事务配置信息。
在这里插入图片描述

2.5.2 属性propagation设置错误

上面我们讲了7中事务传播行为,如果选择以下三种可能导致事务失效:
在这里插入图片描述

2.5.3 rollbackFor设置错误

  1. rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承⾃ RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。
  2. 若在⽬标⽅法中抛出的异常是 rollbackFor 指定的异常的⼦类,事务同样会回滚。
// 希 望 ⾃ 定 义 的 异 常可以进⾏回滚
@Transactional( propagation = Propagation .REQUIRED , rollbackFor = MyException . class)

在这里插入图片描述

2.5.4 同一个类中方法调用

是由于使⽤ Spring AOP 代理,只有当事务⽅法被当前类以外的代码调⽤时,才会由 Spring ⽣成的代理对象来管理。如果我们一个类内有两个方法A和方法B。B开启了事务,A调用了B,A没有开启事务,这样的话,B的事务实际是无效的。
例如:

@Service
public class UserService {

    @Transactional
    public void processUser(User user) {
        // Some processing logic
    }

    public void createUserAndProcess(String username) {
        User user = createUser(username);
        processUser(user);
    }
}

如果外部调用的是createUserAndProcess的话processUser的事务就会失效。

三:总结提升

本文系统的总结了Spring中事务的不同种类、隔离级别、传播机制、声明式事务实现原理、还总结了4中可能导致事务失效的场景,希望读者读完之后能明确事务的原理,并且在使用中避开常见的可能导致事务失效的坑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/911067.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SRM系统招投标管理:提升供应链效能

在现代商业环境中,供应链管理的成功与否对企业的运作效率和竞争力有着至关重要的影响。而招投标管理作为供应链管理的重要环节之一,其有效性和高效性对于企业的成功非常关键。为了提升招投标管理的效率和质量,越来越多的企业开始采用供应关系…

redis7高级篇2 redis的BigKey的处理

一 Bigkey的处理 1.1 模拟造数 1.截图 2.代码 &#xff1a;使用pipe 批量插入10w的数据量 cat /root/export/monidata.txt | redis-cli -h 127.0.0.1 -a 123456 -p 6379 --pipe [rootlocalhost export]# for((i1;i<10*10;i)); do echo "set k$i v$i" >>…

银河麒麟系统遇到回收站文件无法彻底删除问题及解决办法

【问题描述】 系统版本&#xff1a;银河麒麟 主机型号&#xff1a;长城世恒 用户下载了一个近100G的文件保存在/data路径下&#xff0c;导致磁盘空间100%&#xff0c;删除时未选择彻底删除而是删到回收站&#xff0c;空间未彻底释放&#xff0c;导致回收站里也无法删除掉&…

IDEA社区版启动Application==旗舰版SpringBoot

首先你要确定你的javac版本等同于你的本地的 注意你的Java Compiler 要保证和你的Javac版本一致 注意你的modules 中的resource 和 dependence中的版本一致&#xff0c;还有project 以上内容都是基本内容&#xff0c;一定要确认好 创建Application容器 参考&#xff1a;htt…

山 IT

山是地球上最常见的地貌之一&#xff0c;它们覆盖了其中的大部分表面。山脉和山峰以不同的形状和高度展现出来&#xff0c;给人们提供了壮丽的景观。 山的形成是一个长期的地质过程。前期的火山喷发和地壳运动会导致地面的隆起和抬升&#xff0c;形成高地。随着时间的推移&…

如何再钉钉内跳转自己的网页链接

1.跳转网页转码(工具地址) 原网页:https://www.baidu.com/ 转码后:https%3A%2F%2Fwww.baidu.com%2F 2:地址拼接(官方跳转地址:dingtalk://dingtalkclient/page/link?urlURL&pc_slidetrue) 替换URL: dingtalk://dingtalkclient/page/link?urlhttps%3A%2F%2Fwww.baidu.co…

【C语言】位段详解

前言 上一篇文章&#xff0c;我们学习了结构体的相关知识&#xff0c;今天我们来学习和结构体很像的位段 自定义类型&#xff1a;结构体 位段 位&#xff1a;指的是二进制位 位段的声明 位段与结构体的声明有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或…

实战:JVM调优命令工具

1、查看堆内存每个对象的信息 jmap -histo 12719 输出文件 jmap -histo 12719 > ./log.txt num: 序号 instances: 实例个数 bytes: 占用空间大小 class name: 类名称 2、查看堆内存信息 jmap -heap 12719 Heap Configuration: 分配的内存空间大小 Heap Usage: 使用的堆内存…

第二届人工智能与智能信息处理技术国际学术会议(AIIIP 2023)

第二届人工智能与智能信息处理技术国际学术会议&#xff08;AIIIP 2023&#xff09; 2023 2nd International Conference on Artificial Intelligence and Intelligent Information Processing 第二届人工智能与智能信息处理技术国际学术会议&#xff08;AIIIP 2023&#xf…

新闻稿写作与校对:爱校对如何确保信息准确无误?

新闻产业一直以其快节奏和时效性为标志。每一篇新闻稿件都需要在第一时间发布&#xff0c;但在这种快速的环境下&#xff0c;如何确保内容不仅快速&#xff0c;而且准确呢&#xff1f;这就是“爱校对”发挥作用的地方&#xff0c;它为新闻机构和记者提供了一种简单、高效的方法…

【音视频】语音对讲之 平台到设备

目录 反向语音流程 2.8-》设备&#xff0c;广播​编辑 设备-》2.8 回复 ​编辑 设备-》2.8 invite sdp​编辑 刚才是19008&#xff0c;我这记录的是19004​编辑 续 【音视频】国标双向对讲方案_wvp 对讲_dualven_in_csdn的博客-CSDN博客 反向语音流程 2.8-》设备&#xff0c;…

湘潭大学 湘大 XTU OJ 1215 A+B V 题解(非常详细)

链接 AB V 题面 题目描述 小明很喜欢做ab&#xff0c;他但经常忘记进位&#xff0c;所以他算881290,而不是100。 现在你给了小明一些ab的算式&#xff0c;请问他算出来会是什么&#xff1f; 输入 第一行是一个整数K&#xff0c;表示样例的个数。 每个样例占一行&#xff…

通信原理板块——基础知识(二)

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 在上一章节介绍了信号&#xff0c;…

YOLO目标检测——磁瓦缺陷数据集下载分享

磁瓦是一种常见的建筑材料&#xff0c;用于地板、墙壁等表面装饰&#xff0c;磁瓦缺陷数据集是用于研究和分析磁瓦缺陷检测问题的数据集。 数据集点击下载&#xff1a;YOLO磁瓦缺陷数据集2700图片6类别.rar

FastReport 群组未连接至数据库

当FastReport 出现群组未连接至数据库 的报错时&#xff0c;可能是GroupHeader的位置不对&#xff0c;如下图&#xff0c;往上拖一下即可。 然后就可以打印出分组的样式了&#xff1a;

怎样理解三次握手和四次挥手?

1. 三次握手 三次握手的大致流程图如下所示 &#xff08;1&#xff09;当客户端想与服务端建立连接时&#xff0c;客户端会先发送连接请求到服务端&#xff0c;该请求需要服务器接收并确认&#xff0c;这是一次握手&#xff1b; &#xff08;2&#xff09;当服务端收到了客户…

太残忍了,程序员造囚禁被拔牙,全身损伤 88%

周末的时候&#xff0c;看到一条值得大家警惕和警醒的新闻。 据九派新闻报道&#xff0c;中国一男子因未编写出赌博游戏程序&#xff0c;在越南遭到数月的囚禁、虐待。报道称&#xff0c;46 岁的受害人房立敬是一名程序员&#xff0c;由于在中国做生意遇到困难&#xff0c;于 2…

RFID工业识别技术在智能仓储管理中的创新实践

随着信息技术的飞速发展&#xff0c;智能仓储管理已然成为现代物流行业的核心趋势。在这一变革的背景下&#xff0c;RFID工业识别技术崭露头角&#xff0c;为仓储管理注入了创新动力。本文将深入探讨RFID在智能仓库自动化、盘点与分拣中的应用&#xff0c;以及如何解决传统仓储…

你知道fastjson的反序列化漏洞吗?

面试回答 当我们使用 fastjson 进行序列化的时候&#xff0c;当一个类中包含了一个接口&#xff08;或抽象类&#xff09;的时候&#xff0c;会将子类型抹去&#xff0c;只保留&#xff08;抽象类&#xff09;的类型&#xff0c;使得反序列化时无法拿到原始类型。 那么为了解决…

Redis 5环境搭建

一、环境搭建 如果是Centos8&#xff0c;yum 仓库中默认的 Redis版本就是5&#xff0c;直接yum install即可。如果是Centos7&#xff0c;yum 仓库中默认的 Redis版本是3系列&#xff0c;比较老~ 为了我们能在 Centos7中下载到 Redis5 首先要安装额外的软件源 sudo yum insta…