理解 spring 事务传播行为与数据隔离级别

2023-11-13

本文是为了个人学习使用,原文章链接:
https://www.jianshu.com/p/760399781b78
https://blog.csdn.net/m0_37524661/article/details/84935117

注:

1、事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低。

2、对于不同的数据库,支持的隔离级别也不一样:Oracle只能支持读写提交和串行化,而MySQL能够支持4种,对于Oracle默认的隔离级别为读写提交,MySQL则是可重复读。

3、注解@transactional的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的。

事务,是为了保障逻辑处理的原子性(事务里面的操作单元不可切割,要么全部成功,要么全部失败 )、一致性(事务执行前后,业务状态和其他业务状态保持一致. )、隔离性(个事务执行的时候最好不要受到其他事务的影响 )、永久性(一旦事务提交或者回滚.这个状态都要持久化到数据库中)。

通过事务控制,可以避免因为逻辑处理失败而导致产生脏数据等等一系列的问题。

事务有两个重要特性:

  1. 事务的传播行为
  2. 数据隔离级别

1、事务传播行为(Transaction Behavior)

传播行为(pragation)

含义:事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)

@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务

@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务

@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常

@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)

@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

@Transactional(propagation=Propagation.NESTED)
在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前方法的事务。

传播行为级别,定义的是事务的控制范围。通俗点说,执行到某段代码时,对已存在事务的不同处理方式。

Spring 对 JDBC 的事务隔离级别进行了补充和扩展,并提出了 7 种事务传播行为。

注:常用的传播行为主要有三种:REQUIRED 、REQUIRES_NEW、 NESTED

@Transactional注解

默认传播行为:REQUIRED
默认隔离级别:默认使用数据库的隔离级别
超时时间:默认使用底层事物的超时时间

1)Spring 中提供的 7 种传播行为

1.PROPAGATION_REQUIRED,需要事务处理。有则使用,无则新建。这是 Spring 默认的事务传播行为。该级别的特性是,如果 Context 中已经存在事务,那么就将当前需要使用事务的代码加入到 Context 的事务中执行,如果当前 Context 中不存在事务,则新建一个事务执行代码。这个级别通常能满足大多数的业务场景。

2.PROPAGATION_SUPPORTS,支持事务处理。该级别的特性是,如果 Context 存在事务,则将代码加入到 Context 的事务中执行,如果 Context 中没有事务,则使用 非事务 的方式执行。

3.PROPAGATION_MANDATORY,强制性要求事务。该级别的特性是,当要以事务的方式执行代码时,要求 Context 中必须已经存在事务,否则就会抛出异常!使用 MANDATORY 强制事务,可以有效地控制 “必须以事务执行的代码,却忘记给它加上事务控制” 这种情况的发生。举个简单的例子:有一个方法,对这个方法的要求是一旦被调用,该方法就必须包含在事务中才能正常执行,那么这个方法就适合设置为 PROPAGATION_MANDATORY 强制事务传播行为,从而在代码层面加以控制。

4.PROPAGATION_REQUIRES_NEW,每次都新建一个事务。该级别的特点是,当执行到一段需要事务的代码时,先判断 Context 中是否已经有事务存在,如果不存在,就新建一个事务;如果已经存在,就 suspend 挂起当前事务,然后创建一个新事务去执行,直到新事务执行完毕,才会恢复先前挂起的 Context 事务。

5.PROPAGATION_NOT_SUPPORTED,不支持事务。该级别的特点是,如果发现当前 Context 中有事务存在,则挂起该事务,然后执行逻辑代码,执行完毕后,恢复先前挂起的 Context 事务。这个传播行为的事务,可以缩小事务处理过程的范围。举个简单例子,在一个事务中,需要调用一段非核心业务的逻辑操作 1000 次,如果将这段逻辑放在事务中,会导致该事务的范围变大、生命周期变长,为了避免因事务范围扩大、周期变长而引发一些的事先没有考虑到的异常情况发生,可以将这段逻辑设置为 NOT_SUPPORTED 不支持事务传播行为。

6.PROPAGATION_NEVER,对事务要求更严格,不能出现事务!该级别的特点是,设置了该级别的代码,在执行前一旦发现 Context 中有事务存在,就会抛出 Runtime 异常,强制停止执行,有我无他!

