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