SpringBoot之Transactional事务

news2024/11/29 4:38:05

目录

  • 一、事务管理方式
  • 二、事务提交方式
  • 三、事务隔离级别
  • 四、事务传播行为
    • 1、Propagation.REQUIRED
    • 2、Propagation.SUPPORTS
    • 3、Propagation.MANDATORY
    • 4、Propagation.REQUIRES_NEW
    • 5、Propagation.NOT_SUPPORTED
    • 6、Propagation.NEVER
    • 7、Propagation.NESTED
  • 五、事务回滚
  • 六、只读事务
  • 七、解决Transactional注解不回滚
    • 1、检查你方法是不是public的
    • 2、检查是不是通过类内部调用事务方法
    • 3、检查抛出的异常类型是不是unchecked异常
    • 4、数据库引擎是否支持事务
    • 5、异常是不是被你catch住了
    • 6、新开启一个线程


一、事务管理方式

在Spring中,事务有两种实现方式:编程式事务管理声明式事务管理

  • 编程式事务管理:编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,Spring推荐使用 TransactionTemplate

  • 声明式事务管理:建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务管理 不需要入侵代码,通过注解 @Transactional 就可以进行事务操作,更快捷而且简单,推荐使用。


二、事务提交方式

默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。

对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,Spring会将底层连接的自动提交特性设置为false。也就是在使用Spring进行事物管理的时候,Spring会将是否自动提交设置为false,等价于JDBC中的 connection.setAutoCommit(false);,在执行完之后在进行提交 connection.commit();


三、事务隔离级别

隔离级别:指若干个并发的事务之间的隔离程度@Transactional 注解的 isolation 属性,可用来设置隔离级别。默认值为 Isolation.DEFAULT。Isolation枚举了多种隔离级别:

隔离级别说明
Isolation.DEFAULT这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是 TransactionDefinition.ISOLATION_READ_COMMITTED
Isolation.READ_UNCOMMITTED该隔离级别表示 一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
Isolation.READ_COMMITTED该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
Isolation.REPEATABLE_READ该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
Isolation.SERIALIZABLE所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

四、事务传播行为

事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。@Transactional 注解的 propagation 属性可用来设置事务传播行为,默认值为 Propagation.REQUIREDPropagation 枚举了多种事务传播模式,我们以一个简单的例子来说明事务是如何传播的。

public class ServiceA {
	
    public void methodA() {
        ...
        serviceB.methodB();
        ...
    }
}
...
public class ServiceB {
    // 通过propagation属性指定methodB方法的事务传播行为
    @Transactional(propagation = ...)
    public void methodB() {
        ...
    }
}

上面的代码中指定 methodB() 方法的不同事务传播属性值,根据 methodA() 是否开启事务,来说明 methodB() 方法的事务如何传播的。


1、Propagation.REQUIRED

业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。

在这里插入图片描述


2、Propagation.SUPPORTS

该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

在这里插入图片描述


3、Propagation.MANDATORY

该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,将抛出IllegalTransactionStateException异常,此时不仅 methodB() 方法无法得到执行,也会打断 methodA() 方法的执行流程,除非在 methodA() 方法中捕获处理该异常。

在这里插入图片描述


4、Propagation.REQUIRES_NEW

不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

在这里插入图片描述


5、Propagation.NOT_SUPPORTED

声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

在这里插入图片描述


6、Propagation.NEVER

该方法绝对不能在事务范围内执行。如果在,则抛出 IllegalTransactionStateException 异常,此时不仅 methodB() 方法无法得到执行,还会打断 methodA() 方法的执行流程,甚至导致 methodA() 方法发生回滚。

在这里插入图片描述


7、Propagation.NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