7.PROPAGATION_NESTED,嵌套事务。该级别的特点是,如果 Context 中存在事务 A,就将当前代码对应的事务 B 加入到 事务 A 内部,嵌套执行;如果 Context 中不存在事务,则新建事务执行代码。换句话说,事务 A 与事务 B 之间是父子关系,A 是父,B 是子。理解嵌套事务的关键点是:save point。

父、子事务嵌套、save point 的说明:
1 .父事务会在子事务进入之前创建一个 save point;
2 .子事务 rollback ,父事务只会回滚到 save point,而不会回滚整个父事务;
3 .父事务 commit 之前,必须先 commit 子事务。

2)代码举例说明

我在网上看到有一篇文章,采用代码的方式来解释事务传播行为级别,代码方式很清晰,一看就明白了。

首先准备如下两个 Service:

class ServiceA {
     void methodA() {
         ServiceB.methodB();
     }
}

class ServiceB {
     void methodB() {
     }
}    
  1. ServiceB.methodB() 的传播行为定义为 PROPAGATION_REQUIRED , 那么在执行 ServiceA.methodA() 的时候,若 ServiceA.methodA() 已经开启了事务,这时调用 ServiceB.methodB()ServiceB.methodB() 将会运行在 ServiceA.methodA() 的事务内部,而不再开启新的事务。而假如 ServiceA.methodA() 运行的时候发现自己没有在事务中,就会为它分配一个新事务。这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。即使 ServiceB.methodB() 的事务已经被
    提交,但是 ServiceA.methodA() 在接下来的过程中 fail 要回滚,ServiceB.methodB() 也会跟着一起回滚。

  2. 假如 ServiceA.methodA() 的传播行为设置为 PROPAGATION_REQUIRED ServiceB.methodB() 的传播行为为 PROPAGATION_REQUIRES_NEW,那么当执行到 ServiceB.methodB() 的时候, ServiceA.methodA() 所在的事务就会挂起,而 ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,A的事务才会继续执行。 PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW 的事务区别在于事务的回滚程度。因为 ServiceB.methodB 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB 已经提交,那么 ServiceA.methodA 失败回滚, ServiceB.methodB 是不会回滚的。如果 ServiceB.methodB 失败回滚,如果它抛出的异常被 ServiceA.methodA 捕获, ServiceA.methodA 事务仍然可能会提交。

  3. ServiceA.methodA 的事务传播行为是 PROPAGATION_REQUIRED,而 ServiceB.methodB 的事务传播行为是 PROPAGATION_NOT_SUPPORTED,那么当执行到 ServiceB.methodB 时, ServiceA.methodA 的事务挂起,而 ServiceB.methodB 以非事务的状态运行完之后,再继续 ServiceA.methodA 的事务。

  4. 假如 ServiceA.methodA 的事务传播行为是 PROPAGATION_REQUIRED, 而 ServiceB.methodB 的事务级别是 PROPAGATION_NEVER ,那么 ServiceB.methodB 执行时就会抛出异常。

2、数据隔离级别(Isolation Level)

在读取数据库的过程中,如果两个事务并发执行,那么多个事务彼此之间,会对数据产生什么样的影响呢?

这里就引出了事务的第二个特性:数据隔离级别。

数据隔离级别,定义的是事务在数据库端读写方面的控制范围

数据隔离级别分为 4 种:

  1. Serializable:串行化。这是最严格的隔离级别,多个事务之间串行执行,资源消耗极大。
    在这里插入图片描述

  2. Repeatable Read:可重复读。该级别可以确保一个已经被事务读取的数据,另一个事务不能修改这个数据,从而避免了 “脏读” 和 “不可重复读”。仍然有较大的性能损耗。
    在这里插入图片描述

  3. Read Commited:这是大部分主流数据库默认的数据隔离级别。该级别下,只允许读已经提交的数据。例如:当一个事务修改了数据但未提交时,另一个并行事务只会读到该数据修改之前的内容,从而避免了 “脏读”。
    在这里插入图片描述

  4. Read Uncommited:一个事务可以读取另一个并行事务已修改但还未提交的数据。会产生 “脏读”。
    在这里插入图片描述

