关于MyBatis中if判断 0 和 ''相等的问题

问题:使用判断的时候x为Integer值 0 的时候不会进去判断。

原因:MyBatis的源码中对空传的判断是会先转成 0.0D 所以 0 = 0.0D是true的 判断就会进不去,可以更改条件:

源码流程分析:

例如下面代码:

  

当x传入值为 Integer 类型的 0 的时候,这个判断是进不去的。
那为什么进不去呢? 0不是null 也不是 '' 为什么这个判断进不去呢?

首先看下在服务启动的时候MyBatis是怎么处理我们的sql的:

MyBatis会把我们的sql语句进行预处理,并且存储在 mappedStatements 这个对象里
key就是mapper文件命名空间+'.'+id。
value 存储的是一个MappedStatement对象。

protected final Map mappedStatements = new StrictMap("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
image.png

上图下面的红框中,上面是在构造一个当前sql的MappedStatement对象,构造完成后调用configuration类的方法将对象添加到mappedStatements 这个map中去。

  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

MappedStatement 这个类有一个SqlSource 变量


image.png

sql相关的信息都存储在这里面。

因为sql相关的信息都是在启动服务时候预编译好的,所以我们如果修改了xml中的sql相关内容,只通过编译是不会重新加载的,只有重启服务才可以。

看下代码运行时MyBatis的源码中的处理:

我的sql:

  

1、当运行查询的时候,首先会进入到MyBatis中的DynamicSqlSource类中,并把参数传递过来:


image.png

2、创建DynamicContext对象


image.png

3、使用rootSqlNode对象去解析参数并动态拼接出来sql
4、遍历不同的节点

例如:
StaticTextSqlNode 静态文本sql节点(SELECT distinct article_label from ARTICLE )
IfSqlNodeif 条件节点 等
通过遍历节点去往context 实例中设置值。

public class MixedSqlNode implements SqlNode {
  private final List contents;

  public MixedSqlNode(List contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

5、进入IfSqlNode类的apply方法进行判断
调用表达式比较类的evaluateBoolean()方法判断两个表达式是否相等

  @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

6、在ExpressionEvaluator类中使用OgnlCache类的方法进行判断

  public boolean evaluateBoolean(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value instanceof Boolean) {
      return (Boolean) value;
    }
    if (value instanceof Number) {
      return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
    }
    return value != null;
  }

7、在OgnlCache类中调用Ognl类的getValue方法,将表达式和参数全都传进去进行比较。
中间略过.........
8、调用ASTAnd类的getValueBody方法

    protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object result = null;
        int last = this._children.length - 1;

        for(int i = 0; i <= last; ++i) {
            result = this._children[i].getValue(context, source);
            if (i != last && !OgnlOps.booleanValue(result)) {
                break;
            }
        }

        return result;
    }

这个children的值就是 if条件中test 的内容 拆分后的结果,如下:


image.png

9、调用OgnlOps类的equal方法

    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;
        }
    }

在这个方法内部调用了一个isEqual(v1, v2)方法:这个方法比较的isEqual(0, '')返回值是true,所以判定问题就是出在这里的。看下isEqual(v1, v2)方法实现:

    public static boolean isEqual(Object object1, Object object2) {
        boolean result = false;
        if (object1 == object2) {
            result = true;
        } else 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) {
                    int i = 0;

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

        return result;
    }

因为 object1 显然不是空也不是数组,所以会进入最下面的else方法:
也就是

result = object1 != null && object2 != null && (object1.equals(object2) || compareWithConversion(object1, object2) == 0);

object1 != null 结果:true
object2 != null 结果:true
object1.equals(object2) 结果:false

那么问题就只有出现在这里面了:compareWithConversion 结果: true?
执行下面代码的时候 v1:0 v2:''
getNumericType(v1)和getNumericType(t1, t2, true)源码放下面了。

    public static int compareWithConversion(Object v1, Object v2) {
        int result;
        if (v1 == v2) {
            result = 0;
        } else {
            int t1 = getNumericType(v1); // 4
            int t2 = getNumericType(v2);//  10
            int type = getNumericType(t1, t2, true); // 10
            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 instanceof Comparable && v1.getClass().isAssignableFrom(v2.getClass())) {
                        result = ((Comparable)v1).compareTo(v2);
                        break;
                    }

                    throw new IllegalArgumentException("invalid comparison: " + v1.getClass().getName() + " and " + v2.getClass().getName());
                }
            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;
    }

根据下面的值可以看到switch分支会走到 case 8:
会使用doubleValue(v2); 将v2 转换成double值

public static double doubleValue(Object value) throws NumberFormatException {
        if (value == null) {
            return 0.0D;
        } else {
            Class c = value.getClass();
            if (c.getSuperclass() == Number.class) {
                return ((Number)value).doubleValue();
            } else if (c == Boolean.class) {
                return (Boolean)value ? 1.0D : 0.0D;
            } else if (c == Character.class) {
                return (double)(Character)value;
            } else {
                String s = stringValue(value, true);
                return s.length() == 0 ? 0.0D : Double.parseDouble(s);
            }
        }
    }

调用String s = stringValue(value, true);方法获取当前参数的String类型值,并且去掉空格。
如果s.length() == 0 直接返回了0.0D 所以最终结果:
v1 = 0.0D
v2 = 0.0D
v1 == v2 返回了true

结果:最后判断 中的 0 != '' 是false 所以不会拼接进去我们的条件。

注意:getNumericType(v1) 这个方法是判断 v1的类型

public static int getNumericType(Object value) {
        if (value != null) {
            Class c = value.getClass();
            if (c == Integer.class) {
                return 4;
            }

            if (c == Double.class) {
                return 8;
            }

            if (c == Boolean.class) {
                return 0;
            }

            if (c == Byte.class) {
                return 1;
            }

            if (c == Character.class) {
                return 2;
            }

            if (c == Short.class) {
                return 3;
            }

            if (c == Long.class) {
                return 5;
            }

            if (c == Float.class) {
                return 7;
            }

            if (c == BigInteger.class) {
                return 6;
            }

            if (c == BigDecimal.class) {
                return 9;
            }
        }

        return 10;
    }
public static int getNumericType(int t1, int t2, boolean canBeNonNumeric) {
        if (t1 == t2) {
            return t1;
        } else if (canBeNonNumeric && (t1 == 10 || t2 == 10 || t1 == 2 || t2 == 2)) {
            return 10;
        } else {
            if (t1 == 10) {
                t1 = 8;
            }

            if (t2 == 10) {
                t2 = 8;
            }

            if (t1 >= 7) {
                if (t2 >= 7) {
                    return Math.max(t1, t2);
                } else if (t2 < 4) {
                    return t1;
                } else {
                    return t2 == 6 ? 9 : Math.max(8, t1);
                }
            } else if (t2 >= 7) {
                if (t1 < 4) {
                    return t2;
                } else {
                    return t1 == 6 ? 9 : Math.max(8, t2);
                }
            } else {
                return Math.max(t1, t2);
            }
        }
    }

你可能感兴趣的:(关于MyBatis中if判断 0 和 ''相等的问题)