Spring事务管理和事务传播机制详解

news2024/12/24 20:24:56


目录

一.简单理解事务

二.Spring中事务的实现

编程式事务

声明式事务

三.@Transactional 详解

▐ 异常回滚属性 rollbackFor

▐ 事务隔离级别 Isolation

▐ 事务的传播机制 propagation


一.简单理解事务

事务是⼀组操作的集合,是⼀个不可分割的操作。

事务会把一组操作作为一个整体,这组中的全部操作要么同时成功,要么同时失败,不允许一部分成功一部分失败的情况。就拿现实生活中大家玩游戏来举例:

游戏里面有许多充值道具,诸如王者荣耀里面英雄的皮肤,瓦洛兰特里面枪械的皮肤,对于这部分游戏内容我们需要充钱才能获得,在充钱的过程中不知道大家有没有担心过这样一件事:“万一充钱的时候突然网卡了,我钱花了但是没有给我点券和皮肤,把我钱吞了怎么办?”。但是这么多年来这个担忧却从来没有发生过,即使是网络质量不佳我们也只会出现以下俩种情况:

  1. 玩家进行充值后得到了应有的点券或皮肤
  2. 因为网络卡顿,导致充值失败,玩家既没有花费金钱也没有得到游戏道具

那么在这么一个充皮肤的例子中就有俩个操作:①玩家通过微信支付向游戏厂商进行充值操作 ②游戏厂商将皮肤或者点券发放给玩家。将上述俩种结果的情况转化为逻辑关系则:

  • ①成功,并且②也成功
  • ①失败,并且②也失败

作为玩家,我们不希望充值后没有得到应有的奖励(①成功,但②失败);作为游戏厂商,我们不希望玩家不消费就获得充值道具(①失败,但②成功)。对于这样充值皮肤的过程,我们就可以将其理解成为一个事务,要么①成功,并且②也成功;要么①失败,并且②也失败,总之俩个操作必须同时成功或者同时失败。

事务的应用遍布在生活中的各处,诸如银行转账、秒杀下单...

作为开发者,通过事务的处理来保证数据的一致性以及用户的体验是必不可少的,在MySQL中我们也常常用到事务的处理

-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;

那么在Spring中如何使用事务呢?

二.Spring中事务的实现

Spring中对于事务也进行了实现,Spring中的事务操作分为俩类:

  • 编程式事务:手动写代码操作事务
  • 声明式事务:利用注解自动开启和提交事务

编程式事务

Spring⼿动操作事务和MySQL 操作事务类似,我们可以通过以下几个操作来完成

  • 开启事务(获取事务)
  • 提交事务
  • 回滚事务

SpringBoot中内置了两个对象用来创建事务:DataSourceTransactionManager 和 TransactionDefinition

  • DataSourceTransactionManager 事务管理器,⽤来获取事务(开启事务),提交或回滚事务
  • TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus

通过依赖诸如的方式将上述俩个对象注入进来后,通过事务管理器对象dataSourceTransactionManagergetTransaction()方法并且将事务属性对象transactionDefinition作为参数传入就可以获取到一个事务对象,即默认此时开启了事务,当我们需要提交或回滚事务的时候就可以通过事务管理器对象的commit()roolback()方法实现,代码如下:

@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;

public String registry(String name,String password){
    // 开启事务
    TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
    //业务逻辑 用户注册
    userService.registryUser(name,password);
    //提交事务
    dataSourceTransactionManager.commit(transactionStatus);
    //回滚事务
    //dataSourceTransactionManager.rollback(transactionStatus);
    return "注册成功";
}

以上是我们手动设置进行提交和回滚的方式,Spring另外还提供了通过注解来实现事务的方式,即声明式事务

声明式事务

声明式事务的实现很简单,只需要在需要事务的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动开启事务和提交事务,进入方法时⾃动开启事务,⽅法执⾏完成会⾃动提交事务,如果中途发⽣了没有处理的异常则会⾃动回滚事务。如下所示:

@Autowired
private UserService userService;

@Transactional
public String registry(String name,String password){
    //⽤⼾注册
    userService.registryUser(name,password);
    return "注册成功";
}

如果抛出了异常,那么整个方法就会自动回滚

