Spring 事务(事务、声明式事务@Transactional、事务隔离级别、事务传播机制)

news2024/11/25 20:36:39

目录

1.事务的定义

2.Spring中事务的实现

2.1 MySQL中的事务使用

2.2 Spring中编程事务的实现

2.3 Spring中声明式事务

2.3.1 声明式事务的实现 @Transactional

2.3.2 @Transactional 作用域

2.3.3 @Transactional 参数说明

2.3.4 注意事项

(1)解决方法1(将异常抛出)

(2)解决方法2(使用代码手动回滚事务)

2.3.5 @Transactional 工作原理

3. 事务隔离级别

3.1 事务特性

3.2 Spring 中设置事务隔离级别

MySQL 事务隔离级别

Spring 事务隔离级别(5种)

注意事项:

4. Spring事务传播机制

4.1 事务传播机制是什么

4.2 为什么需要事务传播机制

4.3 事务传播机制有哪些

 4.4 Spring 事务传播机制使用

4.4.1 支持当前事务(REQUIRED 默认)

4.4.2 嵌套事务(NESTED)

4.4.5 嵌套事务和REQUIRED事务的区别


1.事务的定义

事务定义:将一组操作封装成一个执行单元(封装到一起),要么一起成功,要么一起失败

那么我们为什么要用事务呢?

比方说银行的转账操作

  1. A账户转账100:-100
  2. B账户接收100:+100

如果没有事务,在A账户转账成功后,出现了失误和问题,B账户并没有加100,那么A账户便无故损失了100,如果使用了事务,当出现了失误和问题后便会立刻回滚给A账户,即要么一起成功,要么一起失败。


2.Spring中事务的实现

Spring中的事务操作分为两种:

  1. 编程式事务(通过代码的方式实现事务)
  2. 声明式事务(通过注释的方式实现声明事务)

2.1 MySQL中的事务使用

MySQL 中事务有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:

-- 开启事务
start transaction;

-- 业务执⾏
-- 提交事务
commit;

-- 回滚事务
rollback;

2.2 Spring中编程事务的实现

Spring 手动操作事务和上述的 MySQL 操作事务类似,他也是有三个操作步骤:

  1. 开启事务(获取事务)
  2. 提交事务
  3. 回滚事务
SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将
TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus,实现代码如下:
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    // 在此方法中使用编程式的事物
    @RequestMapping("/add")
    public int add(UserInfo userInfo) {
        // 非空效验【验证用户名和密码不为空】
        if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
                || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 开启事务(获取事务)
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        int result = userService.add(userInfo);
        System.out.println("add 受影响的行数:" + result);
        // 提交事务
        transactionManager.commit(transactionStatus);
//        // 回滚事务
//        transactionManager.rollback(transactionStatus);
        return result;
    }
}

因为回滚了事务,所以数据库中不会有wangwu这个用户

从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。


2.3 Spring中声明式事务

2.3.1 声明式事务的实现 @Transactional

声明式事务的实现,只需要在方法上添加 @Transactional 注解就可以实现,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完全会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

// 声明式事务(自动提交)
@Transactional
@RequestMapping("/add2")
public int add2(UserInfo userInfo) {
    if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
            || !StringUtils.hasLength(userInfo.getPassword())) {
        return 0;
    }
    int result = userService.add(userInfo);
    System.out.println("add2 受影响的行数:" + result);
    int num = 10/0;
    return result;
}

 我们可以看到在发生异常后事务会自动回滚,所有我们在数据库中无法找寻到添加数据,

发现数据库中无新增数据,这说明@Transactional生效了

2.3.2 @Transactional 作用域

@Transactional  可以用来修饰方法和类

  • 修饰方法时:注意只能应用在 public 方法上,否则不生效
  • 修饰类时:表明该注释对类中的所有 public 方法生效

2.3.3 @Transactional 参数说明

参数作用
value当你配置多个事务管理器时,可以使用该属性指定选择用哪个事务管理器
transactionManager同上
propagation事务的传播行为,默认值为 Propagation.REQUIRED
isolation事务的隔离级别,默认值为 Isolation.DEFAULT
timeout事务的超时时间,默认值为-1,如果超过该时间限制但事务还没完成,则自动回滚事务
readOnly指定事务是否为只读事务,默认值为 false,为了忽略那些不需要事务的方法,比如读取数据可以设置 read-only 为 true
rolibackFor用于指定能够触发事务回滚的异常类型,可以指定多个异常类型
rolibackForClassName同上
noRolibackFor抛出指定的异常类型,不回滚事务,也可以指定多个异常类型
noRollbackForClassName同上

2.3.4 注意事项

@Transactional 在异常被捕获的情况下,不会进行事务自动回滚