第 1 种数据准确性最高,但相应地性能最差。第 4 种性能高,但是相应地读取数据的准确性低。

3、脏读、幻读、不可重复读

脏读、幻读、不可重复读都是并发事务的情况下,因为不同的数据隔离级别而读取到不同的内容。

脏读(Dirty Reads)

脏读,即一个事务读到了另一个事务还未提交的数据。如果脏读读取到的数据最终还是提交了倒还好,但如果这条数据最终回滚了,那么这条数据对于刚刚读取到它的事务而言,就是一条脏数据。

不可重复读(Non-repeatable Reads)

不可重复读,不同的事务读取同一条数据,读取到的内容是不同的。也就是说,对某一条数据而言,不同的事务以同样的重复操作读取,却产生了不同的结果。

幻读(Phantom Reads)

幻读,一个事务按照某种查询条件,第一次读取的数据量和第二次读取的数据量不一样,就像幻觉一样,明明刚才查的是 N 条数据,再查一次就变成了 M 条(M <> N)。

4、如何缩小事务?

假设一个逻辑操作需要检查的条件有 20 个,能否为了减小事务而将检查性的内容放到事务之外呢?

很多系统都是在 DAO 的内部开始启动事务,然后进行操作,最后提交或者回滚。这其中涉及到代码设计的问题。

小一些的系统可以采用这种方式来做,但是在一些比较大的系统,逻辑较为复杂的系统中,势必会将过多的业务逻辑嵌入到 DAO 中,导致 DAO 的复用性下降。所以这不是一个好的实践。

来回答这个问题,能否为了缩小事务,而将一些业务逻辑检查放到事务外面?

答案是:对于核心的业务检查逻辑,不能放到事务之外,而且必须要作为分布式下的并发控制!一旦在事务之外做检查,那么势必会造成事务A已经检查过的数据被事务B所修改,导致事务A徒劳无功而且出现并发问题,直接导致业务控制失败。

所以,在分布式的高并发环境下,对于核心业务逻辑的检查,要采用加锁机制。

比如事务开启需要读取一条数据进行验证,然后逻辑操作中需要对这条数据进行修改,最后提交。

这样的一个过程,如果读取并验证的代码放到事务之外,那么读取的数据极有可能已经被其他的事务修改,当前事务一旦提交,又会重新覆盖掉其他事务的数据,导致数据异常。

所以在进入当前事务的时候,必须要将这条数据锁住,例如使用 Oracle 的 for update 就是一个在分布式环境下很有效的控制手段。

另一种好的实践方式是使用编程式事务而非声明式事务,尤其是在较大规模的项目中。对于大量事务的声明配置,在代码量非常大的情况下,将是一种折磨。

将 DAO 保持针对一张表的最基本操作,然后业务逻辑的处理放入 manager 或 service 中进行,同时使用编程式事务可以更精确地控制事务范围。

特别注意的,对于事务内部一些可能抛出异常的情况,捕获异常时要谨慎,不能随便的 catch Exception,不然会导致事务的异常被吃掉而不能正常回滚。

5、spring 配置声明式事务

Spring配置声明式事务:

  1. 配置SessionFactory
  2. 配置事务管理器
  3. 事务的传播特性
  4. 声明哪些类,哪些方法需要使用事务

编写业务逻辑方法:

  1. 默认情况下运行期异常才会回滚(包括继承了RuntimeException子类),普通异常是不会回滚的。
  2. 编写业务逻辑方法时,最好将异常一直向上抛出,在表示层(view)处理。
  3. 关于事务边界的设置,通常设置到业务层,不要添加到 Dao 上。

(1)使用 xml 配置方式:

<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation">
     <value>classpath:hibernate.cfg.xml</value>
    </property>
</bean>

<!-- 配置 Hibernate 事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 定义通知:定义事务的传播行为 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="del*" propagation="REQUIRED"/>
        <tx:method name="modify*" propagation="REQUIRED"/>
        <tx:method name="*" propagation="REQUIRED" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!-- 声明哪些类哪些方法需要使用事务 -->