@Autowired
private UserService userService;

@Transactional
public String registry(String name,String password){
    //⽤⼾注册
    userService.registryUser(name,password);
    log.info("⽤⼾数据插⼊成功");
    //强制程序抛出异常
    int a = 10/0;
    return "注册成功";
}

@Transactional 可以⽤来修饰⽅法或类,

  • 修饰⽅法时:只有修饰 public ⽅法时才⽣效(修饰其他⽅法时不会报错,但也不会⽣效)
  • 修饰类时:对 @Transactional 修饰的类中所有的 public ⽅法都⽣效

⽅法/类被 @Transactional 注解修饰时,在⽬标⽅法执⾏开始之前会⾃动开启事务,⽅法执⾏结束之后⾃动提交事务,如果在⽅法执⾏过程中出现异常且异常未被捕获,就进⾏事务回滚操作,如果异常被程序捕获,⽅法就被认为是成功执⾏,依然会提交事务。如下通过try...catch...将异常捕获并弹出异常信息。

@Autowired
private UserService userService;

@Transactional
public String registry(String name,String password){
    //⽤⼾注册
    userService.registryUser(name,password);
    log.info("⽤⼾数据插⼊成功");
     //对异常进⾏捕获
     try {
         //强制程序抛出异常
         int a = 10/0;
     }catch (Exception e){
         e.printStackTrace();
     }
    return "注册成功";
}

运⾏程序会发现虽然程序出错了,但是由于异常被捕获并且只是弹出了异常信息,并没有通过throw抛出该异常,所以事务依然得到了提交

反之如果想要实现事务的回滚,通过throw抛出异常即可

@Autowired
private UserService userService;

@Transactional
public String registry(String name,String password){
    //⽤⼾注册
    userService.registryUser(name,password);
    log.info("⽤⼾数据插⼊成功");
     //对异常进⾏捕获
     try {
         //强制程序抛出异常
         int a = 10/0;
     }catch (Exception e){
         //将异常重新抛出去
         throw e;
     }
    return "注册成功";
}

另外也可以自己手动设置回滚事务,使⽤ TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使⽤ setRollbackOnly 设置 setRollbackOnly

@Autowired
private UserService userService;

@Transactional
public String registry(String name,String password){
    //⽤⼾注册
    userService.registryUser(name,password);
    log.info("⽤⼾数据插⼊成功");
     //对异常进⾏捕获
     try {
         //强制程序抛出异常
         int a = 10/0;
     }catch (Exception e){
         // ⼿动回滚事务
         TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
     }
    return "注册成功";
}

三.@Transactional 详解

@Transactional 注解中有以下三个常⻅属性:

  1. rollbackFor: 异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型
  2. Isolation: 事务的隔离级别,默认值为 Isolation.DEFAULT
  3. propagation: 事务的传播机制,默认值为 Propagation.REQUIRED

▐ 异常回滚属性 rollbackFor

@Transactional默认只在遇到运行时异常Error时才会回滚,⾮运⾏时异常不回滚。即下图中蓝色部分的内容才会回滚。

如下,我们刻意抛出IOException,但是由于IOException不是运行时异常,也不是Error,则该事务仍然会正常提交事务,并不会进行回滚操作

@Autowired
private UserService userService;

@Transactional
public String registry(String name,String password) throws IOException {
    //⽤⼾注册
    userService.registryUser(name,password);
    log.info("⽤⼾数据插⼊成功");
    if(true) {
        throw new IOException();
    }
    return "注册成功";
}

如果我们希望其他的异常也会引起事务的回滚,那么通过rollbackFor属性来设置即可,如下就是对于所有异常的抛出都会引起事务的回滚。一旦我们按照下面这样编码,那么即使是抛出IO异常也会引起事务的回滚,因为IOException是继承于Exception的子类

@Autowired
private UserService userService;

@Transactional(rollbackFor = Exception.class)
public String registry(String name,String password) throws IOException {
    //⽤⼾注册
    userService.registryUser(name,password);
    log.info("⽤⼾数据插⼊成功");
    if(true) {
        throw new IOException();
    }
    return "注册成功";
}

 

▐ 事务隔离级别 Isolation

事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题

