Spring(九) - 解惑 spring 嵌套事务.2

    1. 事务传播特性   

         在所有使用 spring 的应用中, 声明式事务管理可能是使用率最高的功能了, 但是, 从我观察到的情况看,绝大多数人并不能深刻理解事务声明中不同事务传播属性配置的的含义, 让我们来看一下 TransactionDefinition 接口中的定义

Java代码 

/** * Support a current transaction, create a new one if none exists. * Analogous to EJB transaction attribute of the same name. * <p>This is typically the default setting of a transaction definition. */  int PROPAGATION_REQUIRED = 0;  /** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: For transaction managers with transaction synchronization, * PROPAGATION_SUPPORTS is slightly different from no transaction at all, * as it defines a transaction scopp that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */  int PROPAGATION_SUPPORTS = 1;  /** * Support a current transaction, throw an exception if none exists. * Analogous to EJB transaction attribute of the same name. */  int PROPAGATION_MANDATORY = 2;  /** * Create a new transaction, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: Actual transaction suspension will not work on out-of-the-box * on all transaction managers. This in particular applies to JtaTransactionManager, * which requires the <code>javax.transaction.TransactionManager</code> to be * made available it to it (which is server-specific in standard J2EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */  int PROPAGATION_REQUIRES_NEW = 3;  /** * Execute non-transactionally, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: Actual transaction suspension will not work on out-of-the-box * on all transaction managers. This in particular applies to JtaTransactionManager, * which requires the <code>javax.transaction.TransactionManager</code> to be * made available it to it (which is server-specific in standard J2EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */  int PROPAGATION_NOT_SUPPORTED = 4;  /** * Execute non-transactionally, throw an exception if a transaction exists. * Analogous to EJB transaction attribute of the same name. */  int PROPAGATION_NEVER = 5;  /** * Execute within a nested transaction if a current transaction exists, * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB. * <p>Note: Actual creation of a nested transaction will only work on specific * transaction managers. Out of the box, this only applies to the JDBC * DataSourceTransactionManager when working on a JDBC 3.0 driver. * Some JTA providers might support nested transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */  int PROPAGATION_NESTED = 6;  

我们可以看到, 在 spring 中一共定义了七种事务传播属性

PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)

最常见的是下面这种:
引用

假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {// 调用 ServiceB 的方法ServiceB.methodB();
}那么如果 ServiceB 的 methodB  如果配置了事务, 就必须配置为 PROPAGATION_NESTED

        这种想法可能害了不少人, 认为 Service 之间应该避免互相调用, 其实根本不用担心这点,PROPAGATION_REQUIRED 已经说得很明白,
        如果当前线程中已经存在事务, 方法调用会加入此事务, 果当前没有事务,就新建一个事务, 所以 ServiceB#methodB() 的事务只要遵循最普通的规则配置为 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(我们称之为外部事务) 如果没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是一定要 rollback 的, 什么 Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 还是 Dao 里 ?




    2. 嵌套事务

         说了这么半天, 那到底什么是真正的事务嵌套呢, 解释之前我们来看一下  Juergen Hoeller 的原话

Juergen Hoeller 写道

PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed.

Such independent inner transactions are for example used for id generation through manual sequences, where the access to the sequence table should happen in its own transactions, to keep the lock there as short as possible. The goal there is to avoid tying the sequence locks to the (potentially much longer running) outer transaction, with the sequence lock not getting released before completion of the outer transaction.

PROPAGATION_NESTED on the other hand starts a "nested" transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. íf the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of of the outer transaction, so it will only be committed at the end of of the outer transaction.

Nested transactions essentially allow to try some execution subpaths as subtransactions: rolling back to the state at the beginning of the failed subpath, continuing with another subpath or with the main execution path there - all within one isolated transaction, and not losing any previous work done within the outer transaction.

For example, consider parsing a very large input file consisting of account transfer blocks: The entire file should essentially be parsed within one transaction, with one single commit at the end. But if a block fails, its transfers need to be rolled back, writing a failure marker somewhere. You could either start over the entire transaction every time a block fails, remembering which blocks to skip - or you mark each block as a nested transaction, only rolling back that specific set of operations, keeping the previous work of the outer transaction. The latter is of course much more efficient, in particular when a block at the end of the file fails.

Juergen Hoeller 写道 

Rolling back the entire transaction is the choice of the demarcation code/config that started the outer transaction.

So if an inner transaction throws an exception and is supposed to be rolled back (according to the rollback rules), the transaction will get rolled back to the savepoint taken at the start of the inner transaction. The immediate calling code can then decide to catch the exception and proceed down some other path within the outer transaction.

If the code that called the inner transaction lets the exception propagate up the call chain, the exception will eventually reach the demarcation code of the outer transaction. At that point, the rollback rules of the outer transaction decide whether to trigger a rollback. That would be a rollback of the entire outer transaction then.