<aop:config>
    <aop:pointcut id="transactionPC" expression="execution(* com.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPC"/>
</aop:config>

<!-- 普通 IOC 注入 -->
<bean id="userManager" class="com.service.UserManagerImpl">
    <property name="logManager" ref="logManager"/>
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="logManager" class="com.service.LogManagerImpl">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

关于 spring 配置中 read-only 的说明:

read-only 配置为 true,会告诉 spring 对应的事务应该被最优化为只读事务。

这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用 Object/Relational 映射工具(如:Hibernate 或 TopLink)时避免 dirty checking(试图“刷新”)。

(2)使用 JPA 注解方式

@Service
public class UserServiceImpl implements IUserService {
  @Resource
  IUserDAO userDAO;
  
  //启动 REQUIRED 默认事务传播行为的方法
  @Transactional
  public void funNone() throws Exception {
    save(new UserEntity("aaa"));
  }
  
  //启动 REQUIRED 默认事务传播行为的方法
  @Transactional(propagation = Propagation.REQUIRED)
  public void funRequire() throws Exception {
    save(new UserEntity("bbb"));
  }
  
  //启动 Nested 嵌套事务的方法
  @Transactional(propagation = Propagation.NESTED)
  public void funNest() throws Exception {
    save(new UserEntity("ccc"));
  }
  
  //REQUIRES_NEW 事务的方法
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void funRequireNew() throws Exception {
    save(new UserEntity("ddd"));
  }

}

本文是为了个人学习使用,原文章链接:
https://www.jianshu.com/p/760399781b78
https://blog.csdn.net/m0_37524661/article/details/84935117

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

理解 spring 事务传播行为与数据隔离级别 的相关文章