首先回顾一下SQL中的事务隔离级别,SQL 标准定义了四种隔离级别,且MySQL 全都⽀持,这四种隔离级别分别是:

1.读未提交(READ UNCOMMITTED):读未提交,也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的数据。

隐患:因为其他事务未提交的数据可能会发⽣回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读

2.读提交(READ COMMITTED):读已提交,也叫提交读。该隔离级别的事务能读取到已经提交事务的数据。

隐患:该隔离级别不会有脏读的问题,但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询可能会得到不同的结果,这种现象叫做不可重复读

3.可重复读(REPEATABLE READ):事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也可以确保同⼀事务多次查询的结果⼀致,并且可重复读是 MySQL 的默认事务隔离级别。

隐患:由于其他事务新插⼊的数据也是可以感知到的,这也就引发了幻读问题,⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因),明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这个现象叫幻读

4.串行化(SERIALIZABLE):序列化,事务最⾼隔离级别,它会强制事务排序,使之不会发⽣冲突, 从而解决了脏读、不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多

Spring中事务隔离级别有5种,相较于上述的SQL的事务隔离级别,多了一个DEFAULT级别,该级别表示当前连接的数据库是什么就用该数据库的事务隔离级别;比如我们连上MySQL,由于MySQL的默认级别是可重复读,那么在我们不做任何修改的情况下,Spring中的事务隔离级别就是可重复读。

  1. Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主
  2. Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中 READ UNCOMMITTED
  3. Isolation.READ_COMMITTED:读已提交,对应SQL标准中 READ COMMITTED
  4. Isolation.REPEATABLE_READ:可重复读,对应SQL标准中 REPEATABLE READ
  5. Isolation.SERIALIZABLE:串⾏化,对应SQL标准中 SERIALIZABLE
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
    private final int value;
    private Isolation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置

@Transactional(isolation = Isolation.READ_COMMITTED)

▐ 事务的传播机制 propagation

事务传播机制:多个事务⽅法存在调⽤关系时,控制事务如何在这些⽅法间进⾏传播的机制

⽐如有两个⽅法A、B都被 @Transactional 修饰,且A⽅法调⽤B⽅法。A⽅法运⾏时会开启⼀个事务,当A调⽤B时,B⽅法本⾝也有事务,当B⽅法运⾏时是加⼊A的事务,还是创建⼀个新的事务呢?

这就涉及到了事务的传播机制,事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题,而事务传播机制解决的是⼀个事务在多个⽅法中传递的问题

@Transactional 注解⽀持事务传播机制的设置,通过 propagation 属性来指定传播⾏为

Spring 事务传播机制有以下 7 种:

  1. Propagation.REQUIRED:默认的事务传播级别。如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
  3. Propagation.MANDATORY:如果当前存在事务,则加⼊该事务;如果当前没有事务, 则抛出异常(强制性)
  4. Propagation.REQUIRES_NEW:不管当前有没有事务都要创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
  5. Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起(不⽤)
  6. Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED : 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
    private final int value;
    private Propagation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}

上文中的描述可能有些枯燥,我们可以用一对新人买房的实况来理解事务传播机制:

  • Propagation.REQUIRED : 需要有房⼦,如果你有房,我们就⼀起住;如果你没房,我们就⼀起买房。(如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则创建⼀个新的事务)
  • Propagation.SUPPORTS : 可以有房⼦,如果你有房,那就⼀起住;如果没房,那就租房. (如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以⾮事务的⽅式继续运⾏)
  • Propagation.MANDATORY : 如果你有房,我们就⼀起住;如果没房就离婚 (如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则抛出异常)
  • Propagation.REQUIRES_NEW : 必须买新房,不管你有没有房,必须要两个⼈⼀起买房,即使有房也不住。 (创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起)
  • Propagation.NOT_SUPPORTED : 不需要房,不管你有没有房,我都不住,必须租房。(以⾮事务⽅式运⾏,如果当前存在事务, 则把当前事务挂起)
  • Propagation.NEVER : 不能有房⼦。 (以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常)
  • Propagation.NESTED : 如果你没房,就⼀起买房;如果你有房,就在这个房子里面再修一个养宠物的小房子。(如果如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏. 如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED )



 本次的分享就到此为止了,希望我的分享能给您带来帮助,创作不易也欢迎大家三连支持,你们的点赞就是博主更新最大的动力!如有不同意见,欢迎评论区积极讨论交流,让我们一起学习进步!有相关问题也可以私信博主,评论区和私信都会认真查看的,我们下次再见

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

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

