改变 Grails 的缺省事务行为

Grails通过Service让我们不用进行任何配置就享受到了声明式事务这一特性。可是,这也让我们不得不接受它预先设置的事务行 为:PROPAGATION_REQUIRED。要是我想使用其它的事务行为该如何做呢?没关系,使用@Transactional进行配置就行了。 

废话少说,直奔正题。下例就展示了自定义Grails事务行为的做法: 

Domain1Service,其中的saveDomain1负责保存Domain1,该方法的行为是始终都启动一个新事务。在这个方法的末端抛出了一个RuntimeException,这将让Grails回滚事务。这是为了证明咱们的配置确实生效了。 

Java代码   收藏代码
  1. package cases     
  2.     
  3. import org.springframework.transaction.annotation.*     
  4.     
  5. class Domain1Service {     
  6.          
  7.     def domain1Service     
  8.     
  9.     static transactional = true    
  10.          
  11.     @Transactional(propagation = Propagation.REQUIRES_NEW)     
  12.     def saveDomain1() {     
  13.         new Domain1().save()     
  14.         throw new RuntimeException("save domain1 failed!")     
  15.     }     
  16. }    


Domain2Service,其中的saveTwoDomains负责保存Domain2,同时它还会调用Domain1Service的saveDomain1,它使用缺省的事务行为。 

Java代码   收藏代码
  1.   package cases     
  2.     
  3. class Domain2Service {     
  4.     def domain1Service     
  5.     
  6.     static transactional = true    
  7.     
  8.     def saveTwoDomains() {     
  9.         new Domain2().save()     
  10.         try{     
  11.             domain1Service.saveDomain1()     
  12.         }catch(e){     
  13.             println 'save domain1 failed!'    
  14.         }     
  15.     }     
  16. }    

这里没有给出Domain1和Domain2的代码,因为要创建它们并不难,而且在整个过程中它们的内容也不会有任何决定性的影响。 

按照代码的意思,调用Domain2Service的saveTwoDomains,如果数据库里有Domain2而没有Domain1,那么我们 的配置就达到目的了。因为进入Domain1Service的saveDomain1之后,将产生一个新事务,而它在方法结束时会回滚,因此 Domain1不会保存。而saveTwoDomains,因为捕获并处理了saveDomain1的RuntimeException,因此不会在抛出 运行时异常,所以自己的事务可以正常结束,故Domain2得以保存。 

要测试这个并不难,写一个集成测试就可以了。但是这次我太懒了,想直接用肉眼看看数据库的结果。记住,这不是推荐的做法,只适用于一次性的探索性测试。 

首先,将DataSource配置成MySQL;接着,启动Grails Console。没错,我就是要在这个Console里直接获得咱们定义的Bean,然后手工触发方法的执行。 

在Console里输入:ctx.getBean("domain2Service").saveTwoDomains() 

正如期望的那样,Domain1失败的信息打印出来了。检查一下数据库吧,Domain2已经保存,而Domain1则完全不见踪影。配置生效了! 

如果你还不放心,那么可以把saveDomain1中那句抛出RuntimeException的话注释掉,然后重新启动Console进行测试。这时,Domain1和Domain2都能正常保存。 

一切都很美好,是不是。但请注意Spring文档里这样一段话: 

引用

  在代理模式(这是缺省的)下,只有通过代理传入的外部方法调用才会被拦截。这意味着,自身的方法调用,在效果上是,目标对象内的方法A调用方法B,在运行时将不会产生实际的事务,即便被调用方法已经使用@Transactional进行了标记。 


通过例子来说明这段话吧。对Domain1Service稍作修改: 

Java代码   收藏代码
  1.   package cases     
  2.     
  3. import org.springframework.transaction.annotation.*     
  4.     
  5. class Domain1Service {     
  6.     
  7.     static transactional = true    
  8.          
  9.     @Transactional(propagation = Propagation.REQUIRES_NEW)     
  10.     def saveDomain1() {     
  11.         new Domain1().save()     
  12.         throw new RuntimeException("save domain1 failed!")     
  13.     }     
  14.          
  15.     def saveTwoDomains() {     
  16.         new Domain2().save()     
  17.         try{     
  18.             saveDomain1()     
  19.         }catch(e){     
  20.             println 'save domain1 failed!'    
  21.         }     
  22.     }     
  23. }    

这次在Console中运行 

ctx.getBean("domain1Service").saveTwoDomains() 

检查一下数据库,你会发现两个对象都保存了,这显然说明saveDomain1的事务配置并没有其作用。那么按照上面那句话和咱们刚才的实验,这是否就意味着,如果今后有类似情况,咱们就得写成2个类呢?不尽然。 

根据Spring文档里的那段话,其关键在于只要让方法调用经过代理就行了。将上述代码saveTwoDomains中的代码 “saveDomain1()”换成“domain1Service.saveDomain1()”,然后重新运行测试,你会发现事务配置又生效了。 

同时,这个实验也说明了,注入的对象并不是实际的Service对象。还是用代码来验证一下: 


Java代码   收藏代码
  1. package cases     
  2.     
  3. import org.springframework.transaction.annotation.*     
  4.     
  5. class Domain1Service {     
  6.          
  7.     def domain1Service     
  8.     ...         
  9.     def test(){     
  10.         println this.class    
  11.         println domain1Service.class    
  12.         this == domain1Service     
  13.     }     
  14. }    


运行测试,你会发现结果为false,而且打印类似下列语句: 

class cases.Domain1Serviceclass cases.Domain1Service$$EnhancerByCGLIB$$69fcb82


补充:

Java代码   收藏代码
  1.   package cases     
  2.     
  3. import org.springframework.transaction.annotation.*     
  4.     
  5. class Domain1Service {     
  6.     
  7.     static transactional = true    
  8.          
  9.          
  10.     def saveDomain1() {     
  11.         new Domain1().save()     
  12.         throw new RuntimeException("save domain1 failed!")     
  13.     }     
  14.          
  15.     def saveTwoDomains() {     
  16.         new Domain2().save()     
  17.         try{     
  18.             Domain1Service.saveDomain1()     
  19.         }catch(e){     
  20.             println 'save domain1 failed!'    
  21.         }     
  22.     }     
  23. }  
  24. 即使不对saveDomain1方法指定@Transactional(propagation = Propagation.REQUIRES_NEW),只要Domain1Service.saveDomain1()仍然会对saveDomain1()方法内部另起一个事物。如上可达到相同的效果

你可能感兴趣的:(改变 Grails 的缺省事务行为)