随机推荐

  • ##顺序表 编码##

    ifndef LIST H define LIST H class List public List int size List 析构函数 void ClearList 清空线性表 bool ListEmpty 判断线性表是否为空 int
  • VXLAN 大二层网络构建实战

    1 VXLAN 大二层网络 1 VXLAN背景 传统vlan可划分1 4094个子网 如果租户数量超过4094 就需要使用vxlan了 vxlan支持1670万个隔离网络通信 可以满足众多不同租户设计自己的内部网络 租户申请的云服务器来自不
  • 简单实用的数据库文档生成器

    哈喽 大家好 我是指北君 大家有没有过这样的经历 在DeadLine即将来临之际 文档尚未完成 面对纷繁复杂的数据库中各种表 视图 关联 存储过程等 你都不知道如何清晰准确的描述他们 不得不一个个打开表 视图 存储过程等查看 甚至可能因为疏
  • springboot自定义kafka消费者KafkaListenerContainerFactory配置说明和实验

    1 说明 实验一下 在spring boot中自行创建kafkaConsumer实例 进行消费 根据配置的不同 得出相应的结果 此例中 我使用 kafkaListenerContainerFactory自行创建了kafkaListener实
  • WSL环境中Pycharm的python控制台中报错 ModuleNotFoundError: No module named ‘xxx‘(WSL+Pycharm环境搭建系列之四)

    WSL环境中Pycharm的python控制台中报错 ModuleNotFoundError No module named xxx 0 前言 1 问题现象 2 问题分析 3 解决办法 0 前言 经过前面几次的努力 我们基本上完成了环境中的
  • 《python数据分析与应用》第四章:pandas统计分析基础

    第四章 4 1 读写不同数据源的数据 4 1 1 读 写数据库数据 1 数据库数据读取 注意 数据库的用户名 一般都是root 和密码 自己设置的 都是要用自己的 地址默认的127 0 0 1 读取函数 你自己把文件存在哪了 和存储函数 你
  • 解密蓝牙mesh系列

    转载自 蓝牙技术联盟 蓝牙mesh网络基本概念 本周跟大家分享解密蓝牙mesh系列第四篇 点这里查看第一篇 第二篇和第三篇文章 同时也是蓝牙mesh网络基本概念的第二部分 借助蓝牙mesh 您将能够创建大型网络并支持成千上万的设备之间进行安
  • css的新玩法,语法与规则

    css的新玩法 语法与规则 important 语法 Selector sRule important 说明 提升指定样式规则的应用优先权 IE6及以下浏览器有个比较显式的支持问题存在 important并不覆盖掉在同一条样式的后面的规则
  • 将Javaweb项目部署到云服务器

    一 前言 由于业务需要 要搞一个文件上传下载服务器 代码当前已经在localhost上调试成功 现在需要把这个项目部署到云服务器上 选择的服务器是vultr的vps 系统是Ubuntu16 04 二 环境准备 1 jdk安装 常规安装即可
  • [Spring][Redis]@Cacheable与redis整合时Value和Key的理解

    更新于2019年11 08 首先 多谢CHENFU ZKK同学指出我的错误 卧槽 吓我一跳 我还以为我以前学的搞错了 若不是我亲自测试一遍 差点信你了 这两天趁有空 跟踪了 Cacheable相关的源码再次确认结果如下 1 直接使用Spri
  • 华为OD机试 - 星际篮球争霸赛(Java)

    题目描述 在星球争霸篮球赛对抗赛中 最大的宇宙战队希望每个人都能拿到MVP MVP的条件是单场最高分得分获得者 可以并列所以宇宙战队决定在比赛中尽可能让更多队员上场 并且让所有得分的选手得分都相同 然而比赛过程中的每1分钟的得分都只能由某一
  • 虚拟机联网图标丢失,连不上网的问题

    虚拟机连不上网 网络图标也消失不见了 输入ifconfig 发现也不显示ens33网卡 盲猜是由于关闭虚拟机的时候 一些配置出错 导致ens33网卡没有启动成功导致的 解决办法 重新开启网卡即可 1 关闭网络服务 sudo service
  • J2EE基础之集合框架List

    前言 今天跟大家发分享的是J2EE基础之集合框架List以及JavaWeb团队项目UML 昨天自己记录了J2EE一些基础知识 希望今天分享的知识对大家有用 首先跟大家讲一下今天要分享的知识 见思维导图 一 UML 1 含义 UML建模技术是
  • 异常日志分析

    从上往下看异常信息 直到第一行是自己写的代码为止 集成第三方工具 比如百度地图 先看demo 按着demo先跑来 然后再优化 跑demo出错了 先看异常日志 解决不了百度 百度地图集成注意类冲突 慢慢来
  • 无源定位入门(三)AOA(1)

    AOA交叉测向 基本原理 以M元均匀线阵为例 设单站匀速运动 第n个时刻的位置为 静止目标 则入射角方向为 设基站上有个M元均匀线阵 相邻阵元之间的间隔为d 其中为波长 第n个时刻线阵的方位角 与X正方向的夹角 为 与XY平面的夹角为 则线
  • 如何用sql语句创建一个表(简单基础)

    使用CREATE TABLE 语句 CREATE TABLE 语句用于创建数据库中的表 SQL CREATE TABLE 语法 CREATE TABLE 表名称 列名称1 数据类型 列名称2 数据类型 列名称3 数据类型 例题 创建一个ac
  • SPDK详解

    一 SPDK简介及基本使用 随着硬盘 闪存技术的高速发展 NVME ssd已逐渐进入分布式存储的核心领域 伴随着NVME ssd的出现 涌现出一批新型的存储名词 包括分层存储 分级存储 冷热存储 混合存储等 而这些名词的出现 也意味着ssd
  • sql 求分位数

    1 oracle求分位数 SELECT RES3 ORG NO 5 RES3 YM X1的上四分位数 PERCENTILE CONT 0 75 WITHIN GROUP ORDER BY RES3 X1 ASC AS X1 75 X1的上中
  • IntelliJ IDEA 常用快捷键,maven依赖图,个性化设置,禁用Search Everywhere

    查看idea 中jar关系图 快捷键 Ctrl 用于注释 取消注释 Ctrl Shift F 全文搜索 Ctrl F 单页面查找 Ctrl Alt Shift L 格式化代码 CTRL N 查找类CTRL SHIFT N 查找文件CTRL
  • 理解 spring 事务传播行为与数据隔离级别

    本文是为了个人学习使用 原文章链接 https www jianshu com p 760399781b78 https blog csdn net m0 37524661 article details 84935117 注 1 事务的隔