昨天在千年妖精丽晶大宾馆,看见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再以头撞墙了。