例如:

  • 一旦 methodA() 方法进行回滚,则 methodB() 方法也会进行回滚。但由于该传播行为是通过数据库事务的保存点进行实现的,那么一旦 methodB() 方法抛出异常发生回滚。

  • 如果 methodA() 方法捕获了 methodB() 方法所抛出的异常,则 methodA() 就不会因此而回滚;而 methodA() 方法如果继续向上抛出异常则其也会被回滚。

在这里插入图片描述


五、事务回滚

@Transactional 注解默认只会对 unchecked异常进行回滚checked异常则不回滚

  • unchecked异常:RuntimeException(比如空指针,1/0)、Error及其子类;

  • checked异常:继承自java.lang.Exception得异常,如IOException、TimeoutException等;

如果期望对检查异常进行回滚,可通过 rollbackForrollbackForClassName 属性添加新的回滚条件:

// 方式1: 支持对所有异常类型进行回滚
@Transactional(rollbackFor = Exception.class)
// 方式2:支持对所有异常类型进行回滚
@Transactional(rollbackForClassName = {"Exception"})

类似地,还可以排除某些异常,使之不发生回滚:

// 方式1: 抛出ArithmeticException异常不进行回滚
@Transactional(noRollbackForClassName = {"ArithmeticException"} )
// 方式2: 抛出ArithmeticException异常不进行回滚
@Transactional(noRollbackFor = {ArithmeticException.class} )

六、只读事务

@Transactional 注解的 readOnly 属性默认为false,如需只读事务可将其配置为true。在只读事务中不允许执行读之外操作,否则事务将会回滚。

@Transactional(readOnly=true)

启动事务会增加线程开销,数据库因共享读取而锁定(具体跟数据库类型和事务隔离级别有关)。通常情况下,仅是读取数据时,不必设置只读事务而增加额外的系统开销


七、解决Transactional注解不回滚

1、检查你方法是不是public的

IDEA 直接会给出提示 Methods annotated with ‘@Transactional’ must be overridable ,原理很简单,@Transactional 注解只能应用到 public 可见度的方法上。 如果应用在 protectedprivate 或者 package 可见度的方法上,也不会报错,不过事务设置不会起作用。

@Transactional
private void deleteUser() throws MyException{
    userMapper.deleteUserA();
    userMapper.deleteUserB();
}

2、检查是不是通过类内部调用事务方法

在A类内部通过一个普通方法 methodA() 调用事务方法 methodB(),那么 methodB() 的事务会生效么?

public class A {
    public void methodA() {
        ...
        methodB();
        ...
    }

    @Transactional
    public void methodB() {
        ...
    }
}

答案是 ,原因很简单。

这里我们将 Spring AOP 后的动态代理类 ProxyA 用伪代码的形式给出,如下所示。可以看到,虽然动态代理类 ProxyA 中的 methodB() 方法被加入了事务切面,但事实上调用 ProxyAmethodA() 方法后,会直接进入目标类A中,即执行 a.methodA() 方法,然后直接调用A类中的methodB() 方法。换言之,methodB() 方法没有通过代理类 ProxyA 进行调用,自然其事务注解不会生效。

public class ProxyA {

    private A a = new A();

    public void methodA() {
        // 执行目标方法
        a.methodA();
    }

    public void methodB() {
        // 前置增强
        ...
        // 执行目标方法
        a.methodB();
        // 后置增强
        ...
    }
}

即使在A类的 methodA() 上也添加 @Transactional 事务注解,methodB() 方法由于没走代理类ProxyA,故 methodB() 方法依然还是使用 methodA() 方法的事务。即使将 methodB() 方法的传播行为设置为 Propagation.REQUIRES_NEW,也不会重新开启一个新的事务。因为 methodB() 方法连 @Transactional 注解都无法生效,设置传播行为更是无任何意义。

3、检查抛出的异常类型是不是unchecked异常

异常虽然抛出了,但是抛出的是非RuntimeException类型的异常,依旧不会生效。

@Transactional
public void deleteUser() throws MyException{
    userMapper.deleteUserA();
    try {
        int i = 1 / 0;
        userMapper.deleteUserB();
    } catch (Exception e) {
        throw new MyException();
    }
}

