Mybatis if判斷Integer型別的值不等於''引發的問題
案例
當傳入的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
原始碼如下:
條件表示式判斷入口,
Ognl#getValue
方法Ognl#getValue
過載方法,這個方法將引數tree
強轉為Node
型別,它的實際型別是SimleNode
,並呼叫SimleNode#getValue
方法,SimleNode
中有Node[] children
屬性,OGNL
使用組合設計模式,將age != ''
分成ASTProperty
封裝的age
屬性節點和ASTConst
封裝的''
常量節點,且age
屬性節點的children
屬性有一個ASTConst
常量節點,它value
是age
的屬性名稱。ASTConst
型別節點的children
一般情況下為null
,即此類節點一般沒有子節點SimleNode#getValue
方法else
分支呼叫了evaluateGetValueBody
方法,此方法中呼叫ExpressionNode#isConstant
方法,判斷SimleNode
的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
,條件不成立