PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED区别

来源:互联网 发布:linux中的find命令 编辑:程序博客网 时间:2024/06/05 18:54
 最容易弄混淆的其实是 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 特性呢, 我们用代码来说话 
    
Java代码  收藏代码
  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 又是怎么回事呢? 继续看代码 

Java代码  收藏代码
  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 如下 
Java代码  收藏代码
  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 

Java代码  收藏代码
  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, falsefalse, 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() 方法 

Java代码  收藏代码
  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 : 
  
2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+ 
3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0 


确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了. (全文完) 
0 0
原创粉丝点击