如果我想check异常也想回滚怎么办,注解上面写明异常类型即可(指定了回滚异常类型为Exception)

@Transactional(rollbackFor=Exception.class) 

类似的还有norollbackFor,自定义不回滚的异常。


4、数据库引擎是否支持事务

如果是MySQL,注意表要使用支持事务的引擎,比如 InnoDB,如果是MyISAM,事务是不起作用的。


5、异常是不是被你catch住了

当异常被捕获后,并且没有再抛出,那么deleteUserA是不会回滚的。

@Transactional
public void deleteUser() {
    userMapper.deleteUserA();
    try {
        int i = 1 / 0;
        userMapper.deleteUserB();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

6、新开启一个线程

如下的方式 deleteUserA() 也不会回滚,因为spring实现事务的原理是通过 ThreadLocal 把数据库连接绑定到当前线程中,新开启一个线程获取到的连接就不是同一个了。

@Transactional
public void deleteUser() throws MyException{
    userMapper.deleteUserA();
    try {
        //休眠1秒,保证deleteUserA先执行
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(() -> {
        int i = 1/0;
        userMapper.deleteUserB();
    }).start();    
}

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

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

相关文章

前后端分离项目之登录页面(前后端请求、响应和连接数据库)

目录 一、前端登录发起请求 二、后端请求接收 三、连接数据库 四、后端响应 五、前端处理 六、在前端验证用户是否登录 七、web会话跟踪 八、请求拦截器和响应拦截器 本文Vue-cli前端项目基于文章: Vue-cli搭建项目(包含Node.js安装和ElementUI安装)_小俱的…

2. requests.get()函数访问网页(小白入门)

2. requests.get()函数访问网页(小白入门) 文章目录 2. requests.get()函数访问网页(小白入门)1. 人工访问网页2. 爬虫第一步:发起网络请求3. requests库的安装4. requests.get()函数:发送网络请求5. 代码解析1. 导入库的语法2. 指定网址3. 发送请求4. 输…

chatgpt赋能python:Python取消撤销——让你的代码更加高效

Python取消撤销——让你的代码更加高效 在Python编程的过程中,经常会出现需要撤销操作的场景。但是,在一些复杂的代码编辑器中,常规的CtrlZ撤销操作可能无法满足你对代码精度的要求。为此,Python取消撤销就应运而生。 Python取消…

多变量系统的最小二乘辨识问题的推导以及matlab仿真

1.单输入单输出情况的推导;2.两输入两输出情况的推导,并进行matlab仿真以及完成仿真报告。 多变量系统的最小二乘辨识问题是确定一个线性多输入多输出(MIMO)系统的未知参数,使得该系统能够以最佳方式近似给定输入和输出之间的关系。在本例中,我们将展示单输入单输出(SIS…

软件外包开发的测试用例

软件测试用例是一组详细的步骤、输入数据、预期结果和实际结果,用于验证软件是否满足特定需求或功能。编写测试用例的目的是确保软件的质量和性能。今天和大家分享编写软件测试用例的一般步骤,希望对大家有所帮助。北京木奇移动技术有限公司,…

解决record on line 2: wrong number of fields

背景 基于"encoding/csv"库解析。 共解析多个文档,只有这一个解析有问题,所用代码一致,进行比较后 发现该文档和其它文档不同,其它文档是第一行就是列名,下面都是数据; 而这个文档前两行有数据且…

k8s部署Elasticsearch集群+Kibana方案--开启X-Pack 安全认证

前言 本文中使用StatefulSet 方式部署 Elasticsearch 集群,并且开启X-Pack 安全认证,存储使用的是NFS,属于一个初学者自己探索的方案,如果有比较好的方案,还请不吝评论赐教。 版本说明: Kubernetes v1.25…

微信小程序uniapp医患管理系统预约挂号就诊处方满意评价系统

从系统开发环境、系统目标、设计流程、功能设计等几个方面对系统进行了系统设计。开发出本医患关系管理系统,主要实现了管理员后端;首页、个人中心、用户管理、医生管理、医生信息管理、患者信息管理、预约就诊管理、就诊信息管理、投诉管理、投诉反馈管…

【走进Linux的世界】Linux---基本指令(2)

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【Linux专栏】🎈 本专栏旨在分享学习Linux的一点学习心得,欢迎大家在评论区讨论💌 目录 ls *man指令小…

redis缓存穿透、缓存雪崩 、缓存击穿

一、缓存穿透、缓存雪崩 、缓存击穿 摘自尚硅谷文档 1、缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个…

Spin加载中(antd-design组件库)loading效果简单使用

1.Spin加载中 用于页面和区块的加载中状态。 2.何时使用 页面局部处于等待异步数据或正在渲染过程时,合适的加载动效会有效缓解用户的焦虑。 组件代码来自: 加载中 Spin - Ant Design 3.本地验证前的准备 参考文章【react项目antd组件-demo:hello-world_…

PyTorch深度学习实战(2)——PyTorch基础

PyTorch深度学习实战(2)——PyTorch基础 0. 前言1. 搭建 PyTorch 环境2. PyTorch 张量2.1 张量初始化2.2 张量运算2.3 张量对象的自动梯度计算 3. PyTorch 张量相对于 NumPy 数组的优势小结系列链接 0. 前言 PyTorch 是广泛应用于机器学习领域中的强大开…

AutoHotKey脚本初步:判断和选择

文章目录 脚本基础连击识别setTimer判断和选择 脚本基础 尽管通过窗口识别与按键映射,就可以胜任很多工作了,但AutoHotKey仍提供了一些简单的编程功能,对一些稍微复杂的任务,也可以做到得心应手。但要注意一点,AHK的V…

【操作系统】Linux 中的 Page Cache

【操作系统】Linux 中的 Page Cache 参考资料: 文件 I/O 简明概述 - page cache 进程写文件时,进程发生了崩溃,已写入的数据会丢失吗? Linux Page Cache 调优在 Kafka 中的应用 【操作系统】一文带你深入浅出零拷贝技术 【操作系…

大数据Doris(三十四):Doris配置Spark与Yarn

文章目录 Doris配置Spark与Yarn 一、Doris配置Spark 1、配置 SPARK_HOME 环境变量 2、配置SPARK 依赖包

JavaScript之BOM(八)

JavaScript之BOM 1、BOM中的对象2、window对象2.1、简介2.2、常用的属性与方法2.3、常用的事件2.4、定时器和延时器 3、navigator 常用属性与方法4、history 常用属性与方法5、location 常用属性与方法 BOM:浏览器对象模型(Browser Object Model&#xf…

交直流系统潮流计算及相互关联特性分析(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

chatgpt赋能python:Python取消合并单元格

Python取消合并单元格 在Excel中,合并单元格是一个非常常见的操作,它可以将多个单元格合并成一个单元格。这样可视化效果会更好,但是实际上会影响数据的计算和操作。如果你想取消这个操作,手工操作可能会非常费时间。不过&#x…

chatgpt赋能python:Python变量赋值

Python 变量赋值 在 Python 中,我们可以使用多种符号来给变量赋值。本文将介绍这些符号以及它们在编程中的应用。 等号() 在 Python 中,我们最常用的符号是等号(),它可以将一个值赋给一个变量…

Image fusion based on generative adversarial network consistent with perception

1.摘要 深度学习是红外和可见光图像融合领域中快速发展的方法。在这个背景下,密集块在深层网络中的使用显著提高了浅层信息的利用率,并且生成对抗网络(GAN)的组合也提高了两个源图像的融合性能。我们提出了一种基于密集块和GAN的…