So essentially, it depends on your exception handling. If you catch the exception thrown by the inner transaction, you can proceed down some other path within the outer transaction. If you let the exception propagate up the call chain, it's eventually gonna cause a rollback of the entire outer transaction.

  也就是说, 最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下 Juergen Hoeller 的话 :
   
    PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.


    另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务,  它是已经存在事务的一个真正的子事务. 潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

    由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.


3. 嵌套事务使用
3.1 PROPAGATION_REQUIRES_NEW 

        那么外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话

ServiceA {  /** * 事务属性配置为 PROPAGATION_REQUIRED */  void methodA() {  ServiceB.methodB();  }  }  ServiceB {   /** * 事务属性配置为 PROPAGATION_REQUIRES_NEW */   void methodB() {  }      
}     

 这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW, 所以两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果, 因为它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) .


3.2 PROPAGATION_NESTED 

        PROPAGATION_NESTED 又是怎么回事呢? 继续看代码

ServiceA {  /** * 事务属性配置为 PROPAGATION_REQUIRED */  void methodA() {  ServiceB.methodB();  }  
}  ServiceB {  /** * 事务属性配置为 PROPAGATION_NESTED */   void methodB() {  }    
}     

        现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为PROPAGATION_NESTED, 此时两者之间又将如何协作呢?        

        从 Juergen Hoeller 的原话中我们可以找到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

 1. 改写 ServiceA 如下

ServiceA {      /** * 事务属性配置为 PROPAGATION_REQUIRED */  void methodA() {  try {  ServiceB.methodB();  } catch (SomeException) {  // 执行其他业务, 如 ServiceC.methodC();  }  }  
}  

        而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了 prototype.js 中的 Try 函数 )

2. 代码不做任何修改

        复制一下上面代码:

ServiceA {  /** * 事务属性配置为 PROPAGATION_REQUIRED */  void methodA() {  ServiceB.methodB();  }  
}  ServiceB {  /** * 事务属性配置为 PROPAGATION_NESTED */   void methodB() {  }    
}     

         那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此),
   外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException). 


  4. Spring  PROPAGATION_NESTED 源码

        上面大致讲述了潜套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager

