mybatis if test 采坑记

3,604 阅读1分钟

前言

最近遇到的一个关于mybatis的xml条件判断的问题,平时写的都是以判断空属性是否为空,例如:


<if test="type!=null and type !=''">  
    and status = 1   
</if>

所以在这一块并没有足够的重视。

正文

最近刚好要写一个与上边不一样的写法,但是还是以上面的为基础,是对属性的值进行判断: 判断type属性的值


<if test="type!=null and type !=''">  
    <if test="type=='6'">  
        and status = 61   
    </if>
    <if test="type!='6'">  
        and status = 2  
    </if>
</if>

结果就是无论我传什么值,SQL中都是执行的status=2。 在网上也找到了很多解决办法,但是都不是自己想要的结果;

网上的解决办法

  1. 内双外单

<if test='type=="6"'>  
    and status = 1   
</if>

  1. 加.toString()

<if test="type=='6'.toString()">  
    and status = 1   
</if>

源码分析

从参考文章1中可以知道,mybatis使用ognl表达式来进行解析的;下面使用ognl来模拟解析;且不使用上面的两种方法;

pom引入

    <dependency>
         <groupId>ognl</groupId>
         <artifactId>ognl</artifactId>
         <version>3.2.1</version>
     </dependency>

测试代码:


       // 构建一个OgnlContext对象
       OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this,
               new DefaultClassResolver(),
               new DefaultTypeConverter(),
               new DefaultMemberAccess(true));

       Map<String,Object> hashMap=new HashMap<>(1);
       hashMap.put("name", "aa");
       context.setRoot(hashMap);
       String expression="name =='aa'";
       try {
           Boolean flag = (Boolean) Ognl.getValue(expression, context, context.getRoot());
           System.out.println(flag);
       } catch (OgnlException e) {
           e.printStackTrace();
       }

自己的解决办法

除了网上的那两种办法,还有其他的一些方法可以解决我遇到的问题;

  1. 由于我比较的是数字类型,将传入的值改为数字类型的值传入,再将mybatis的xml中的单引号去掉,如下:
        
       hashMap.put("name", 6);
       context.setRoot(hashMap);
       String expression="name ==6";

ognl源码解析

下面对ognl的源码进行分析:

1. 根据Ognl.getValue方法定位到 OgnlOps类的isEqual方法

    
    public static boolean isEqual(Object object1, Object object2)
    {
        boolean result = false;
// 1. 判断两个对象是否为同一个对象
        if (object1 == object2) {
            result = true;
        } else {
// 2. 集合判断
            if ((object1 != null) && object1.getClass().isArray()) {
                if ((object2 != null) && object2.getClass().isArray() && (object2.getClass() == object1.getClass())) {
                    result = (Array.getLength(object1) == Array.getLength(object2));
                    if (result) {
                        for(int i = 0, icount = Array.getLength(object1); result && (i < icount); i++) {
                            result = isEqual(Array.get(object1, i), Array.get(object2, i));
                        }
                    }
                }
            } else {
// 3. 非集合判断
// 3.1 当两个值都为String类型时,就直接equals后就返回了,如果不是,则进入compareWithConversion方法;
                // Check for converted equivalence first, then equals() equivalence 
                result = (object1 != null) && (object2 != null)&& (object1.equals(object2) || (compareWithConversion(object1, object2) == 0));
            }
        }
        return result;
    }

这个方法传入了两个值,第一个值是调用mybatis方法传入的值,这个值的类型就有很多种了,例如BigDecimal,String,Integer等等, 第二个是从mybatis的xml中读取出来的值,这个值要根据xml文件中的写法来判断了:

  1. 外双内单 1.1 单长度为1时读取为Character, 1.2 单长度大于1时,读取为String

再根据第三个判断定位到compareWithConversion方法

2. compareWithConversion方法

  1. 该方法以getNumericType方法来判断参数数字类型,其实可以根据方法名判断出,进入到这里后,都是做数字类型判断,再转换,再比较
  2. 在转换类型的过程中,如果传入的字符无法被转换,将抛出错误,例如:传入的值为字母就无法被转换;
  3. 当mybatis的xml文件中的判断值为""或者''时,该方法会将值赋值为0;
  4. 当mybatis的xml文件中的判断值为'',且里面的值为单数值时,注意转换的值并不是原值;例如:'6'解析为double:54.0

public static int compareWithConversion(Object v1, Object v2)
    {
        int result;
        if (v1 == v2) {
            result = 0;
        } else {
            // 1
            int t1 = getNumericType(v1), t2 = getNumericType(v2), type = getNumericType(t1, t2, true);
            switch(type) {
            case BIGINT:
                result = bigIntValue(v1).compareTo(bigIntValue(v2));
                break;
            case BIGDEC:
                result = bigDecValue(v1).compareTo(bigDecValue(v2));
                break;
            case NONNUMERIC:
                if ((t1 == NONNUMERIC) && (t2 == NONNUMERIC)) {
                    if ((v1 instanceof Comparable) && v1.getClass().isAssignableFrom(v2.getClass())) {
                        result = ((Comparable) v1).compareTo(v2);
                        break;
                    } else {
                        throw new IllegalArgumentException("invalid comparison: " + v1.getClass().getName() + " and "
                                + v2.getClass().getName());
                    }
                }
                // else fall through
            case FLOAT:
            case DOUBLE:
                // 这里注意doubleValue方法,当传入的值为字母时,会抛出错误;
                double dv1 = doubleValue(v1),
                dv2 = doubleValue(v2);
                return (dv1 == dv2) ? 0 : ((dv1 < dv2) ? -1 : 1);
            default:
                long lv1 = longValue(v1),
                lv2 = longValue(v2);
                return (lv1 == lv2) ? 0 : ((lv1 < lv2) ? -1 : 1);
            }
        }
        return result;
    }

总结

  1. 如果属性不为数字类型,使用上边的网上的解决办法就差不多可以了

  2. 如果属性为数字类型,例如Integer,BigDecimal等等会有一个问题 当传入的值为0时,且当mybatis的xml文件中的比较为''或者""时,比较的结果就是false,如下:


    // 1 双引号"",传入的值为0,或者其他类型为0的值
    hashMap.put("name",0); 
    hashMap.put("name",BigDecimal.ZERO);
    String expression="name!=null and name!=\"\"";

    // 2 单引号,传入的值为0,或者其他类型为0的值
    hashMap.put("name",0);
    hashMap.put("name",BigDecimal.ZERO);
    String expression="name!=null and name!=''";

解决办法:

  1. 将传入的值类型转换为String类型
  2. 在mybatis的xml中修改,如下:

    <if test="infSrc != null and infSrc!=''">AND INF_SRC = #{infSrc,jdbcType=BIGINT}</if>
    // 或者
    <if test="infSrc != null and infSrc!=''">AND INF_SRC = #{infSrc,jdbcType=DECIMAL}</if>

这个根据自己传入值类型而定;

最后

项目地址:github.com/guodayede/j…

参考文章

  1. mybatis的if test 字符串的坑
  2. Mybatis if test 字符串比较不生效
  3. MyBatis中OGNL表达式的强制对象类型