@Transactional
@RequestMapping("/add2")
public int add3(UserInfo userInfo) {
    if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
            || !StringUtils.hasLength(userInfo.getPassword())) {
        return 0;
    }
    int result = userService.add(userInfo);
    System.out.println("add2 受影响的行数:" + result);
    try {
        int num = 10/0;
    } catch (Exception e) {

    }
    return result;
}

这样是把异常打印出来了,且数据库中也添加进去了,但是出现了异常应该回滚才对的!

(1)解决方法1(将异常抛出)

2)解决方法2(使用代码手动回滚事务)

手动回滚事务,在⽅法中使⽤ TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码

    @Transactional
    @RequestMapping("/add3")
    public int add3(UserInfo userInfo) {
        if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
                || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        int result = userService.add(userInfo);
        System.out.println("add2 受影响的行数:" + result);
        try {
            int num = 10/0;
        } catch (Exception e) {
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

2.3.5 @Transactional 工作原理

@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。 @Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。

@Transactional 实现思路:

 

@Transactional 具体执行细节:

 


3. 事务隔离级别

3.1 事务特性

事务有四大特性(ACID):原子性,持久性,一致性,隔离性

  • 原子性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态就像这个 事务从来没有执行过⼀样。
  • 持久性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精准度、串联性以及后续数据库可以自发性地完成预定的工作
  • 一致性:事务处理结束后,对数据的修改就是永久的。即使系统故障也不会丢失
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由交叉执行而导致数据的不一致。

而这 4 种特性中,只有隔离性(隔离级别)是可以设置的


3.2 Spring 中设置事务隔离级别

在设置事务隔离级别前我们想一下,为什么要设置事务隔离级别呢?

设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的

这个可控表示的是,比如疫情的时候,有确诊、密接、次密接等针对不同的人群,采取不同的隔离级别,这种方式与事务的隔离级别类似,都是让某种行为操作变的 更可控事务的隔离级别就是为了防止,其他事务影响当前事务执行的一种策略

MySQL 事务隔离级别

  1. 读未提交(READ UNCOMMITTED):事务A在读取数据的时候读取到了事务B尚未提交的数据,但是在过一会后事务B对A读取到的数据进行回滚了,那么A读取的数据就是脏读,读未提交侧重于查询,既然有了脏读,那么也肯定有不可重复读和幻读的问题
  2. 读已提交(READ COMMITTED):针对上面脏读的问题来解决的,事务A读到了事务B已经提交的数据,然后过了一会事务B将提交的数据进行修改了,此时事务A又读了一次B的数据,发现两次读到读到数据不一样,这个就叫不可重复读的问题,读已提交侧重的是修改,还是存在不可重复读和幻读的问题
  3. 可重复读(REPEATABLE READ) :针对的是上面不可重复读的问题,事务A此时查询数据发现表中只有一条数据,然后事务B又过来拆台了,事务B又插入了一条数据,事务A再次查询发现,哎!我出现幻觉了吗,刚刚不是只有一条数据么,现在咋又变成了两条数据了,是出现幻觉了吗,这个问题就叫 幻读,可重复读侧重的是添加和删除,只存在幻读的问题了
  4. 串行化(SERIALIZABLE):事务最高的隔离级别,解决了脏读、不可重复读、幻读的问题,但这个级别执行效率低
事务隔离级别脏读不可重复读幻读
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITTED)
可重复读(REPEATABLE READ)
串行化(SERIALIZABLE)

Spring 事务隔离级别(5种)

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。

注意事项:

  1. 当 Spring 中设置了事务隔离级别和连接的数据库(MySQL)事务隔离级别发送冲突的时候,以Spring为准
  2. Spring 中的事务隔离级别机制的实现是依靠连接数据库支持事务隔离级别为基础

4. Spring事务传播机制

4.1 事务传播机制是什么

Spring 事务传播机制:多个事务在相互调用时,事务是如何传递的

4.2 为什么需要事务传播机制

事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性(稳定性的)

而事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题,如下图所示:

 

4.3 事务传播机制有哪些

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

  1.     Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。
  2.     Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3.     Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4.     Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  5.     Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  6.     Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  7.     Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED
     
以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:

 

 4.4 Spring 事务传播机制使用

4.4.1 支持当前事务(REQUIRED 默认)

UserController:

@Autowired
    private UserService userService;
@Autowired
    private LogService logService;
 
@Transactional// 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // 非空效验
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
                !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 添加用户
        int result = userService.add(userInfo);
        if (result>0){
            //日志
            logService.add();
        }
        /*try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            *//*throw e;*//*
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }*/
        return result;
    }

UserService:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
 
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer add(UserInfo userInfo) {
        int result = userMapper.add(userInfo);
        System.out.println("用户添加:" + result);
        return result;
    }
 
}

LogService :

