转自http://www.iteye.com/topic/35907

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

  1. /**
  2. * Support a current transaction, create a new one if none exists.
  3. * Analogous to EJB transaction attribute of the same name.
  4. * <p>This is typically the default setting of a transaction definition.
  5. */
  6. int PROPAGATION_REQUIRED = 0;
  7.  
  8. /**
  9. * Support a current transaction, execute non-transactionally if none exists.
  10. * Analogous to EJB transaction attribute of the same name.
  11. * <p>Note: For transaction managers with transaction synchronization,
  12. * PROPAGATION_SUPPORTS is slightly different from no transaction at all,
  13. * as it defines a transaction scopp that synchronization will apply for.
  14. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
  15. * will be shared for the entire specified scope. Note that this depends on
  16. * the actual synchronization configuration of the transaction manager.
  17. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
  18. */
  19. int PROPAGATION_SUPPORTS = 1;
  20.  
  21. /**
  22. * Support a current transaction, throw an exception if none exists.
  23. * Analogous to EJB transaction attribute of the same name.
  24. */
  25. int PROPAGATION_MANDATORY = 2;
  26.  
  27. /**
  28. * Create a new transaction, suspend the current transaction if one exists.
  29. * Analogous to EJB transaction attribute of the same name.
  30. * <p>Note: Actual transaction suspension will not work on out-of-the-box
  31. * on all transaction managers. This in particular applies to JtaTransactionManager,
  32. * which requires the <code>javax.transaction.TransactionManager</code> to be
  33. * made available it to it (which is server-specific in standard J2EE).
  34. * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
  35. */
  36. int PROPAGATION_REQUIRES_NEW = 3;
  37.  
  38. /**
  39. * Execute non-transactionally, suspend the current transaction if one exists.
  40. * Analogous to EJB transaction attribute of the same name.
  41. * <p>Note: Actual transaction suspension will not work on out-of-the-box
  42. * on all transaction managers. This in particular applies to JtaTransactionManager,
  43. * which requires the <code>javax.transaction.TransactionManager</code> to be
  44. * made available it to it (which is server-specific in standard J2EE).
  45. * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
  46. */
  47. int PROPAGATION_NOT_SUPPORTED = 4;
  48.  
  49. /**
  50. * Execute non-transactionally, throw an exception if a transaction exists.
  51. * Analogous to EJB transaction attribute of the same name.
  52. */
  53. int PROPAGATION_NEVER = 5;
  54.  
  55. /**
  56. * Execute within a nested transaction if a current transaction exists,
  57. * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
  58. * <p>Note: Actual creation of a nested transaction will only work on specific
  59. * transaction managers. Out of the box, this only applies to the JDBC
  60. * DataSourceTransactionManager when working on a JDBC 3.0 driver.
  61. * Some JTA providers might support nested transactions as well.
  62. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
  63. */
  64. 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)

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

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

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

最容易弄混淆的其实是 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 特性呢, 我们用代码来说话 :

  1. ServiceA {
  2.  
  3. /**
  4. * 事务属性配置为 PROPAGATION_REQUIRED
  5. */
  6. void methodA() {
  7. ServiceB.methodB();
  8. }
  9.  
  10. }
  11.  
  12. ServiceB {
  13.  
  14. /**
  15. * 事务属性配置为 PROPAGATION_REQUIRES_NEW
  16. */
  17. void methodB() {
  18. }
  19.  
  20. }

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

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

  1. ServiceA {
  2.  
  3. /**
  4. * 事务属性配置为 PROPAGATION_REQUIRED
  5. */
  6. void methodA() {
  7. ServiceB.methodB();
  8. }
  9.  
  10. }
  11.  
  12. ServiceB {
  13.  
  14. /**
  15. * 事务属性配置为 PROPAGATION_NESTED
  16. */
  17. void methodB() {
  18. }
  19.  
  20. }

现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 从 Juergen Hoeller 的原话中我们可以找到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 嵌套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

1. 改写 ServiceA 如下

  1. ServiceA {
  2.  
  3. /**
  4. * 事务属性配置为 PROPAGATION_REQUIRED
  5. */
  6. void methodA() {
  7. try {
  8. ServiceB.methodB();
  9. } catch (SomeException) {
  10. // 执行其他业务, 如 ServiceC.methodC();
  11. }
  12. }
  13.  
  14. }

这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 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).

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

  1. /**
  2. * Create a TransactionStatus for an existing transaction.
  3. */
  4. private TransactionStatus handleExistingTransaction(
  5. TransactionDefinition definition, Object transaction, boolean debugEnabled)
  6. throws TransactionException {
  7.  
  8. ... 省略
  9.  
  10. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
  11. if (!isNestedTransactionAllowed()) {
  12. throw new NestedTransactionNotSupportedException(
  13. "Transaction manager does not allow nested transactions by default - " +
  14. "specify 'nestedTransactionAllowed' property with value 'true'");
  15. }
  16. if (debugEnabled) {
  17. logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
  18. }
  19. if (useSavepointForNestedTransaction()) {
  20. // Create savepoint within existing Spring-managed transaction,
  21. // through the SavepointManager API implemented by TransactionStatus.
  22. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
  23. DefaultTransactionStatus status =
  24. newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
  25. status.createAndHoldSavepoint();
  26. return status;
  27. }
  28. else {
  29. // Nested transaction through nested begin and commit/rollback calls.
  30. // Usually only for JTA: Spring synchronization might get activated here
  31. // in case of a pre-existing JTA transaction.
  32. doBegin(transaction, definition);
  33. boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
  34. return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
  35. }
  36. }
  37. }

