昨天在千年妖精麗晶大賓館,看見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再以頭撞墻了。