@RestController
public class LogService {
    //捣乱,设置一个算数异常
    @Transactional(propagation = Propagation.REQUIRED)
    public int add() {
        int num = 10 / 0;
        return 1;
    }
}

测试REQUIRED我们可以看到logservice中是有错误的,我们先添加了userService.add(userinfo)事务,当我们加入了log service事务出问题了,虽然之前已经添加成功了,但是应该也是回滚,如果都回滚那么就是ok的即add2没有添加数据库那么说明符合预期,当数据成功添加到数据库是就代表出错了

 发现回滚了,这个就是加入事务,将自身变成整体的一部分,自身出错了那么整体就会出问题

执行流程描述:

  1. UserService中的保存方法正常执行
  2. LogService 保存日志程序报错,因为使用的是 Controller 中的事务,所以整个事务回滚。
  3. 数据库中没有插入任何数据,也就是步骤1中的用户插入方法也回滚了。

4.4.2 嵌套事务(NESTED)

UserService:

@Controller
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NESTED)
    public Integer add(Userinfo userinfo){
        int result = userMapper.add(userinfo);
        System.out.println("用户添加:" + result);
        return result;
    }
}

UserController:

 @Transactional(propagation = Propagation.NESTED)// 声明式事务(自动提交)
    @RequestMapping("/add2")
    public Integer add2(Userinfo userinfo){
        // 非空校验
        if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername())
                || !StringUtils.hasLength(userinfo.getPassword())) {
            return 0;
        }
        int result = userService.add(userinfo);
        if (result > 0){
            logService.add();
        }
        //        System.out.println("add2:" + result);
        return result;
    }

LogService:

@Service
public class LogService {
    @Transactional(propagation = Propagation.NESTED)
//    @Transactional = @Transactional(propagation = Propagation.REQUIRED)
//    @Transactional默认的是Propagation.REQUIRED
    public int add(){
        int num = 10/0;
        return 1;
    }
}

最终执行结果,用户表和日志表都没有添加任何数据。

发现嵌套事务也是没有问题的

但是当我们去让其手动回滚时:

LogService:

@Service
public class LogService {
    @Transactional(propagation = Propagation.NESTED)
//    @Transactional = @Transactional(propagation = Propagation.REQUIRED)
//    @Transactional默认的是Propagation.REQUIRED
    public int add(){
        try {
            int num = 10/0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return 1;
    }
}

 

没有报错且添加成功没有回滚

这就是嵌套事务的特点,相当于logservice是临时工,出事了开了就行了,不会影响到整体


 

4.4.5 嵌套事务和REQUIRED事务的区别

  1. 整个事务如果全部执行成功,二者的结果是⼀样的。