一目了然 
1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!! 
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法

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

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

JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint : 
1. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+ 
2. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0 
确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了.

spring的嵌套事务的更多相关文章

  1. 一次Spring Transactional嵌套事务使用不同的rollbackFor的分析

    起因: 项目期间由于一次异常回滚问题,发现自己在事务知识方面知识的遗漏,趁着这次机会,做了几次rollbackFor的测试. 测试:   现在有两个事务,事务oute包含事务Inner.事务A回滚规则 ...

  2. Spring:Spring嵌套事务方式

    Spring遇到嵌套事务时,怎么实现 实验时却遇到一个奇怪的问题: 1.当ServiceA.a()方法调用ServiceB.b()方法时,内层事务提交和回滚,都不受外层事务提交或回滚的影响. 2.当S ...

  3. spring知识大全(4)

    5 Spring对事务的支持 一.AOP事务的含义: 事务当作一个切面,动态地织入到目标对象,形成一个代理对象. 二.Spring的事务机制 Spring支持声明式事务. Spring使用事务服务代理 ...

  4. (转)添加PROPAGATION_REQUIRES_NEW 事务没有产生作用

    最近在做事务添加时  发现自己的事务没有新建,上网查到   仅用作收藏. 其二  注意  事务的注解  应该在 内层的事务上面 一.描述 Spring遇到嵌套事务时,当被嵌套的事务被定义为" ...

  5. 添加PROPAGATION_REQUIRES_NEW 事务没有产生作用

    最近在做事务添加时  发现自己的事务没有新建,上网查到   仅用作收藏. 其二  注意  事务的注解  应该在 内层的事务上面 一.描述Spring遇到嵌套事务时,当被嵌套的事务被定义为“PROPAG ...

  6. SpringBoot整合MongoDB,在多数据源下实现事务回滚。

    项目中用到了MongoDB,准备用来存储业务数据,前提是要实现事务,保证数据一致性,MongoDB从4.0开始支持事务,提供了面向复制集的多文档事务特性.能满足在多个操作,文档,集合,数据库之间的事务 ...

  7. 脱离 Spring 实现复杂嵌套事务,之一(必要的概念)

    事务传播行为种类 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为, 它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播: 表1事务传播行为类型 事务 ...

  8. 关于Spring 事务管理传播属性的配置及作用-嵌套事务

    先了解事务的7种传播属性: PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务.这是最常见的选择. PROPAGATION_SUPPORTS -- 支持当前 ...

  9. 事务之六:spring 嵌套事务

    一.基本概念 事务的隔离级别,事务传播行为见<事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件) > 二. 嵌套事务示例 2.1.Pro ...

随机推荐

  1. 【React踩坑记一】React项目中禁用浏览器双击选中文字的功能

    常规项目,我们只需要给标签加一个onselectstart事件,return false就可以 例: <div onselectstart="return false;" & ...

  2. 七天学会NodeJS——第一天

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:http://nqdeng.github.io/7-days-nodejs Node.js 是一个能 ...

  3. 【杂项】关于NOIP2018复赛若干巧合的声明

    导言 参加NOIP2018时本人学龄只有两个月,却斩获了省一等奖,保送了重点中学,这看上去是个我创造的神话,然而,在我自己心中,我认为这只是个巧合(其实我认为运气也是实力的一部分),接下来,我将说明一 ...

  4. MyISAM和InnoDB在索引上的差别及其它区别

    首先我们知道MyISM和InnoDB索引都是由B+树实现的,但在索引管理数据方式上却有所不同. InnoDB是聚集索引,数据文件是和(主键)索引绑在一起的,即索引 + 数据 = 整个表数据文件,通过主 ...

  5. 99% 的人都不知道的 Kubernetes 网络疑难杂症排查方法

    原文链接:Kubernetes 网络疑难杂症排查分享 大家好,我是 roc,来自腾讯云容器服务 (TKE) 团队,经常帮助用户解决各种 K8S 的疑难杂症,积累了比较丰富的经验,本文分享几个比较复杂的 ...

  6. JS鼠标吸粉特效

    HTML <canvas id=canvas></canvas> CSS * { margin: 0; padding: 0; } html { overflow: hidde ...

  7. (十一)c#Winform自定义控件-列表

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...

  8. js高程3--面向对象的程序设计--创建对象

    创建对象 这是js高程3--第6章面向对象的程序设计--第二节创建对象的总结与自己的理解,每一种模式都有自己的优点与缺点,搞清楚它们出现的历史原因,优缺点,我们才能使用的更加游刃有余! 本片文章并没有 ...

  9. windows下用GCC编译DLL

    此程序有3个文件,分别为 export.h .export.c .main.c export.h 文件内容 /*此头很有必要,别人在调用的时候知道有哪些方法*/ #ifdef BUILD_DLL #d ...

  10. vscode保存代码,自动按照eslint规范格式化代码设置

    # vscode保存代码,自动按照eslint规范格式化代码设置 编辑器代码风格一致,是前端代码规范的一部分.同一个项目,或者同一个小组,保持代码风格一致很必要.就拿vue项目来说,之前做的几个项目, ...