案例
当传入的person
属性age
的值为0
时,mybatis
预编译下面的语句会报错,因为预编译的sql
为:update person where id = 1
。
<update id="update" parameterType="com.p7.demo.model.Person">
update person
<set>
<if test="age != ''">
age = #{age}
</if>
</set>
where id = 1
</update>
为什么 if 条件判断没有通过?
通过源码了解到,mybatis
在预编译sql
时,使用OGNL
表达式来解析if
标签,对于Integer
类型属性,在判断不等于''
时,例如age != ''
,OGNL
会返回''
的长度,源码:(s.length() == 0) ? 0.0 : Double.parseDouble( s )
,因此表达式age != ''
被当做age != 0
来判断,所以当age
为0
时,if
条件判断不通过。
总结
- 在设计实体类时,实体属性要使用基本数据类型的包装类型,因为基本数据类型有默认值
if
条件判断number
类型,没必要判断''
的情况,只需判断null
的情况,如果非要判断''
的情况,那么要考虑到等于0
的情况,即<if test="age != '' or age == 0">
源码分析
mybatis
调用OGNL
源码的入口:
-
将
age != ''
表达式封装为ognl.Node
,ognl.Node
使用组合设计模式,Node
分为普通节点,常量节点等类型的节点 -
调用
Ognl
的getValue
静态方法,参数1:条件表达式封装的ognl.Node
,参数2:Person
对象(mybatis
传入的是一个Map
)
调试OGNL
的代码:
依赖:
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>2.6.9</version>
</dependency>
代码:
public static void main(String[] args) throws Exception {
String expression = "age != ''";
Node node = new OgnlParser(new StringReader(expression)).topLevelExpression();
Person person = new Person();
person.setAge(0);
Object value = Ognl.getValue(node, person);
System.out.println(value);
}
-
条件表达式判断入口,
Ognl#getValue
方法 -
Ognl#getValue
重载方法,这个方法将参数tree
强转为Node
类型,它的实际类型是SimpleNode
,并调用SimpleNode#getValue
方法,SimpleNode
中有Node[] children
属性,OGNL
使用组合设计模式,将age != ''
分成ASTProperty
封装的age
属性节点和ASTConst
封装的''
常量节点,且age
属性节点的children
属性有一个ASTConst
常量节点,它value
是age
的属性名称。ASTConst
类型节点的children
一般情况下为null
,即此类节点一般没有子节点 -
SimpleNode#getValue
方法else
分支调用了evaluateGetValueBody
方法,此方法中调用ExpressionNode#isConstant
方法,判断SimpleNode
的children
是否全是常量节点(常量节点请参考SimleNode
的子类),这里hasContantValue
为false
,所以在执行return
的三目运算时,调用ASTNotEq#getValueBody
方法,因为我们的条件是不等于,所以我们应该查看ASTNotEq
中的方法。 -
ASTNotEq#getValueBody
方法,SimpleNode
中的children[0]
是age
属性节点,children[1]
是''
常量节点,这里分别获取节点的值。children[0]
是属性节点,调用getValue
方法,重复走第3步的操作,注意,此时调用的getValueBody
是ASTProperty
的方法,获取age
节点的常量节点,并从person
类中提取age
的值,而children[1]
是常量节点,因此走第3
步操作时,hasConstantValue
是true
,直接返回constantValue
,值为""
。然后调用OgnlOps#equal
方法,参数1:age
的实际值,参数2:与age
做判断的值''
-
OgnlOps#equal
方法,在第二个if
条件中,调用isEqual
方法,isEqual
最里层的else
中调用的compareWithConversion
方法,首先通过getNumericType
方法获取到参数的真实类型,再通过getNumericType
重载方法获取两个参数的最大类型(参见NumericTypes
接口)这里返回NONNUMERIC
,然后通过switch
分支处理,由于参数1和参数2不都为NONNUMERIC
,因此switch
分支走DOUBLE
的case
,分别获取两个参数的Double
值,对于参数2空字符串,在调用doubleValue
方法时,先去掉左右空格,再取字符串长度作为最后的值,与age
的值进行比较,即return (dv1 == dv2) ? 0 : ((dv1 < dv2) ? -1 : 1);
,dv1
和dv2
都是0
,然后回看result = (compareWithConversion(object1, object2, true) == 0) || object1.equals(object2);
返回true
,return OgnlOps.equal( v1, v2 )? Boolean.FALSE : Boolean.TRUE;
返回false
,最终<if test="age != ''">
返回false
,条件不成立