  2. 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果

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

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

相关文章

html学习第2篇---标签(1)

html学习第2篇---标签 1、标题标签h1---h62、段落标签p3、换行标签br4、文本格式化标签5、div标签和span标签6、图像标签img6.1、图像属性6.2、相对路径、绝对路径 7、超链接标签a7.1、属性7.2、分类 8、注释标签和特殊字符8.1、注释8.2、特殊字符 1、标题标签h1—h6 为了使网…

【java安全】FastJson反序列化漏洞浅析

文章目录 【java安全】FastJson反序列化漏洞浅析0x00.前言0x01.FastJson概述0x02.FastJson使用序列化与反序列化 0x03.反序列化漏洞0x04.漏洞触发条件0x05.漏洞攻击方式JdbcRowSetImpl利用链TemplatesImpl利用链**漏洞版本**POC漏洞分析 【java安全】FastJson反序列化漏洞浅析 …

网络丢包故障如何定位?如何解决?

引言 本期分享一个比较常见的网络问题--丢包。例如我们去ping一个网站,如果能ping通,且网站返回信息全面,则说明与网站服务器的通信是畅通的,如果ping不通,或者网站返回的信息不全等,则很可能是数据被丢包了…

java8:HashMap的实现原理

一概述 这个哈希表是基于 Map 接口的实现的,它允许 null 值和null 键,它不是线程同步的,同时也不保证有序。 Map 的这种实现方式为 get(取)和 put(存)带来了比较好的性能。但是如果涉及到大量的…

C++入门:内联函数,auto,范围for循环,nullptr

目录 1.内联函数 1.1 概念 1.2 特性 1.3 内联函数与宏的区别 2.auto关键字(C11) 2.1 auto简介 2.2 auto的使用细则 2.3 auto不能推导的场景 3.基于范围的for循环(C11) 3.1 范围for的语法 3.2 范围for的使用方法 4.指针空值nullptr(C11) 4.1 C98中的指针空值 1.内联…

开悟Optimization guide for intermediate tracks

目录 认识模型 参考方案(按模块拆解) 认识模型 模型控制1名英雄进行镜像1 v 1对战 Actor集群资源为64核CPU 问题特点:单一公平对抗场景(同英雄镜像对赛),单位时间样本产能低,累计训练资源相…

macOS - 安装 Python 及地址

文章目录 Python 官方安装包Pip3Applications - PythonMiniconda多个python环境有多种方式安装 python,比如 Python 官方包、anaconda、miniconda、brew 等 这里记录使用 Python 官方包进行安装,和 miniconda 安装方式,以及安装后 各执行文件、安装包的地址。 明确这些地址后…

Arduino开发Seeed Studio XIAO RP2040

前言 准备一些硬件设备 Seeed Studio XIAO RP2040 一块电脑——window 或 Mac 一台Type-C数据线 某些USB线只支持充电,无传输数据功能。 连接电脑 按住boot按钮,然后将 Seeed Studio XIAO RP2040 连接到 PC。 2. 如果电脑文件管理器上显示了“RPI-RP2…

一生一芯9——ubuntu22.04安装valgrind

这里安装的valgrind版本是3.19.0 下载安装包 在选定的目录下打开终端,输入以下指令 wget https://sourceware.org/pub/valgrind/valgrind-3.19.0.tar.bz2直至下载完成 解压安装包 输入下面指令解压安装包 tar -xvf valgrind-3.19.0.tar.bz2.tar.bz2注&#xf…

大转盘抽奖活动设计完全指南,轻松打造火爆营销

在如今竞争激烈的商业环境中,如何吸引顾客、提升销售额成为了每个商家都必须面对的问题。而大转盘抽奖活动作为一种互动性强、刺激性高的推广方式,成为了越来越多商家的首选。本文将详细介绍如何通过乔拓云后台制作大转盘抽奖活动,助力商家的…

高压功率放大器在管道损伤检测中的应用有哪些

高压功率放大器管道损伤检测中有着广泛的应用。管道是现代社会中重要的基础设施之一,用于输送各种液体或气体。然而,由于外部因素或长时间使用引起的磨损、腐蚀或撞击等问题,管道可能出现损伤,这可能对环境和人员安全产生严重影响…

【ag-grid-vue】基本使用

ag-grid是一款功能和性能强大外观漂亮的表格插件,ag-grid几乎能满足你对数据表格所有需求。固定列、拖动列大小和位置、多表头、自定义排序等等各种常用又必不可少功能。关于收费的问题,绝大部分应用用免费的社区版就够了,ag-grid-community社…

axios 进阶

axios 进阶 接口传参方式 使用 xhr 原生技术或者是 axios 时,它的 post 传参方式是键值对的形式 keyvalue。但是在实际开发中一般是使用对象的形式定义数据,方便读取和赋值。所以当我们需要发起请求时可以通过 qs 这一款插件将对象转成键值对形式&…

221. 最大正方形 Python

文章目录 一、题目描述示例 1示例 2示例 3 二、代码三、解题思路 一、题目描述 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。 示例 1 输入:matrix [["1","0","1","0&q…

视频集中存储/云存储平台EasyCVR国标GB28181协议接入的报文交互数据包分析

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。视频汇聚融合管理…

loss.sum.backward()为什么要sum()?

在动手学深度学习中,这样解释的: 当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的y和x,求导的结果可以是一个高阶张量。 然而,虽然这些更奇特的对象确实出现在高级机器学习中&#xff…

TypeScript初体验

1.安装编译TS工具包 npm i -g typescript 2. 查看版本号 tsc -v 3.创建ts文件 说明:创建一个index.ts文件 4.TS编译为JS tsc index.ts 5.执行JS代码 node index.js 6.简化TS的步骤 6.1安装 npm i -g ts-node 6.2执行 ts-node index.ts

PL端案例开发手册

目 录 前 言 1 工程编译、程序加载方法 1.1 工程编译 1.2 程序加载 2 led-flash 2.1 案例说明 2.2 操作说明 2.3 关键代码 更多帮助 前 言 本文主要介绍PL端案例的使用说明,适用开发环境:Windows 7/10 64bit、Xilinx Unified 20…

SpringDataRedis 使用

1. SpringDataRedis 特点2. 使用 SpringDataRedis 步骤3. 自定义 RedisTemplate 序列化4. SpringDataRedis 操作对象 1. SpringDataRedis 特点 提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis)提供了 RedisTemplate 统一 API 来操作 Redis支持 Redi…

Python实现企业微信群告警

Python实现企业微信告警 1. 创建企业微信群机器人 1-1. 什么是企业微信群机器人? 企业微信群机器人是企业微信平台提供的一种功能,可以通过Webhook方式将消息发送到指定的企业微信群中。它可以用于自动化发送通知、告警等信息,实现监控和信…