问题:使用判断<if test='x != null and x != "" '>的时候x为Integer值 0 的时候不会进去判断。
原因:MyBatis的源码中对空传的判断是会先转成 0.0D 所以 0 = 0.0D是true的 判断就会进不去,可以更改条件:
<if test='x != null and x != "" or x == 0'>
源码流程分析:
例如下面代码:
<select id="getLables" resultType="java.lang.String">
SELECT distinct article_label from ARTICLE
<if test="x != null and x != ''">
where 1 = 1
</if>
</select>
当x传入值为 Integer 类型的 0 的时候,这个判断是进不去的。
那为什么进不去呢? 0不是null 也不是 '' 为什么这个判断进不去呢?
首先看下在服务启动的时候MyBatis是怎么处理我们的sql的:
MyBatis会把我们的sql语句进行预处理,并且存储在 mappedStatements 这个对象里
key就是mapper文件命名空间+'.'+id。
value 存储的是一个MappedStatement对象。
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
上图下面的红框中,上面是在构造一个当前sql的MappedStatement对象,构造完成后调用configuration类的方法将对象添加到mappedStatements 这个map中去。
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
MappedStatement 这个类有一个SqlSource 变量
sql相关的信息都存储在这里面。
因为sql相关的信息都是在启动服务时候预编译好的,所以我们如果修改了xml中的sql相关内容,只通过编译是不会重新加载的,只有重启服务才可以。
看下代码运行时MyBatis的源码中的处理:
我的sql:
<select id="getLables" resultType="java.lang.String">
SELECT distinct article_label from ARTICLE
<if test="x != null and x != ''">
where 1 = 1
</if>
</select>
1、当运行查询的时候,首先会进入到MyBatis中的DynamicSqlSource类中,并把参数传递过来:
2、创建DynamicContext对象
3、使用rootSqlNode对象去解析参数并动态拼接出来sql
4、遍历不同的节点
例如:
StaticTextSqlNode 静态文本sql节点(SELECT distinct article_label from ARTICLE )
IfSqlNodeif 条件节点 等
通过遍历节点去往context 实例中设置值。
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> 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 的内容 拆分后的结果,如下:
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
结果:最后判断 <if test="x != null and x != ''">中的 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);
}
}
}