昨天在千年妖精麗晶大賓館,看見sparkle同學抱怨Spring的transaction template不好用。因為一些我沒有問的原因,他們不能使用聲明式事務,所以就只剩下兩個選擇:
1。直接用hibernate事務。
2。用spring的TransactionTemplate。
直接用hibernate事務有以下問題:
1。代碼完全綁定在Hibernate上。
2。自己控制事務難度比較大,不容易處理得好。
對第二點,很多人可能都不以為然,不就是一個beginTransaction和一個commit(), rollback()么?太門縫里看人了吧?
我就舉個sparkle同學的小弟寫的代碼吧:
我想會這么寫的人應該不在少數。同志們,革命不是請客吃飯那么簡單地。
問題在哪?
1。最嚴重的。try里面一旦有Error拋出,rollback()就不會被執行。sparkle同學說,出了Error我們就不管了。可以,反正出Error的幾率大概很小。所以你的軟件可以說“大多數情況是可以工作地”。
2。這塊代碼最終拋出Exception!如果外面直接套一個函數的話,簽名上就得寫"throws Exception"。這種函數就一個字:“害群之馬”。你讓調用者根本不知道會出什么異常,籠統地告訴人家“什么情況都可能發生”可不是負責任的態度。
3。這個代碼依賴于rollback()的特定實現。因為一旦exception是在beginTransaction()之前或者beginTransaction()時候拋出的,那么本來不應該調用rollback()的。調用 rollback()會出什么結果呢?如果rollback()不檢查當前是否在事務中,就壞菜了。而且,就算rollback()做這個檢查,嵌套事務 也會把一切搞亂。因為很有可能整塊代碼是處在另外一個大的事務中的。調用我們的代碼在我們拋出異常的時候,也許會選擇redo,或者修復一些東西,不見得總是選擇回 滾它那一層的事務的,不分青紅皂白地就rollback上層事務,這個代碼的健壯性真的很差。
看,小小一段代碼,bug和潛在問題如此之多。你還說自己寫事務控制簡單嗎?
真正健壯的,不對外界和調用者有多余的假設依賴的代碼,可以這樣寫:
這個代碼不難理解,但是如果處處都用這個代碼,也夠丑陋的。
既然已經用了spring,為什么不用spring的TransactionTemplate呢?用Spring TransactionTemplate(下面簡稱tt)的好處如下:
1。事務代碼不依賴hibernate,便于移植。
2。自動得到異常安全,健壯的事務處理。寫代碼的時候幾乎可以完全忘記事務的存在。
當然,sparkle同學有他的道理。使用spring tt需要實現TransactionCallback接口。而java的匿名類語法非常繁瑣。更可恨的是,匿名類只能引用定義成final的局部變量,這 樣在從tt里面往外傳遞返回值的時候就非常不方便。我們可能需要這么寫:
可是,這么多的方案,sparkle同學都不滿意。也是,這些方案都免不了類型不安全的down cast。而處理原始類型的結果還需要裝箱!
因為這些原因,我構思了一個簡單的spring tt的wrapper。一個Tx類。這個Tx類可以這么用:
下面看看Tx, TxBlock, TransactionBlockException這三個類的設計:
這個小工具除了after(),還支持before(), lastly()。before()在事務開始前運行。after()在事務結束后運行。lastly()保證不管是否出現異常都會被執行。
如此,一個薄薄的封裝,spring tt用起來庶幾不會讓sparkle再以頭撞墻了。
1。直接用hibernate事務。
2。用spring的TransactionTemplate。
直接用hibernate事務有以下問題:
1。代碼完全綁定在Hibernate上。
2。自己控制事務難度比較大,不容易處理得好。
對第二點,很多人可能都不以為然,不就是一個beginTransaction和一個commit(), rollback()么?太門縫里看人了吧?
我就舉個sparkle同學的小弟寫的代碼吧:
一個大的try-catch塊,在出現任何異常的時候都rollback。try{
PetBean pet =;
beginTransaction();
ssn.delete();
commit();
petLog();
}
catch(Exception e){
rollback();
throw e;
}
我想會這么寫的人應該不在少數。同志們,革命不是請客吃飯那么簡單地。
問題在哪?
1。最嚴重的。try里面一旦有Error拋出,rollback()就不會被執行。sparkle同學說,出了Error我們就不管了。可以,反正出Error的幾率大概很小。所以你的軟件可以說“大多數情況是可以工作地”。
2。這塊代碼最終拋出Exception!如果外面直接套一個函數的話,簽名上就得寫"throws Exception"。這種函數就一個字:“害群之馬”。你讓調用者根本不知道會出什么異常,籠統地告訴人家“什么情況都可能發生”可不是負責任的態度。
3。這個代碼依賴于rollback()的特定實現。因為一旦exception是在beginTransaction()之前或者beginTransaction()時候拋出的,那么本來不應該調用rollback()的。調用 rollback()會出什么結果呢?如果rollback()不檢查當前是否在事務中,就壞菜了。而且,就算rollback()做這個檢查,嵌套事務 也會把一切搞亂。因為很有可能整塊代碼是處在另外一個大的事務中的。調用我們的代碼在我們拋出異常的時候,也許會選擇redo,或者修復一些東西,不見得總是選擇回 滾它那一層的事務的,不分青紅皂白地就rollback上層事務,這個代碼的健壯性真的很差。
看,小小一段代碼,bug和潛在問題如此之多。你還說自己寫事務控制簡單嗎?
真正健壯的,不對外界和調用者有多余的假設依賴的代碼,可以這樣寫:
放棄try-catch,改用try-finally。這樣就不需要捕獲異常再拋出那么麻煩。然后用一個bool變量來告訴finally塊是否需要回滾。PetBean pet =;
beginTransaction();
ok = false;
try{
ssn.delete();
ok = true;
commit();
petLog();
}
finally{
if(!ok)
rollback();
}
這個代碼不難理解,但是如果處處都用這個代碼,也夠丑陋的。
既然已經用了spring,為什么不用spring的TransactionTemplate呢?用Spring TransactionTemplate(下面簡稱tt)的好處如下:
1。事務代碼不依賴hibernate,便于移植。
2。自動得到異常安全,健壯的事務處理。寫代碼的時候幾乎可以完全忘記事務的存在。
當然,sparkle同學有他的道理。使用spring tt需要實現TransactionCallback接口。而java的匿名類語法非常繁瑣。更可恨的是,匿名類只能引用定義成final的局部變量,這 樣在從tt里面往外傳遞返回值的時候就非常不方便。我們可能需要這么寫:
多么丑陋的result[0], result[1]呀。其它還有一些變體,比如每個結果用一個Object[],或者定義一個通用的Ref類來支持"get()"和"set()"。//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]);
可是,這么多的方案,sparkle同學都不滿意。也是,這些方案都免不了類型不安全的down cast。而處理原始類型的結果還需要裝箱!
因為這些原因,我構思了一個簡單的spring tt的wrapper。一個Tx類。這個Tx類可以這么用:
通過把局部變量定義成Tx類的成員變量,我們繞過了downcast和原始類型裝箱拆箱的麻煩。通過把事務之后要執行的動作封裝在after()這個成員函數里面,我們可以方便地引用run()里面產生的結果。//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, TxBlock, TransactionBlockException這三個類的設計:
所有的代碼都在這了(除了import)。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);
}
}
這個小工具除了after(),還支持before(), lastly()。before()在事務開始前運行。after()在事務結束后運行。lastly()保證不管是否出現異常都會被執行。
如此,一個薄薄的封裝,spring tt用起來庶幾不會讓sparkle再以頭撞墻了。