1. 程式人生 > >Mybatis if判斷Integer型別的值不等於''引發的問題

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來判斷,所以當age0時,if條件判斷不通過。

總結

  1. 在設計實體類時,實體屬性要使用基本資料型別的包裝型別,因為基本資料型別有預設值
  2. if條件判斷number型別,沒必要判斷''
    的情況,只需判斷null的情況,如果非要判斷''的情況,那麼要考慮到等於0的情況,即<if test="age != '' or age == 0">

原始碼分析

mybatis呼叫OGNL原始碼如下:

  1. age != ''表示式封裝為ognl.Nodeognl.Node使用組合設計模式,Node分為普通節點,常量節點等型別的節點
  2. 呼叫OgnlgetValue靜態方法,引數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原始碼如下:

  1. 條件表示式判斷入口,Ognl#getValue方法

    這裡寫圖片描述

  2. Ognl#getValue過載方法,這個方法將引數tree強轉為Node型別,它的實際型別是SimleNode,並呼叫SimleNode#getValue方法,SimleNode中有Node[] children屬性,OGNL使用組合設計模式,將age != ''分成ASTProperty封裝的age屬性節點和ASTConst封裝的''常量節點,且age屬性節點的children屬性有一個ASTConst常量節點,它valueage的屬性名稱。ASTConst型別節點的children一般情況下為null,即此類節點一般沒有子節點

    這裡寫圖片描述

    這裡寫圖片描述

  3. SimleNode#getValue方法else分支呼叫了evaluateGetValueBody方法,此方法中呼叫ExpressionNode#isConstant方法,判斷SimleNodechildren是否全是常量節點(常量節點請參考SimleNode的子類),這裡hasContantValuefalse,所以在執行return的三目運算時,呼叫ASTNotEq#getValueBody方法,因為我們的條件是不等於,所以我們應該檢視ASTNotEq中的方法。

    這裡寫圖片描述

    這裡寫圖片描述

  4. ASTNotEq#getValueBody方法,SimpleNode中的children[0]age屬性節點,children[1]''常量節點,這裡分別獲取節點的值。children[0]是屬性節點,呼叫getValue方法,重複走第3步的操作,注意,此時呼叫的getValueBodyASTProperty的方法,獲取age節點的常量節點,並從person類中提取age的值,而children[1]是常量節點,因此走第3步操作時,hasConstantValuetrue,直接返回constantValue,值為""。然後呼叫OgnlOps#equal方法,引數1:age的實際值,引數2:與age做判斷的值''
    這裡寫圖片描述

    這裡寫圖片描述

    這裡寫圖片描述

  5. OgnlOps#equal方法,在第二個if條件中,呼叫isEqual方法,isEqual最裡層的else中呼叫的compareWithConversion方法,首先通過getNumericType方法獲取到引數的真實型別,再通過getNumericType過載方法獲取兩個引數的最大型別(參見NumericTypes介面)這裡返回NONNUMERIC,然後通過switch分支處理,由於引數1和引數2不都為NONNUMERIC,因此switch分支走DOUBLEcase,分別獲取兩個引數的Double值,對於引數2空字串,在呼叫doubleValue方法時,先去掉左右空格,再取字串長度作為最後的值,與age的值進行比較,即return (dv1 == dv2) ? 0 : ((dv1 < dv2) ? -1 : 1);dv1dv2都是0,然後回看result = (compareWithConversion(object1, object2, true) == 0) || object1.equals(object2);返回truereturn OgnlOps.equal( v1, v2 )? Boolean.FALSE : Boolean.TRUE;返回false,最終<if test="age != ''">返回false,條件不成立

    這裡寫圖片描述

    這裡寫圖片描述

    這裡寫圖片描述

    這裡寫圖片描述

    這裡寫圖片描述

    這裡寫圖片描述

    這裡寫圖片描述