昨天在千年妖精丽晶大宾馆,看见sparkle同学抱怨Spring的transaction template不好用。因为一些我没有问的原因,他们不能使用声明式事务,所以就只剩下两个选择:
1。直接用hibernate事务。
2。用spring的TransactionTemplate。

直接用hibernate事务有以下问题:
1。代码完全绑定在Hibernate上。
2。自己控制事务难度比较大,不容易处理得好。

对第二点,很多人可能都不以为然,不就是一个beginTransaction和一个commit(), rollback()么?太门缝里看人了吧?

我就举个sparkle同学的小弟写的代码吧:
try {
   PetBean pet 
=  ;
   
   beginTransaction();
    ssn.delete();
    
    commit();
    petLog();
  }
  
catch (Exception e){
     rollback();
     
throw  e;
  }

一个大的try-catch块,在出现任何异常的时候都rollback。

我想会这么写的人应该不在少数。同志们,革命不是请客吃饭那么简单地。

问题在哪?

1。最严重的。try里面一旦有Error抛出,rollback()就不会被执行。sparkle同学说,出了Error我们就不管了。可以,反正出Error的几率大概很小。所以你的软件可以说“大多数情况是可以工作地”。

2。这块代码最终抛出Exception!如果外面直接套一个函数的话,签名上就得写"throws Exception"。这种函数就一个字:“害群之马”。你让调用者根本不知道会出什么异常,笼统地告诉人家“什么情况都可能发生”可不是负责任的态度。

3。这个代码依赖于rollback()的特定实现。因为一旦exception是在beginTransaction()之前或者beginTransaction()时候抛出的,那么本来不应该调用rollback()的。调用 rollback()会出什么结果呢?如果rollback()不检查当前是否在事务中,就坏菜了。而且,就算rollback()做这个检查,嵌套事务 也会把一切搞乱。因为很有可能整块代码是处在另外一个大的事务中的。调用我们的代码在我们抛出异常的时候,也许会选择redo,或者修复一些东西,不见得总是选择回 滚它那一层的事务的,不分青红皂白地就rollback上层事务,这个代码的健壮性真的很差。

看,小小一段代码,bug和潜在问题如此之多。你还说自己写事务控制简单吗?

真正健壮的,不对外界和调用者有多余的假设依赖的代码,可以这样写:

 PetBean pet  =  ;
  
 beginTransaction();
 ok 
=   false ;
 
try {
    ssn.delete();
    
    ok 
=   true ;
    commit();
   
    petLog();
  }
  
finally {
     
if ( ! ok)
        rollback();
  }

放弃try-catch,改用try-finally。这样就不需要捕获异常再抛出那么麻烦。然后用一个bool变量来告诉finally块是否需要回滚。

这个代码不难理解,但是如果处处都用这个代码,也够丑陋的。

既然已经用了spring,为什么不用spring的TransactionTemplate呢?用Spring TransactionTemplate(下面简称tt)的好处如下:
1。事务代码不依赖hibernate,便于移植。
2。自动得到异常安全,健壮的事务处理。写代码的时候几乎可以完全忘记事务的存在。

当然,sparkle同学有他的道理。使用spring tt需要实现TransactionCallback接口。而java的匿名类语法非常繁琐。更可恨的是,匿名类只能引用定义成final的局部变量,这 样在从tt里面往外传递返回值的时候就非常不方便。我们可能需要这么写:
// xxx
  Object[] result  =  (Object[])tt.execute( new  TransactionCallback() {
      
public  Object doInTransaction(TransactionStatus status) {
          Object obj1 
=   new  Integer(resultOfUpdateOperation1());
          Object obj2 
=  resultOfUpdateOperation2();
          
return   new  Objetct[]{obj1,obj2};
      }
  });
  System.out.println(((Integer)result[
0 ]).intValue() + 1 );
  System.out.println(result[
1 ]);

多么丑陋的result[0], result[1]呀。其它还有一些变体,比如每个结果用一个Object[],或者定义一个通用的Ref类来支持"get()"和"set()"。

可是,这么多的方案,sparkle同学都不满意。也是,这些方案都免不了类型不安全的down cast。而处理原始类型的结果还需要装箱!


因为这些原因,我构思了一个简单的spring tt的wrapper。一个Tx类。这个Tx类可以这么用:
// xxx
new  Tx(){
  
private  result0;
  
private  String result1;
  
protected   void  run(){
      result0 
=  resultOfUpdateOperation1();
      result1 
=  resultOfUpdateOperation2();
  }
  
protected  Object after(){
    System.out.println(result0
+ 1 );
    System.out.println(result1);
    
return   null ;
  }
}.exec(tt);

通过把局部变量定义成Tx类的成员变量,我们绕过了downcast和原始类型装箱拆箱的麻烦。通过把事务之后要执行的动作封装在after()这个成员函数里面,我们可以方便地引用run()里面产生的结果。


下面看看Tx, TxBlock, TransactionBlockException这三个类的设计:

public   abstract   class  TxBlock  implements  TransactionCallback{
  
protected   void  before()
  
throws  Throwable{}

  
protected   abstract   void  run(TransactionStatus status);
  
protected  Object after()
  
throws  Throwable{
    
return   null ;
  }
  
protected   void  lastly(){}
  
public   final  Object exec(TransactionTemplate tt){
    
try {
      before();
      tt.execute(
this );
      
return  after();
    }
    
catch (RuntimeException e){
      
throw  e;
    }
    
catch (Error e){
      
throw  e;
    }
    
catch (Throwable e){
      
throw   new  TransactionBlockException(e);
    }
    
finally {
      lastly();
    }
  }
  
public  Object doInTransaction(TransactionStatus status){
    run(status);
    
return   null ;
  }
}



public   abstract   class  Tx  extends  TxBlock{
  
protected   abstract   void  run();
  
protected   void  run(TransactionStatus status) {
    run();
  }
}

public   class  TransactionBlockException  extends  NestedRuntimeException {

  
public  TransactionBlockException(String arg0, Throwable arg1) {
    
super (arg0, arg1);
  }

  
public  TransactionBlockException(String arg0) {
    
super (arg0);
  }
  
public  TransactionBlockException(Throwable e){
    
this ( " error in transaction block " , e);
  }
}

所有的代码都在这了(除了import)。
这个小工具除了after(),还支持before(), lastly()。before()在事务开始前运行。after()在事务结束后运行。lastly()保证不管是否出现异常都会被执行。

如此,一个薄薄的封装,spring tt用起来庶几不会让sparkle再以头撞墙了。