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


      众所周知,数据库有事务处理(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阶段判断时,就不会出现回滚指令的代码冗余了。 

  • 相关阅读:
    Android开发开源一款结合databinding写的用于RecyclerView的简单高效MultiTypeAdapter
    Android开发databinding和RecyclerView.ViewHolder的完美结合
    Android开发华为手机不弹出Toast,报HwRTBlurUtils: check blur style for HwToast-Toast...的原因
    android开发Toolbar标题居中显示的解决方法
    记录使用xshell通过ssh方式连接Linux机器的步骤
    同一局域网内手机访问电脑本地localhost网页的方法
    Gradle里面的依赖implementation和api的真正理解
    Android开发使用kotlin编写的泛型模式的MVP框架
    nyoj-3-多边形重心问题(求多边形面积和中心)
    nyoj-1132-promise me a medal(求线段交点)
  • 原文地址:https://www.cnblogs.com/elaron/p/2757912.html
Copyright © 2020-2023  润新知