相关文章

CORS解决前端跨域案例学习

跨域的概念不再解释&#xff0c;直接演示下出现跨域的情况&#xff1a; 前端代码&#xff08;index.html&#xff09;: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" conten…

comfyUI工作流也能变现了,“SD变现宝”把工作流转为小程序,重塑内容创作者的商业之路

前言 在数字化浪潮的推动下&#xff0c;内容创作行业正经历着前所未有的变革。如何在这个充满竞争与机遇的时代中脱颖而出&#xff0c;成为每个创作者必须面对的挑战。 SD变现宝&#xff0c;作为ComfyUI的最新插件&#xff0c;凭借其独特的功能与优势&#xff0c;为创作者们开…

赋能未来制造:三品图文档管理软件在大连船推图文档管理中的深度应用与成效

在信息化浪潮席卷全球的今天&#xff0c;企业的研发管理能力已经成为衡量其核心竞争力的重要标尺。三品软件与大连船用推进器有限公司携手合作&#xff0c;成功实施了EDM图文档协同管理系统项目&#xff0c;为企业在激烈的市场竞争中提供强有力的支持&#xff0c;确保其始终处于…

RCE绕过练习

一.了解eval与assert eval与assert区别_eval assert-CSDN博客https://blog.csdn.net/qq_53568983/article/details/129782507 看了php官方文档,assert中提到的许多名词不明白,转而搜索文章,这篇是解释的是最直白的 其中提到eval不是一个函数,是语言构造器,不能被可变函数调用…

Git代码管理规范

1. 简介 git 分支分为集成分支、功能分支和修复分支&#xff0c;分别命名为 develop、feature 和 hotfix&#xff0c;均为单数。不可使用 features、future、hotfixes、hotfixs 等错误名称。 master&#xff08;主分支&#xff0c;永远是可用的稳定版本&#xff0c;不能直接在…

数据中台运营与实战案例集锦(125页PPT)

方案简介&#xff1a; 本篇通过理论讲解与实战案例相结合的方式&#xff0c;深入剖析了数据中台的概念、架构、关键技术、实施路径以及运营策略。内容覆盖从数据中台规划到落地的全过程&#xff0c;包括数据治理、数据资产管理、数据服务化、数据分析与挖掘、以及如何通过数据…

PythonStudio 控件使用常用方式(二十六)TProgressBar

PythonStudio是一个极强的开发Python的IDE工具&#xff0c;官网地址是&#xff1a;https://glsite.com/ &#xff0c;在官网可以下载最新版的PythonStudio&#xff0c;同时&#xff0c;在使用PythonStudio时&#xff0c;它也能及时为用户升到最新版本。它使用的是Delphi的控件&…

MySQL:复杂查询(一)——聚合函数分组查询联合查询01

目录 1、聚合查询 1.1 聚合函数 1.1.1 COUNT() 1.1.2 SUM() 1.1.3 AVG() 1.1.4 MAX()&#xff0c;MIN() 1.2 分组查询 1.2.1 GROUP BY子句 1.2.1.1 round() 1.2.2 HAVING 1.2.3 示例 2、联合查询&#xff08;表连接查询&#xff09; 2.1 内连接 2.1.1 ①取相关表笛…

机器学习——全连接(MLP多层感知机)的理解

全连接即是矩阵乘&#xff0c;因此在transformer中获取QKV理论上是输入与QKV权重矩阵相乘&#xff0c;但实际操作则是使用全连接即nn.Linear()&#xff0c;注意这里的输入和输出都是二维的[batch,d_model]&#xff0c;即每个样本是一维的。

【Echarts】custom自定义图表实现甘特图

效果图 主要注意点&#xff1a; 1、右上角图例visualMap实现 2、visualMap增加formatter 3、series使用custom自定义图表&#xff0c;encode解析四维数组。核心是renderItem方法&#xff0c;必填项&#xff0c;且需要注意要全部定义在options里面&#xff01;&#xff01;&…

