3、事务的传播特性

转载地址1

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

其实这种是不认识 Spring 事务传播机制而造成的误解,Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:

  • int getPropagationBehavior():事务的传播行为
  • int getIsolationLevel():事务的隔离级别
  • int getTimeout():事务的过期时间
  • boolean isReadOnly():事务的读写特性。

很明显,除了事务的传播行为外,事务的其它特性 Spring 是借助底层资源的功能来完成的,Spring 无非只充当个代理的角色。但是事务的传播行为却是 Spring 凭借自身的框架提供的功能,是 Spring 提供给开发者最珍贵的礼物,讹传的说法玷污了 Spring 事务框架最美丽的光环。

所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持 7 种事务传播行为:

  • PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
  • PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

转载地址2

我们都知道事务的概念,那么事务的传播特性是什么呢?(此处着重介绍传播特性的概念,关于传播特性的相关配置就不介绍了,可以查看spring的官方文档)
在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。
如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。
以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernatesession传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。

默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承这样事务才会回滚!

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

转载地址3:

 

最近遇到了一个spring事务导致的问题,所以写了几个小程序了解了一下事务的传播特性,下面分别举例子分别看看事务的传播特性。

事务的几种传播特性

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

/**
 * TransactionTestService	 test1和test2配有事务(PROPAGATION_REQUIRED) */
public interface TransactionTestService {
  //事务属性 PROPAGATION_REQUIRED
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    test2();
  }
  //事务属性 PROPAGATION_REQUIRED
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }
}
 
/**
 * main
 */
public class TransactionTestMain {
 
  public static void main(String[] args) throws Exception{
    TransactionTestService transactionTestService = (TransactionTestService)context.getBean("transactionTestService");
    try {
      transactionTestService.test1();
    } catch (Exception e) {
    }
  }
}

上述代码中test1()和test2()都配有PROPAGATION_REQUIRED事务属性,test1()内部调用test2(),这样test1()和test2()方法将都处于同一事务之中,当在test2()中抛出异常,会导致test1()和test2()方法中的事务都回滚。

但是,如果test1()方法对调用test2()时捕获异常,结果会是怎样的呢? test1应该能正常写入没问题,那么test2呢?

//test1()中捕获test2()抛出的异常
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    try {
      test2();
    }catch (Exception e) {
    }
 
  }

最后的结果test2()也将会正常的写入。其实,上面情况中如果只是test1()配有PROPAGATION_REQUIRED事务属性,test2()不配置任何事务属性,发生的结果也是一致的。上面的情形相当于:

public static void main(String[] args) throws Exception{
    Connection con = null;
    try {
      con=getConnection();
      con.setAutoCommit(false);
      transactionTestService.test1(); //test1()和test2()处于同一事务之中
      con.commit();
    } catch (Exception e) {
      con.rollback();
    } finally {
      closeCon();
    }
  }

上述test1()和test2()是同一个类的两个方法,那么要是它们处于不同类呢?

//main函数不变,test1()中调用test2()地方换成调用另一个类中配有PROPAGATION_REQUIRED事务属性的方法
  //不对otherService.test2()捕获异常的情形就没必要说了,必定都回滚。
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    try {
      otherService.test2();  //PROPAGATION_REQUIRED事务属性
    } catch (Exception e) {
    }
  }
 
  //otherService.test2()
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }

上述同样捕获了异常,但是结果会怎样呢? 结果有点出乎意料,与之前test1(),test2()处于同一类的情形不同,这个时候,两个方法都将回滚,并且在调用test1()的地方会抛出下面异常。这是由于子事务在回滚的时候已经将主事务标记成了rollback-only,这样导致主事务在提交的时候就会抛出下面这个异常。 另外:如果otherService.test2()没有配置任何事务属性,那么test2()抛出异常的时候,将导致test1()和test2()都回滚。

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

上述情况网上查询到一种解决方式是在transactionManager中将globalRollbackOnParticipationFailure 设置为false(默认是true)。但是这样又带来另一个问题,子事务也给一并提交了(这个时候子事务产生异常,不想提交),具体的解决方式还没找到,但是我觉得不应该将这两个配置在同一事务中。

<bean id="transactionManager"    
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource">
            <ref local="dataSource" />
        </property>
        <property name="globalRollbackOnParticipationFailure" value="false" /> 
    </bean>

2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行

//TransactionTestService	 test1()配有事务(PROPAGATION_SUPPORTS)
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    throw new Exception();
  }
 
 
/**
 * main
 */
  public class TransactionTestMain {
 
    public static void main(String[] args) throws Exception{
      TransactionTestService transactionTestService = (TransactionTestService)context.getBean("transactionTestService");
      try {
        transactionTestService.test1();
      } catch (Exception e) {
      }
     }
  }

