Spring事务详解

转载地址:http://www.iteye.com/topic/78674?page=1

 Spring声明式事务让我们从复杂的事务处理中得到解脱。使得我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码。
我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性。事务属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务只读标志组成。我们在进行事务划分时,需要进行事务定义,也就是配置事务的属性。
Spring在TransactionDefinition接口中定义这些属性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事务管理的核心接口。

Java代码 复制代码
  1. TransactionDefinition   
  2. public interface TransactionDefinition {   
  3.     int getPropagationBehavior();   
  4.     int getIsolationLevel();   
  5.     int getTimeout();   
  6.     boolean isReadOnly();   
  7. }  
TransactionDefinition
public interface TransactionDefinition {
    int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
}



getTimeout()方法,它返回事务必须在多少秒内完成。
isReadOnly(),事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的。
getIsolationLevel()方法返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据。

在TransactionDefinition接口中定义了五个不同的事务隔离级别
ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
  例如:
  Mary的原工资为1000,财务人员将Mary的工资改为了8000,但未提交事务

Java代码 复制代码
  1. Connection con1 = getConnection();   
  2. con.setAutoCommit(false);   
  3. update employee set salary = 8000 where empId ="Mary";  
  Connection con1 = getConnection();
  con.setAutoCommit(false);
  update employee set salary = 8000 where empId ="Mary";


与此同时,Mary正在读取自己的工资

Java代码 复制代码
  1. Connection con2 = getConnection();   
  2. select  salary from employee where empId ="Mary";   
  3. con2.commit();  
Connection con2 = getConnection();
select  salary from employee where empId ="Mary";
con2.commit();



Mary发现自己的工资变为了8000,欢天喜地!
而财务发现操作有误,而回滚了事务,Mary的工资又变为了1000

Java代码 复制代码
  1. //con1   
  2.   con1.rollback();  
//con1
  con1.rollback();


像这样,Mary记取的工资数8000是一个脏数据。

ISOLATION_READ_COMMITTED  保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。

ISOLATION_REPEATABLE_READ  这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

在事务1中,Mary 读取了自己的工资为1000,操作并没有完成

Java代码 复制代码
  1. con1 = getConnection();   
  2. select salary from employee empId ="Mary";  
con1 = getConnection();
select salary from employee empId ="Mary";



在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.

Java代码 复制代码
  1. con2 = getConnection();   
  2. update employee set salary = 2000;   
  3. con2.commit();  
con2 = getConnection();
update employee set salary = 2000;
con2.commit();



在事务1中,Mary 再次读取自己的工资时,工资变为了2000

Java代码 复制代码
  1. //con1   
  2. select salary from employee empId ="Mary";  
//con1
select salary from employee empId ="Mary";



在一个事务中前后两次读取的结果并不致,导致了不可重复读。
使用ISOLATION_REPEATABLE_READ可以避免这种情况发生。

ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

目前工资为1000的员工有10人。
事务1,读取所有工资为1000的员工。

Java代码 复制代码
  1. con1 = getConnection();   
  2. Select * from employee where salary =1000;  
con1 = getConnection();
Select * from employee where salary =1000;

共读取10条记录

这时另一个事务向employee表插入了一条员工记录,工资也为1000

Java代码 复制代码
  1. con2 = getConnection();   
  2. Insert into employee(empId,salary) values("Lili",1000);   
  3. con2.commit();  
con2 = getConnection();
Insert into employee(empId,salary) values("Lili",1000);
con2.commit();



事务1再次读取所有工资为1000的员工

Java代码 复制代码
  1. //con1   
  2. select * from employee where salary =1000;  
//con1
select * from employee where salary =1000;



共读取到了11条记录,这就产生了幻像读。
ISOLATION_SERIALIZABLE能避免这样的情况发生。但是这样也耗费了最大的资源。

getPropagationBehavior()返回事务的传播行为,由是否有一个活动的事务来决定一个事务调用。

在TransactionDefinition接口中定义了七个事务传播行为

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED   
  2. methodA{   
  3. ……   
  4. methodB();   
  5. ……   
  6. }   
  7.   
  8. //事务属性 PROPAGATION_REQUIRED   
  9. methodB{   
  10.    ……   
  11. }  
//事务属性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}

//事务属性 PROPAGATION_REQUIRED
methodB{
   ……
}


使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。

单独调用methodB方法

Java代码 复制代码
  1. main{   
  2.   metodB();   
  3. }  
main{
  metodB();
}


相当于

Java代码 复制代码
  1. Main{   
  2. Connection con=null;   
  3.   
  4.    rry{   
  5.       con = getConnection();   
  6.       con.setAutoCommit(false);   
  7. //方法调用   
  8. methodB();   
  9. //提交事务   
  10. con.commit();   
  11. }   
  12. Catch(RuntimeException ex){   
  13.   //回滚事务   
  14.   con.rollback();     
  15. }   
  16. finally{   
  17.   //释放资源   
  18.   closeCon();   
  19. }   
  20. }  
