/**
* 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;
事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

在我所见过的误解中, 最常见的是下面这种:

假如有两个业务接口 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 里 。
     说了这么半天, 那到底什么是真正的事务嵌套呢, 解释之前我们来看一下  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。

那么,外部事务如何利用嵌套事务的 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 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) . 
         那么,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 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了 prototype.js 中的 Try 函数 )

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

参考:

https://blog.csdn.net/zmx729618/article/details/77976793

解惑spring事务传播特性之嵌套事务的更多相关文章

  1. 事务、事务特性、事务隔离级别、spring事务传播特性

    事务.事务特性.事务隔离级别.spring事务传播特性   1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...

  2. 什么是事务、事务特性、事务隔离级别、spring事务传播特性

    1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败). 2.事务特性: 事务特性分为四个:原子性(At ...

  3. spring事务传播特性实验(2):PROPAGATION_REQUIRED实验结果与分析

    本文延续上一文章(spring事务传播特性实验(1):数据准备),在已经准备好环境的情况下,做如下的实验,以验证spring传播特性,加深对spring传播特性的理解. 本次主要验证PROPAGATI ...

  4. Spring事务传播特性的浅析——事务方法嵌套调用的迷茫

    Spring事务传播机制回顾 Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务.结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷. 其实这 ...

  5. 什么是事务?事务特性?事务隔离级别?spring事务传播特性?

    一.事务的概述 什么是事务? 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句,当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交.判断事务是否配置成功的关键点在于出现异常时事 ...

  6. spring 事务传播特性 和隔离级别

    事务的几种传播特性1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务.如果没有事务则开启2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务 ...

  7. Spring 事务传播特性

    Spring 事务属性一共有四种:传播行为.隔离级别.只读和事务超时 a)   传播行为定义了被调用方法的事务边界. 传播行为 意义 PROPERGATION_MANDATORY 表示方法必须运行在一 ...

  8. 数据库与spring事务传播特性

    一.spring事务管理的实现原理,基于AOP 1) REQUIRED ,这个是默认的属性 Support a current transaction, create a new one if non ...

  9. 【原创】spring中的事务传播特性

    关于spring的传播特性,我对其进行了详细的叙述了下: PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务.这是最常见的选择. 比如方法A调用方法B,如果方法 ...

随机推荐

  1. hive面试题(免费拿走不谢)

    Hive 最常见的几个面试题 1.hive 的使用, 内外部表的区别,分区作用, UDF 和 Hive 优化(1)hive 使用:仓库.工具(2)hive 内部表:加载数据到 hive 所在的 hdf ...

  2. 个人学习HTML以及CSS所得体会

    拥有自己样式的浏览器: 苹果,欧朋,谷歌,IE,火狐 form标签<form></form> 表单属性: 1,action主要同来规定表单的作用,提交到处理器上面处理URL,默 ...

  3. elementUI中的loading

    先安装引入 import ElementUI from 'element-ui' import { Loading } from 'element-ui' 在vue的原型链上定义一个打开loading ...

  4. 发现护考上机考试的一个bug:附软件截图(模拟软件)

    目录: 一.文章主旨 二.问题发现的起因 三.bug(问题)描述 四.软件截图 五.我的思考 六.一点期盼 一.文章主旨: 2019年5月18.19.20日,又是一年一度的护资考试(上机考),考试前夕 ...

  5. IDEA找不到maven仓库无法下载依赖解决办法

    1.确认Maven安装正常,在cmd窗口输入mvn -version 可以获得版本号: 2. 确认maven安装包下/conf/setting.xml配置文件正确 本地仓库位置: <localR ...

  6. PAT乙级1041

    题目链接 https://pintia.cn/problem-sets/994805260223102976/problems/994805281567916032 题解 简单的信息录入和查询而已. ...

  7. docker 在push镜像到本地registry出现的500 Internal Server Error

    ]# docker push 192.168.163.131:5000/test The push refers to a repository [192.168.163.131:5000/test] ...

  8. org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException

    org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method ...

  9. 1.打开windows中功能的快捷方式

    1.打开组策略 命令:gpedit.msc 2.打开注册表 命令:regedit 3.快速打开本地安全组策略 命令:secpol.msc 4.打开服务 命令:services.msc 5.系统退域的时 ...

  10. 两种atm取款方式

    1.//函数 密码 账号function User(username, password, account){ this.username = username; this.password = pa ...