昨天在千年妖精麗晶大賓館,看見sparkle同學抱怨Spring的transaction template不好用。因為一些我沒有問的原因,他們不能使用聲明式事務(wù),所以就只剩下兩個選擇:
1。直接用hibernate事務(wù)。
2。用spring的TransactionTemplate。
直接用hibernate事務(wù)有以下問題:
1。代碼完全綁定在Hibernate上。
2。自己控制事務(wù)難度比較大,不容易處理得好。
對第二點,很多人可能都不以為然,不就是一個beginTransaction和一個commit(), rollback()么?太門縫里看人了吧?
我就舉個sparkle同學的小弟寫的代碼吧:
try{
PetBean pet =
;

beginTransaction();
ssn.delete(
);

commit();
petLog(
);
}
catch(Exception e){
rollback();
throw e;
}
一個大的try-catch塊,在出現(xiàn)任何異常的時候都rollback。
我想會這么寫的人應(yīng)該不在少數(shù)。同志們,革命不是請客吃飯那么簡單地。
問題在哪?
1。最嚴重的。try里面一旦有Error拋出,rollback()就不會被執(zhí)行。sparkle同學說,出了Error我們就不管了。可以,反正出Error的幾率大概很小。所以你的軟件可以說“大多數(shù)情況是可以工作地”。
2。這塊代碼最終拋出Exception!如果外面直接套一個函數(shù)的話,簽名上就得寫"throws Exception"。這種函數(shù)就一個字:“害群之馬”。你讓調(diào)用者根本不知道會出什么異常,籠統(tǒng)地告訴人家“什么情況都可能發(fā)生”可不是負責任的態(tài)度。
3。這個代碼依賴于rollback()的特定實現(xiàn)。因為一旦exception是在beginTransaction()之前或者beginTransaction()時候拋出的,那么本來不應(yīng)該調(diào)用rollback()的。調(diào)用
rollback()會出什么結(jié)果呢?如果rollback()不檢查當前是否在事務(wù)中,就壞菜了。而且,就算rollback()做這個檢查,嵌套事務(wù)
也會把一切搞亂。因為很有可能整塊代碼是處在另外一個大的事務(wù)中的。調(diào)用我們的代碼在我們拋出異常的時候,也許會選擇redo,或者修復(fù)一些東西,不見得總是選擇回
滾它那一層的事務(wù)的,不分青紅皂白地就rollback上層事務(wù),這個代碼的健壯性真的很差。
看,小小一段代碼,bug和潛在問題如此之多。你還說自己寫事務(wù)控制簡單嗎?
真正健壯的,不對外界和調(diào)用者有多余的假設(shè)依賴的代碼,可以這樣寫:
PetBean pet =
;

beginTransaction();
ok = false;
try{
ssn.delete(
);

ok = true;
commit();
petLog(
);
}
finally{
if(!ok)
rollback();
}
放棄try-catch,改用try-finally。這樣就不需要捕獲異常再拋出那么麻煩。然后用一個bool變量來告訴finally塊是否需要回滾。
這個代碼不難理解,但是如果處處都用這個代碼,也夠丑陋的。
既然已經(jīng)用了spring,為什么不用spring的TransactionTemplate呢?用Spring TransactionTemplate(下面簡稱tt)的好處如下:
1。事務(wù)代碼不依賴hibernate,便于移植。
2。自動得到異常安全,健壯的事務(wù)處理。寫代碼的時候幾乎可以完全忘記事務(wù)的存在。
當然,sparkle同學有他的道理。使用spring
tt需要實現(xiàn)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]呀。其它還有一些變體,比如每個結(jié)果用一個Object[],或者定義一個通用的Ref類來支持"get()"和"set()"。
可是,這么多的方案,sparkle同學都不滿意。也是,這些方案都免不了類型不安全的down cast。而處理原始類型的結(jié)果還需要裝箱!
因為這些原因,我構(gòu)思了一個簡單的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和原始類型裝箱拆箱的麻煩。通過把事務(wù)之后要執(zhí)行的動作封裝在after()這個成員函數(shù)里面,我們可以方便地引用run()里面產(chǎn)生的結(jié)果。
下面看看Tx, TxBlock, TransactionBlockException這三個類的設(shè)計:
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()在事務(wù)開始前運行。after()在事務(wù)結(jié)束后運行。lastly()保證不管是否出現(xiàn)異常都會被執(zhí)行。
如此,一個薄薄的封裝,spring tt用起來庶幾不會讓sparkle再以頭撞墻了。