Main{
Connection con=null;

   rry{
      con = getConnection();
      con.setAutoCommit(false);
//方法调用
methodB();
//提交事务
con.commit();
}
Catch(RuntimeException ex){
  //回滚事务
  con.rollback();  
}
finally{
  //释放资源
  closeCon();
}
}


Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。

单独调用MethodA时,在MethodA内又会调用MethodB.

执行效果相当于

Java代码 复制代码
  1. main{   
  2.    Connection con = null;   
  3.    try{   
  4.       con = getConnection();   
  5.       methodA();   
  6.       con.commit();   
  7. }   
  8. cathc(RuntimeException ex){   
  9.  con.rollback();   
  10. }   
  11. finally{   
  12.   closeCon();   
  13. }    
  14. }  
main{
   Connection con = null;
   try{
      con = getConnection();
      methodA();
      con.commit();
}
cathc(RuntimeException ex){
 con.rollback();
}
finally{
  closeCon();
} 
}


调用MethodA时,环境中没有事务,所以开启一个新的事务.
当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务。

PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED    
  2. methodA(){   
  3.   methodB();   
  4. }   
  5.   
  6. //事务属性 PROPAGATION_SUPPORTS    
  7. methodB(){   
  8.   ……   
  9. }  
//事务属性 PROPAGATION_REQUIRED 
methodA(){
  methodB();
}

//事务属性 PROPAGATION_SUPPORTS 
methodB(){
  ……
}


单纯的调用methodB时,methodB方法是非事务的执行的。
当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED    
  2. methodA(){   
  3.   methodB();   
  4. }   
  5.   
  6. //事务属性 PROPAGATION_MANDATORY    
  7. methodB(){   
  8.   ……   
  9. }  
//事务属性 PROPAGATION_REQUIRED 
methodA(){
  methodB();
}

//事务属性 PROPAGATION_MANDATORY 
methodB(){
  ……
}


当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常
throw new IllegalTransactionStateException("Transaction propagation 'mandatory' but no existing transaction found");

当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED    
  2. methodA(){   
  3.   doSomeThingA();   
  4. methodB();   
  5. doSomeThingB();   
  6. }   
  7.   
  8. //事务属性 PROPAGATION_REQUIRES_NEW    
  9. methodB(){   
  10.   ……   
  11. }  
//事务属性 PROPAGATION_REQUIRED 
methodA(){
  doSomeThingA();
methodB();
doSomeThingB();
}

//事务属性 PROPAGATION_REQUIRES_NEW 
methodB(){
  ……
}


当单独调用methodB时,相当于把methodb声明为REQUIRED。开启一个新的事务,事务地执行。

当调用methodA时

Java代码 复制代码
  1. main(){   
  2.   methodA();   
  3. }  
main(){
  methodA();
}

情况有些大不一样.相当于下面的效果。

Java代码 复制代码
  1. main(){   
  2.  TransactionManager tm = null;   
  3. try{   
  4.   //获得一个JTA事务管理器   
  5.    tm = getTransactionManager();   
  6.    tm.begin();//开启一个新的事务   
  7.    Transaction ts1 = tm.getTransaction();   
  8.    doSomeThing();   
  9.    tm.suspend();//挂起当前事务   
  10.    try{   
  11.      tm.begin();//重新开启第二个事务   
  12.      Transaction ts2 = tm.getTransaction();   
  13.      methodB();   
  14.      ts2.commit();//提交第二个事务   
  15.         
  16.    }   
  17.   Catch(RunTimeException ex){   
  18.      ts2.rollback();//回滚第二个事务   
  19.   }   
  20.   finally{   
  21.     //释放资源   
  22.   }   
  23.    //methodB执行完后,复恢第一个事务   
  24.    tm.resume(ts1);   
  25. doSomeThingB();   
  26.    ts1.commit();//提交第一个事务   
  27. }   
  28. catch(RunTimeException ex){   
  29.   ts1.rollback();//回滚第一个事务   
  30. }   
  31. finally{   
  32.   //释放资源   
  33. }   
  34. }  
main(){
 TransactionManager tm = null;
try{
  //获得一个JTA事务管理器
   tm = getTransactionManager();
   tm.begin();//开启一个新的事务
   Transaction ts1 = tm.getTransaction();
   doSomeThing();
   tm.suspend();//挂起当前事务
   try{
     tm.begin();//重新开启第二个事务
     Transaction ts2 = tm.getTransaction();
     methodB();
     ts2.commit();//提交第二个事务
     
   }
  Catch(RunTimeException ex){
     ts2.rollback();//回滚第二个事务
  }
  finally{
    //释放资源
  }
   //methodB执行完后,复恢第一个事务
   tm.resume(ts1);
doSomeThingB();
   ts1.commit();//提交第一个事务
}
catch(RunTimeException ex){
  ts1.rollback();//回滚第一个事务
}
finally{
  //释放资源
}
}


在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。
使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。

PROPAGATION_NOT_SUPPORTED  总是非事务地执行,并挂起任何存在的事务。

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED    
  2. methodA(){   
  3.   doSomeThingA();   
  4. methodB();   
  5. doSomeThingB();   
  6. }   
  7.   
  8. //事务属性 PROPAGATION_NOT_SUPPORTED    
  9. methodB(){   
  10.   ……   
  11. }  