/*** Create a TransactionStatus for an existing transaction.*/private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled)throws TransactionException {......if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {if (!isNestedTransactionAllowed()) {throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions by default - " +"specify 'nestedTransactionAllowed' property with value 'true'");}if (debugEnabled) {logger.debug("Creating nested transaction with name [" + definition.getName() + "]");}if (useSavepointForNestedTransaction()) {// Create savepoint within existing Spring-managed transaction,// through the SavepointManager API implemented by TransactionStatus.// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);status.createAndHoldSavepoint();return status;}else {// Nested transaction through nested begin and commit/rollback calls.// Usually only for JTA: Spring synchronization might get activated here// in case of a pre-existing JTA transaction.return startTransaction(definition, transaction, debugEnabled, null);}}......
}

1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!

再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法 

/** * Create a savepoint and hold it for the transaction. * @throws org.springframework.transaction.NestedTransactionNotSupportedException * if the underlying transaction does not support savepoints */  
public void createAndHoldSavepoint() throws TransactionException {  setSavepoint(getSavepointManager().createSavepoint());  
}  

  可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager
中的 TransactonObject 都是它的子类 :

  JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint : 

2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+

3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0 

 确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了. (全文完)

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

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

相关文章

K8s中的Secret

Secret作用&#xff1a;加密数据存在etcd里面&#xff0c;让pod容器以挂载Volume方式进行访问。场景&#xff1a;凭据

linux允许root远程ssh登录

修改文件/etc/ssh/sshd_config # cat /etc/ssh/sshd_config ... #LoginGraceTime 2m #PermitRootLogin prohibit-password #StrictModes yes #MaxAuthTries 6 #MaxSessions 10 ...将 #PermitRootLogin prohibit-password标注为&#xff1a; PermitRootLogin yes样例&#xf…

规划路线(微信小程序、H5)

//地图getLocationDian(e1, e2) {console.log(e1, e2);let self this;self.xx1 [];self.xx2 [];self.points [];// self.markers[]console.log(self.markers, >marks);// self.$jsonp(url, data).then(re > {// var coors re.result.routes[0].polyline;// for (v…

文件编辑(vi/vim)

在所有的命令执行前&#xff0c;先按Esc 1、vi的基本概念   基本上vi可以分为三种状态&#xff0c;分别是命令模式&#xff08;command mode&#xff09;、插入模式&#xff08;Insert mode&#xff09;和底行模式&#xff08;last line mode&#xff09;&#xff0c;各模式…

Llama 2 with langchain项目详解(三)

Llama 2 with langchain项目详解(三) 17.3 Llama 2 with langchain基础 本节讲解在LangChain中使用Llama 2模型的基础知识,展示如何运行LangChain的代码,及在云端运行Llama 2的700亿模型。 首先,使用Python的pip管理器安装一系列库,包括huggingface/transformers、datase…

【Python】Locust持续优化:InfluxDB与Grafana实现数据持久化与可视化分析

目录 前言 influxDB 安装运行InfluxDB 用Python 上报数据到influxdb ocust 数据写入到 influx Locust的生命周期 上报数据 优化升级 配置Grafana 总结 资料获取方法 前言 在进行性能测试时&#xff0c;我们需要对测试结果进行监控和分析&#xff0c;以便于及时发现问…

HTML+CSS+JavaScript:渲染电商站购物车页面

一、需求 根据下图渲染购物车页面 二、代码素材 以下是缺失JS部分的代码&#xff0c;感兴趣的小伙伴可以先自己试着写一写 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatib…

政务云建设与应用解决方案[42页PPT]

导读&#xff1a;原文《政务云建设与应用解决方案[42页PPT]》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 完整版领取方式 完整版领取方式&#xff1a; 如需获取完…

ELF文件格式解析

ELF(Executable and Linkable Format) 即可执行可链接文件格式&#xff0c;是目前操作系统上最常见的可执行文件格式。不同系统的目标文件不一样&#xff0c;Windows是PE&#xff08;Portable Executable&#xff09;&#xff0c;linux是ELF&#xff08;Executable Linkable Fo…

【SpringCloud】RabbitMQ基础

1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;…

用Rust实现23种设计模式之 策略模式

关注我&#xff0c;学习Rust不迷路&#xff01;&#xff01; 优点 灵活性&#xff1a;策略模式允许你在运行时动态地选择不同的算法或行为&#xff0c;而无需修改已有的代码。可扩展性&#xff1a;通过添加新的策略类&#xff0c;你可以轻松地扩展策略模式的功能。可维护性&a…

vue2-diff算法

1、diff算法是什么&#xff1f; diff算法是一种通过同层的树节点进行比较的高效算法。 其有两个特点&#xff1a; 比较只会在同层级进行&#xff0c;不会跨层级进行。 在diff比较的过程中&#xff0c;循环从两边向中间比较。 diff算法在很多场景中都有应用&#xff0c;在vue中&…

Jmeter(一) - 从入门到精通 - 环境搭建(详解教程)

1.JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序&#xff0c;被设计为用于测试客户端/服务端结构的软件(例如web应用程序)。它可以用来测试静态和动态资源的性能&#xff0c;例如&#xff1a;静态文件&#xff0c;Java Servlet,CGI Scripts,Java Object,数据库和FTP服务器…

Hadoop学习:深入解析MapReduce的大数据魔力(二)

Hadoop学习&#xff1a;深入解析MapReduce的大数据魔力&#xff08;二&#xff09; 3.3 Shuffle 机制3.3.1 Shuffle 机制3.3.2 Partition 分区3.3.3 Partition 分区案例实操3.3.4 WritableComparable 排序3.3.5 Combiner 合并 3.4 OutputFormat 数据输出3.4.1 OutputFormat 接口…

HttpServletRequest和HttpServletResponse的获取与使用

相关笔记&#xff1a;【JavaWeb之Servlet】 文章目录 1、Servlet复习2、HttpServletRequest的使用3、HttpServletResponse的使用4、获取HttpServletRequest和HttpServletResponse 1、Servlet复习 Servlet是JavaWeb的三大组件之一&#xff1a; ServletFilter 过滤器Listener 监…

医学图像处理

医学图像处理 opencv批量分片高像素图像病理图像色彩特征提取基于 imgaug、skimage 实现色彩增强基于 Cycle-GAN 完成染色标准化 病理图像细微特征提取自动数据标注分类场景下的医学图像分析分割场景下的医学图像分析检测场景下的医学图像分析 , i ] k 8 < * I opencv批量…

4.DNS和负载均衡

文章目录 coreDNS概念部署croeDNS测试 kubernetes多master集群结构master节点部署 负载均衡配置部署nginx做四层反向代理安装高可用 keepalivednginx监控脚本修改k8s中组件的配置文件 coreDNS 概念 coreDNS是kubernetes的默认DNS实现。可以为集群中的service资源创建一个资源名…

cesium加载三维模型3dtiles

1.将数据和代码放到一个目录下 目的&#xff1a;为避免跨域 输入cmd命令 python3 -m http.server 5500 2.三维服务地址 http://127.0.0.1:5500/data/mars3d-max-shihua-3dtiles-master/tileset.json 3.模型网页地址 http://127.0.0.1:5500/cesium/cesium%E5%8A%A0%E8%BD%…

PyTorch中加载模型权重 A匹配B|A不匹配B

在做深度学习项目时&#xff0c;从头训练一个模型是需要大量时间和算力的&#xff0c;我们通常采用加载预训练权重的方法&#xff0c;而我们往往面临以下几种情况&#xff1a; 未修改网络&#xff0c;A与B一致 很简单&#xff0c;直接.load_state_dict() net ANet(num_cla…

TypeScript技能总结(三)

typescript是js的超集&#xff0c;目前很多前端框架都开始使用它来作为项目的维护管理的工具&#xff0c;还在不断地更新&#xff0c;添加新功能中&#xff0c;我们学习它&#xff0c;才能更好的在的项目中运用它&#xff0c;发挥它的最大功效 //泛型 > 参数和返回值类型相…