现在的位置: 首页 > 综合 > 正文

在业务层进行回滚操作时如何避免回滚指令冗余

2012年05月08日 ⁄ 综合 ⁄ 共 2651字 ⁄ 字号 评论关闭

  众所周知,数据库有事务处理(Database Transaction),当一个事务中的操作没有能全部进行时,之前的操作将回滚。

  如果操作都在同一个数据库上,那可以直接使用数据库事务进行处理,但是如果跨数据库操作呢?可以使用JTA。来看看百度百科中JTA的解释:“JTA,即Java Transaction API,译为Java事务API。JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。”。有兴趣的朋友可以搜一下JTA的用法。

  把回滚放在业务层有利有弊

  利在于可以不用增加DAO层的代码,DAO层只单纯扮演数据读写的角色,操作的粒度很细。细粒度意味着DAO层接口可以拥有更好的复用性。并且,如果不使用JTA,那么业务层中将不会混入与SQL相关的语句。所有与DB有关的部分都被封装在DAO层不会泄露到上层。当DB被更换时,只需要更改DAO层的数据接口代码,而不需要改动业务层代码。

  弊在于在业务层用代码实现回滚是一件复杂的事情,需要做一步一步的判断,并且回滚指令是累加的。

  为什么说回滚指令是累加的呢?

  假设现在有4个操作(operation1~4),只有当这4个操作都顺利进行时才接受,否则就回滚之前进行的操作。

  那么大致的逻辑就是(伪代码,假设有四个替换操作,每个替换都成功时才算正确,否则将回滚之前的操作):

public boolean rollBackExample(String oldID, Object newOne){
        
        boolean res = true;
        Object oldOne = getObject(oldID);
        
        res = Replace1(oldID, newOne);
        if(res){
            //operation1 success
            //now doing operation 2
            res = Replace2(oldID, newOne);
            if(res){
                //operation2 success
                //now doing operation 3
                res = Replace3(oldID, newOne);
                if(res){
                    //operation3 success
                    //now doing operation 4
                    res = Replace4(oldID, newOne);
                    if(res){
                        return true;
                    }else{
                        //rollback Replace3 \Replace2 \ Replace1
                        Replace3(newOne.getID(), oldOne);
                        Replace2(newOne.getID(), oldOne);
                        Replace1(newOne.getID(), oldOne);
                        return false;
                    }
                }else{
                    //rollback Replace2 \ Replace1
                    Replace2(newOne.getID(), oldOne);
                    Replace1(newOne.getID(), oldOne);
                    return false;
                }
            }else{
                //rollback Replace1
                Replace1(newOne.getID(), oldOne);
                return false;
            }
        }else{
            return false;
        }
        
    }

  可以看到,代码中进行逐级进行了判断,并且依据操作进行程度的加深,回滚的列表逐渐增多。把回滚的操作单独提出来可以看得更明显些:

  当第二个操作出错时,只需回滚

//rollback Replace1
 Replace1(newOne.getID(), oldOne);

  当第三个操作出错时,需要回滚:

//rollback Replace2 \ Replace1
 Replace2(newOne.getID(), oldOne);
 Replace1(newOne.getID(), oldOne);

  当第四个操作出错时,需要回滚:

//rollback Replace3 \Replace2 \ Replace1
 Replace3(newOne.getID(), oldOne);
 Replace2(newOne.getID(), oldOne);
 Replace1(newOne.getID(), oldOne);

  假设这个事务有N个操作组成,那么当进行到第N个操作时出错,需要进行N-1项回滚。而累积的代码为1 + 2 + …… + N - 1 = N(N-1)/2行代码,直观点看就是如果有10项操作,那么理论上将有9项可能的回滚操作,并且在函数中将累计出现45行用于回滚的代码。用于描述回滚的代码的平均重复出现次数达5次。非常拖沓。

  要如何解决这个代码不优雅的问题呢?

  首先,判断条件是不可少的,也就是if-else语句无法省略。因为operationj可能是在operationi(j later then i)的基础上运行的,因此需要一步步判断以避免出错。

  其次,不管是哪一步出错,它进行回滚的操作都是与自己所处的执行深度成正相关的。当第k步出错时,k-1及之前的步骤就需要回滚,每一个操作都是如此。这个性质可以在没有写break的switch语句中找到影子。当case1执行后,会接着执行case2……以此类推。

  因此我们可以将需要进行的回滚操作设计到一个switch-case语句中,伪代码如下:

public boolean rollBackExample2(String oldID, Object newOne) {

        boolean res = true;
        Object oldOne = getObject(oldID);
        int phase = 0;

        res = Replace1(oldID, newOne);
        if (res) {
            res = Replace2(oldID, newOne);
            if (res) {
                res = Replace3(oldID, newOne);
                if (res) {
                    res = Replace4(oldID, newOne);
                    if (res) {
                        phase = 4;
                    }
                } else {
                    phase = 3;
                }
            } else {
                phase = 2;
            }
        } else {
            phase = 1;
        }

        switch (phase) {
        case 4:
            return true;
        case 3:
            Replace3(newOne.getID(), oldOne);
        case 2:
            Replace2(newOne.getID(), oldOne);
        case 1:
            Replace1(newOne.getID(), oldOne);
        default:
            return false;
        }
    }

可以看到,当使用switch-case结构+phase阶段判断时,就不会出现回滚指令的代码冗余了。 

抱歉!评论已关闭.