//事务属性 PROPAGATION_REQUIRED 
methodA(){
  doSomeThingA();
methodB();
doSomeThingB();
}

//事务属性 PROPAGATION_NOT_SUPPORTED 
methodB(){
  ……
}


当单独调用methodB时,不启用任何事务机制,非事务地执行。
当调用methodA时,相当于下面的效果

Java代码 复制代码
  1. main(){   
  2.  TransactionManager tm = null;   
  3. try{   
  4.   //获得一个JTA事务管理器   
  5.    tm = getTransactionManager();   
  6.    tm.begin();//开启一个新的事务   
  7.    Transaction ts1 = tm.getTransaction();   
  8.    doSomeThing();   
  9.    tm.suspend();//挂起当前事务   
  10.      methodB();   
  11.    //methodB执行完后,复恢第一个事务   
  12.    tm.resume(ts1);   
  13. doSomeThingB();   
  14.    ts1.commit();//提交第一个事务   
  15. }   
  16. catch(RunTimeException ex){   
  17.   ts1.rollback();//回滚第一个事务   
  18. }   
  19. finally{   
  20.   //释放资源   
  21. }   
  22. }  
main(){
 TransactionManager tm = null;
try{
  //获得一个JTA事务管理器
   tm = getTransactionManager();
   tm.begin();//开启一个新的事务
   Transaction ts1 = tm.getTransaction();
   doSomeThing();
   tm.suspend();//挂起当前事务
     methodB();
   //methodB执行完后,复恢第一个事务
   tm.resume(ts1);
doSomeThingB();
   ts1.commit();//提交第一个事务
}
catch(RunTimeException ex){
  ts1.rollback();//回滚第一个事务
}
finally{
  //释放资源
}
}

使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED    
  2. methodA(){   
  3.   doSomeThingA();   
  4. methodB();   
  5. doSomeThingB();   
  6. }   
  7.   
  8. //事务属性 PROPAGATION_NEVER    
  9. methodB(){   
  10.   ……   
  11. }  
//事务属性 PROPAGATION_REQUIRED 
methodA(){
  doSomeThingA();
methodB();
doSomeThingB();
}

//事务属性 PROPAGATION_NEVER 
methodB(){
  ……
}

单独调用methodB,则非事务的执行。
调用methodA则会抛出异常
throw new IllegalTransactionStateException(
"Transaction propagation 'never' but existing transaction found");


PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。

使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;
而nestedTransactionAllowed属性值默认为false;

Java代码 复制代码
  1. //事务属性 PROPAGATION_REQUIRED    
  2. methodA(){   
  3.   doSomeThingA();   
  4. methodB();   
  5. doSomeThingB();   
  6. }   
  7.   
  8. //事务属性 PROPAGATION_NESTED   
  9. methodB(){   
  10.   ……   
  11. }  
//事务属性 PROPAGATION_REQUIRED 
methodA(){
  doSomeThingA();
methodB();
doSomeThingB();
}

//事务属性 PROPAGATION_NESTED
methodB(){
  ……
}


如果单独调用methodB方法,则按REQUIRED属性执行。

如果调用methodA方法,相当于下面的效果

Java代码 复制代码
  1. main(){   
  2. Connection con = null;   
  3. Savepoint savepoint = null;   
  4. try{   
  5.   con = getConnection();   
  6.   con.setAutoCommit(false);   
  7.   doSomeThingA();   
  8.   savepoint = con2.setSavepoint();   
  9.   try  
  10.       methodB();   
  11.   }catch(RuntimeException ex){   
  12.      con.rollback(savepoint);   
  13.   }   
  14.   finally{   
  15.     //释放资源   
  16.   }   
  17.   
  18.   doSomeThingB();   
  19.   con.commit();   
  20. }   
  21. catch(RuntimeException ex){   
  22.   con.rollback();   
  23. }   
  24. finally{   
  25.   //释放资源   
  26. }   
  27. }  
main(){
Connection con = null;
Savepoint savepoint = null;
try{
  con = getConnection();
  con.setAutoCommit(false);
  doSomeThingA();
  savepoint = con2.setSavepoint();
  try
      methodB();
  }catch(RuntimeException ex){
     con.rollback(savepoint);
  }
  finally{
    //释放资源
  }

  doSomeThingB();
  con.commit();
}
catch(RuntimeException ex){
  con.rollback();
}
finally{
  //释放资源
}
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。

嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。

PROPAGATION_REQUIRED应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。

 

 

 

 

 

 

解惑 spring 嵌套事务

转载地址:http://www.iteye.com/topic/35907?page=1

/**
  * @author 王政
  * @date 2006-11-24
  * @note 转载请注明出处
  */

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

Java代码 复制代码
  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;  
/**
	 * 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 里 ?

     说了这么半天, 那到底什么是真正的事务嵌套呢, 解释之前我们来看一下  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 特性呢, 我们用代码来说话
   

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. }     
	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 又是怎么回事呢? 继续看代码

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. }     
	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 如下

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. }   
	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

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.         i
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值