程序员如何平衡日常编码工作与提升式学习?

程序员的两难&#xff1a;如何平衡日常编码与持续学习 在科技日新月异、更新迭代迅速的编程世界中&#xff0c;程序员面临的一个重要挑战是如何在繁忙的日常编码工作和持续的专业提升之间找到平衡。是否应当在工作时间全身心投入到项目推进中&#xff0c;还是应该抽出时间学习…

第38篇 冒泡排序<二>

Q&#xff1a;如何设计C语言程序对数组进行降序排列&#xff1f; A&#xff1a;基本原理&#xff1a;通过不断的比较和交换数组中的数据元素&#xff0c;最终使得最大的数据“冒泡”排到到数组最末&#xff0c;并逐步缩小待排序的范围直到所有数据都排列正确位置。首先定义简单…

行业大模型:信用评分大模型、生产优化大模型、库存管理大模型、物流行业大模型、零售行业大模型

金融行业大模型&#xff1a;信用评分大模型 信用评分模型在金融行业中扮演着至关重要的角色&#xff0c;它通过对个人或企业的信用状况进行评估&#xff0c;帮助金融机构有效控制风险&#xff0c;提高业务效率。以下是信用评分模型的特点及案例介绍&#xff1a; 信用评分模型…

git放弃本地add/commit

git放弃本地add/commit 还未添加add的情况已经执行git add缓存了的&#xff1a;可以用命令 还未添加add的情况 # 放弃某个文件git checkout <filename># 放弃所有文件git checkout .已经执行git add缓存了的&#xff1a;可以用命令 git reset HEAD filepathname &#x…

【开端】如何高效记录并整理编程学习笔记

如何高效记录并整理编程学习笔记&#xff1f; 在编程学习的海洋中&#xff0c;高效的笔记记录和整理方法就像一张珍贵的航海图&#xff0c;能够帮助我们在浩瀚的知识中找到方向。如何建立一个既能快速记录又易于回顾的笔记系统&#xff1f;如何在繁忙的学习中保持笔记的条理性…

巴黎奥运会中国奖牌数据分析

目录 一、每个比赛日奖牌变化数据分析 二、奖牌项目占比数据分析 2.1金牌中项目占比分析 2.2 银牌中项目占比分析 2.3 铜牌中项目占比分析 2.4 奖牌总数中项目占比分析 在巴黎奥运会上&#xff0c;中国队的表现可谓亮眼&#xff0c;各项比赛日的奖牌总数和不同项目的奖牌…

【软件测试】功能测试理论基础

目录 项目的测试流程&#x1f3f4; 需求评审 评审形式 测试人员在需求评审中职责 测试计划与方案 测试计划 问题 测试方案&#x1f3f4; 测试计划与方案的对比 功能测试设计&#x1f3f4; 测试设计的步骤 项目的测试流程&#x1f3f4; 作用&#xff1a; 有序有效开展…

js 深拷贝、浅拷贝深度解析

赋值操作&#xff1a; let obj{a:1,b:[1,2,3],c:{m:2}}let newObjobjnewObj.a2newObj.b.push(4)newObj.c.m3console.log(obj,newObj); 将一个对象赋值给一个变量&#xff0c;其实就是将这个对象在栈内存中的引用地址复制给了这个变量&#xff0c;这两个对象指向堆内存中的同一个…

基于SpringBoot+Vue的产业园区智慧公寓管理系统(带1w+文档)

基于SpringBootVue的产业园区智慧公寓管理系统(带1w文档) 基于SpringBootVue的产业园区智慧公寓管理系统(带1w文档) 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包…

Multisim 用LM358 运放模拟线性稳压器 - 运放输出饱和 - 前馈电容

就是拿运放搭一个可调的LDO 稳压器&#xff0c;类似下面这个功能框图里的感觉。本来应该非常简单&#xff0c;没什么好说的&#xff0c;没想到遇到了两个问题。 原理 - 理想运放 我用PNP 三极管Q2 作为输出&#xff0c;运放输出电压升高时&#xff0c;流过PNP 三极管BE 的电流变…