Mybatis踩坑--test语句

在Mybatis 3.2.2版本中,遇到一个SQL查询问题,当数值类型的optStatus字段与空字符串使用`!=`进行比较时,传入0值时条件不生效。深入源码发现,OGNL的equals方法在特定情况下错误地判断0等于空字符串。这个问题提醒我们在使用Mybatis时,务必确保比较表达式的类型匹配,避免出现意料之外的结果。

Mybatis version:3.2.2

问题:如下sql语句,关注点在optStatus != '' 这句上。

select * from table
where ...
    <if test="optStatus != null and optStatus != ''">
           AND OPT_STATUS = #{optStatus,jdbcType=DECIMAL}
    </if>

其中optStatus是数值类型。

此时如果optStatus传入参数为0时,按常理理解这个条件会执行 and OPT_STATUS=0 这个条件,但是诡异的事情发生了,这个条件没有执行,而如果optStatus为非0数字时,能够正常运行。先不说这个奇葩的写法,数值型用!=''判断(这里的用法肯定是不合理的),为啥传入0,看似满足的条件却不满足了呢。二话不说,只能撸源码。

前面就不看了(有兴趣debug一下),直接看重点,因为我们的条件是!=,所以定位到类ASTNotEq,看getValueBody方法:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.ognl;

class ASTNotEq extends ExpressionNode {
    public ASTNotEq(int id) {
        super(id);
    }

    public ASTNotEq(OgnlParser p, int id) {
        super(p, id);
    }

    protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object v1 = super.children[0].getValue(context, source);
        Object v2 = super.children[1].getValue(context, source);
        return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;
    }

    public String getExpressionOperator(int index) {
        return "!=";
    }
}

根据源码,getValueBody方法中的局部变量v1=0,v2='',按常理0!='',OgnlOps.equals(v1,v2)应该返回false才对,这样getValueBody返回TRUE,AND语句才会执行。而真实情况是OgnlOps.equals(v1,v2)返回了True,原因接着看OgnlOps.equal。

public abstract class OgnlOps implements NumericTypes {
   ......
    public static boolean equal(Object v1, Object v2) {
        if (v1 == null) {
            return v2 == null;
        } else if (v1 != v2 && !isEqual(v1, v2)) {
            if (v1 instanceof Number && v2 instanceof Number) {
                return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
            } else {
                return false;
            }
        } else {
            return true;
        }
    }
   .......
public static boolean isEqual(Object object1, Object object2) {
        boolean result = false;
        if (object1 == object2) {
            result = true;
        } else if (object1 != null && object2 != null) {
            if (object1.getClass().isArray() && object2.getClass().isArray() && object2.getClass() == object1.getClass()) {
                result = Array.getLength(object1) == Array.getLength(object2);
                if (result) {
                    int i = 0;

                    for(int icount = Array.getLength(object1); result && i < icount; ++i) {
                        result = isEqual(Array.get(object1, i), Array.get(object2, i));
                    }
                }
            } else if (object1 != null && object2 != null) {
                result = compareWithConversion(object1, object2, true) == 0 || object1.equals(object2);
            }
        }

        return result;
    }
...............
public static int compareWithConversion(Object v1, Object v2, boolean equals) {
        int result;
        if (v1 == v2) {
            result = 0;
        } else {
            int t1 = getNumericType(v1);
            int t2 = getNumericType(v2);
            int type = getNumericType(t1, t2, true);
            switch(type) {
            case 6:
                result = bigIntValue(v1).compareTo(bigIntValue(v2));
                break;
            case 9:
                result = bigDecValue(v1).compareTo(bigDecValue(v2));
                break;
            case 10:
                if (t1 == 10 && t2 == 10) {
                    if (v1 != null && v2 != null) {
                        if (v1.getClass().isAssignableFrom(v2.getClass()) || v2.getClass().isAssignableFrom(v1.getClass())) {
                            if (v1 instanceof Comparable) {
                                result = ((Comparable)v1).compareTo(v2);
                                break;
                            }

                            if (equals) {
                                result = v1.equals(v2) ? 0 : 1;
                                break;
                            }
                        }

                        if (!equals) {
                            throw new IllegalArgumentException("invalid comparison: " + v1.getClass().getName() + " and " + v2.getClass().getName());
                        }

                        result = 1;
                        break;
                    } else {
                        boolean var10000 = v1 != v2;
                    }
                }
            case 7:
            case 8:
                double dv1 = doubleValue(v1);
                double dv2 = doubleValue(v2);
                return dv1 == dv2 ? 0 : (dv1 < dv2 ? -1 : 1);
            default:
                long lv1 = longValue(v1);
                long lv2 = longValue(v2);
                return lv1 == lv2 ? 0 : (lv1 < lv2 ? -1 : 1);
            }
        }

        return result;
    }
}

 方法执行的顺序是 equals()->isEquals()->compareWithConversion()。根据getNumericType()方法,0返回4,''返回10,type=10,就会走到case 10:语句,而t2=10,t1=4,所以不会进入到if (t1 == 10 && t2 == 10) 语句内,且没有break语句,进而进入case 8:,而doubleValue()对参数0和''均返回0.0D,所以判断为0=='',这就是根源所在。

总结,虽然还没完全理解OGNL里面的这些逻辑,不知道没有break是故意为之还是bug,但是可以确定的是,在使用的时候是不恰当的,数值型的不能和字符串比较。在不改变mybatis和ognl源码的前提下,使用mybatis要注意,test语句的比较表达式要满足类型匹配,否则会有意想不到的惊喜(当然,本文举例在实际应用中自测的情况下很容易发现问题)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值