解惑spring嵌套事务

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

代码

/**

* Support a current transaction; create a new one if none exists.

* Analogous to EJB transaction attribute of the same name.

*

This is typically the default setting of a transaction definition.

*/

int PROPAGATION_REQUIRED = 0;

/**

* Support a current transaction, execute non-transitionally if none exists.

* Analogous to EJB transaction attribute of the same name.

*

Note: For transaction managers with transaction synchronization,

* PROPAGATION_SUPPORTS is slightly different from no transaction at all,

* as it defines a transaction scoop 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.

*

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 javax.transaction.TransactionManager 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.

*

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 javax.transaction.TransactionManager 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.

*

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中一共定义了六种事务传播属性,如果你觉得看起来不够直观,那么我来转贴一个满大街都有的翻译

引用

lPROPAGATION_REQUIRED --支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

lPROPAGATION_SUPPORTS --支持当前事务,如果当前没有事务,就以非事务方式执行。

lPROPAGATION_MANDATORY --支持当前事务,如果当前没有事务,就抛出异常。

lPROPAGATION_REQUIRES_NEW --新建事务,如果当前存在事务,把当前事务挂起。

lPROPAGATION_NOT_SUPPORTED --以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

lPROPAGATION_NEVER --以非事务方式执行,如果当前存在事务,则抛出异常。

lPROPAGATION_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里?

说了这么半天,那到底什么是真正的事务嵌套呢,解释之前我们来看一下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. if the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of the outer transaction, so it will only be committed at the end 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启动一个新的,不依赖于环境的"内部"事务.这个事务将被完全 committed或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).

上面大致讲述了嵌套事务的使用场景,下面我们来看如何在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 =

newTransactionStatus(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.

doBegin(transaction, definition);

boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);

return newTransactionStatus(definition, transaction, true, newSynchronization, 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都是它的子类:

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

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

3.Connection.getMetaData().supportsSavepoints()必须为true,即jdbc drive必须支持JDBC 3.0确保以上条件都满足后,你就可以尝试使用PROPAGATION_NESTED了. (全文完)

java 事务嵌套_解惑 spring 嵌套事务相关推荐

  1. java 事务嵌套_嵌套事务回滚

    嵌套事务的回滚 2017年05月02日 16:33:32 阅读数:1974 嵌套事务和事务保存点的错误处理 对于嵌套事务. 1.外部起事务,内部起事务,内外都有Try Catch 内部出错:如果内部事 ...

  2. java spring 事务传播_实战Spring事务传播性与隔离性

    一.事务传播性 1.1 什么是事务的传播性 事务的传播性一般在事务嵌套时候使用,比如在事务A里面调用了另外一个使用事务的方法,那么这俩个事务是各自作为独立的事务执行提交,还是内层的事务合并到外层的事务 ...

  3. 解惑 spring 嵌套事务

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

  4. spring 事务 会话_测试Spring的“会话”范围

    spring 事务 会话 在基于Spring的Web应用程序中,bean的作用域可以是用户"会话". 从本质上讲,这意味着对会话范围Bean的状态更改仅在用户会话范围内可见. 本条 ...

  5. java 域模型_基于Spring实现领域模型模式 - RUP实践者指南 - JavaEye技术网站

    事务脚本.领域模型及表模块是Martin Fowler在<企业应用架构模式>中总结的三种领域逻辑组织模式.各有各的优点和缺点,这里不打算讨论它们各自的适用场景,只简单总结一下在应用领域模 ...

  6. springboot默认开启事务吗_香~Spring Boot 应用也可以有配置中心。

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 8:55 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | J ...

  7. java 路由框架_使用Spring框架和AOP实现动态路由

    本文的大体思路是展示了一次业务交易如何动态地为子系统处理过程触发业务事件.本文所示的例子使用Spring框架和Spring AOP有效地解耦业务服务和子系统处理功能.现在让我们仔细看看业务需求. 业务 ...

  8. java邮件支持_使用Spring的JAVA Mail支持简化邮件发送功能

    闲来无事,翻看<Spring in Action>,发现Spring集成了对JAVA Mail的支持,有点小激动的看了一遍,嗯,话说真的简单了很多. Spring的邮件发送的核心是Mail ...

  9. java 平滑升级_服务器spring boot版本,平滑升级

    1.在pom文件中加入: org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-s ...

最新文章

  1. WIRW:淡水分子微生物生态学综述
  2. SDL介绍和简单实用
  3. 直接插入排序(内部排序)
  4. 20155303狄惟佳预备作业三Linux学习笔记
  5. 10个有趣的Python教程,附视频讲解+练手项目。
  6. Dubbo(九)之注解配置
  7. EE JSP:使用自定义标签库生成动态内容
  8. Oracle 创建表空间、用户、权限_(plsql)
  9. LeetCode:917. 仅仅反转字母
  10. 停止使用C#异步流保存到磁盘
  11. Intellij Idea选中内容后Backspace删除无法使用,Ctrl+c/Ctrl+v/Ctrl+s/Ctrl+d等等快捷键无法使用的问题的解决
  12. bug篇——Windows启动redis报错Creating Server TCP listening socket 127.0.0.1:6379: bind: No error...
  13. 文件app如何连接服务器,如何实现app与服务器连接数据库
  14. jsonrpc php使用,Json-RPC 的 PHP 扩展 php-JsonRPC
  15. 程序员 文本编辑器 c语言,程序员必备的五款文本编辑器
  16. 程序读取凡人修仙传热度数据
  17. 当3D应用遇上云流送技术使用更方便
  18. 阿里服务器稳定性介绍
  19. 2.天猫商品数据爬虫(已模拟登录)
  20. Oracle问题:如何远程连接Oracle数据库

热门文章

  1. numa节点_NUMA架构下的内存访问延迟区别!
  2. 容器入门(3) - docker
  3. OpenShift 4 之增加 HTPasswd 方式的身份认证
  4. 将您的SQL Server工作负载迁移到PostgreSQL –第1部分
  5. 应用挂载beegfs指定目录_BeeGFS源码分析1-元数据服务概要分析
  6. winexec函数 执行powershell 脚本_丢掉 Powershell.exe 来渗透测试
  7. 64位电脑mysql_Windows 64位操作系统下安装和配置MySQL
  8. the deep ritz method论文梳理
  9. python numpy数组画图_python – Numpy和Pyplot的条件绘图
  10. springboot多环境配置_编辑 SpringBoot|第五章:多环境配置