TransactionTestService的test1()配有PROPAGATION_SUPPORTS事务属性,我们知道这种情形下如果配的是PROPAGATION_REQUIRED事务属性,那么test1()将新建事务运行,但是PROPAGATION_SUPPORTS属性不会新建,这种情形下,test1()方法将正常提交。那如果是外层事务配有事务呢?如下所示,此种情形test2()将处于事务之中了。

//PROPAGATION_REQUIRED事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    test2();
  }
  //PROPAGATION_SUPPORTS事务属性
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }

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

//情形1:PROPAGATION_REQUIRED事务属性  情形2:不配任何事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    otherService.test2();
  }
  //otherService的test2()配置PROPAGATION_MANDATORY事务属性
  public void test2() throws Exception {
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }
 
//test1()处于情形2时抛出异常,test2()不能够提交,但是test1()却是可以成功提交的
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

上述情况,当test1()处于情形1时,会是的test1()和test2()都处于事务当中;test1()处于情形2时,就会抛异常,但是这个时候test2()不能够提交,但是test1()却是可以成功提交的。此外,还有一点,注意上述test2()是处于另一个类中的,如果是处于同一个类,那么PROPAGATION_MANDATORY不会因为外层是否有事务而抛异常。

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

//情形1:PROPAGATION_REQUIRED事务属性  情形2:不配任何事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
    otherService.test2();
  }
  //otherService的test2()配置PROPAGATION_REQUIRES_NEW事务属性
  public void test2() throws Exception {
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }

上述情况,test1()处于情形2时test2()新建事务,这点没有问题。那如果test1()处于情形1呢?也就是说test1()已经有事务了。结果是test2()处于新的事务中,怎么确定是处于新的事务中呢?看下面代码:

//test1配置PROPAGATION_REQUIRED事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
      otherService.test2();  //PROPAGATION_REQUIRES_NEW事务属性
  }
 
  //otherService.test2()
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }

如果是在同一事务中,那么情形将同于讲述PROPAGATION_REQUIRED属性时的情形,test1()和test2()都将回滚,并且抛出异常事务被打上rollback-only标记的异常。但是这里,结果就是test2()回滚,test1正常提交,所以otherService.test2()处于新的事务中。注意:上述情况test2()也是另一个类的方法,如果属于同一类,也就是test1()和test2()处于同一个类,test1()中调用test2(),那么配置的PROPAGATION_REQUIRES_NEW将无效(跟test2()什么事务属性都没配置一样)。但是如果是main函数中直接调用test2(),那么还是会起一个新的事务。

另外一种证明test1()和test2()处于不同事务的方式是,在test2()不抛出异常,然后再test1()调用了test2()之后,抛出异常,最后结果是()回滚,test2()正常提交。

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

//test1配置PROPAGATION_REQUIRED事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
      otherService.test2();  //PROPAGATION_NOT_SUPPORTED事务属性
  }
 
  //otherService.test2()
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }

如果otherService.test2() 没有配置事务属性,那么test2()抛出异常时,test1()和test2()都回滚。但是现在test2()配置了PROPAGATION_NOT_SUPPORTED事务属性,那么test2()将以非事务运行,而test1()继续运行在事务中,也就是说,此时,test1()回滚,test2()正常提交。注意:如果test1()和test2()在同一类中,那么test2()的PROPAGATION_NOT_SUPPORTED失效。

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

//test1配置PROPAGATION_REQUIRED事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
      otherService.test2();  //PROPAGATION_NEVER事务属性
  }
 
  //otherService.test2()
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
    throw new Exception();
  }
//test1()配置PROPAGATION_REQUIRED事务属性, otherService.test2()配置PROPAGATION_NEVER事务属性,将抛下面异常:
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

当然上述情况,如果是test1()和test2()在同一类中,那么PROPAGATION_NEVER也将失效。

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

最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED。PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint.。嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.

//test1配置PROPAGATION_REQUIRED事务属性
  public void test1() throws Exception{
    avInfoTaskTunnel.insertAvInfoTask();
      otherService.test2();  //PROPAGATION_NESTED事务属性
  }
 
  //otherService.test2()
  public void test2() throws Exception{
    avRequestTunnel.insertAvRequest();
  }

上述情况,如果otherService.test2()配置PROPAGATION_REQUIRES_NEW事务属性,这样test1()回滚,但是test2()正常提交,因为这是两个事务。但是如果otherService.test2()配置PROPAGATION_NESTED事务属性,那么test1()和test2()都将回滚。

转载于:https://my.oschina.net/u/3551274/blog/1795209

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值