原文地址:http://blog.51cto.com/wangguangshuo/1944531
今天工作中發現一個Long類型的參數沒有傳到sql中去,在sql xml配置文件中是使用if test標簽判斷:
<if test="version != null and version != ''">xxxxx</if>
通過debug發現參數中的version是有值的,但出來的sql語句就沒有這個version
網上查了一些有不少同樣這樣的問題,大致解決辦法分兩種:
1、去掉空字符串判斷
<if test="version != null">xxxxx</if>
2、添加0值判斷
<if test="version != null and version != '' or version == 0">xxxxx</if>
這兩種方法都是可以的,在我看來是這樣,如果這個version類型和我的情況一樣,是包裝類型而不是基本數據類型的話,第一種就足夠了,而且更貼近實際,因為包裝類型除了有值的情況就是null,不會為""空字符串的,String類型不在我討論的范圍內,標題已經說了是數字0,況且如果是String的話就不會有這個問題了。
知道了怎么解決這個問題,那就想知道為什么這個問題會出現,當然要查看mybatis的源碼了,
sql語句是通過獲取BoundSql來的(網上查看mybatis層次結構),這個是通過方法getBoundSql(),這個方法定義在一個接口SqlSource,它有五個實現,從命名上看應該是找DynamicSqlSource,
<img/>暫時缺少圖片
if判斷的這種屬於動態的sql所以直接找它了
這里面有句:rootSqlNode.apply(content);這個是添加動態sql 的,點進去查看
apply也是在一個接口里,查看實現,各種和標簽有關的命名實現,這個標簽是if當然就是找IfSqlNode了,它里面的實現是通過evaluator.evaluateBoolean()方法判斷的,點進去繼續看
發現一行重要的代碼:
if(value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
現在真相大白了,不管是Inteter還是Long,只要是值為0,都會判斷為false
真的很抱歉,上面的分析錯了,早上來了之后有四個瀏覽,立刻隱藏了這篇文章准備修改,希望那四位網友能再看到更正,上面的分析是在昨天晚上下班后,現在我又看了一遍昨天打開的源碼:
上面的value其實是test后面的表達式的返回值,而不是傳入的version參數,test后面的表達式如果只寫一個0的話就會走上面的if判斷,直接返回false,但眼前的情況是version為0,test后面的表達式就是:0 != null and 0 != '',這個表達式是由org.apache.ibatis.scripting.xmltags.OgnlCache類的getValue(String expression,Objec root)方法得來,下載了ognl源碼包關聯之后從新走的debug追蹤了一下,還是從上面IfSqlNode中的apply方法判斷說起:
(DynamicContext context) { (.evaluateBoolean(context.getBindings())) { .apply(context)} }
進入evaluateBoolean方法:
(String expressionObject parameterObject) { Object value = OgnlCache.(expressionparameterObject)(value Boolean) (Boolean) value(value Number) !BigDecimal(String.(value)).equals(BigDecimal.)value != }
這里面expression參數就是“version != null and version != ''”這個getValue方法里面就是調用Ognl包中的方法處理之后返回的一個值,進去這個getValue方法中
Object (String expressionObject root) { { Map<ObjectOgnlCla***esolver> context = Ognl.(rootOgnlCla***esolver())Ognl.((expression)contextroot)} (OgnlException e) { BuilderException(+ expression + + ee)} }
這里面parseExpression(expression)是解析表達式的,沒有決定到最終結果,還是要進去看getvalue方法,里面調用了好多層,debug下一步一步進去追蹤,會發現下面的關鍵代碼:
Object getValueBody(OgnlContext contextObject source) OgnlException { Object v1 = .children[].getValue(contextsource)Object v2 = .children[].getValue(contextsource)OgnlOps.equal(v1v2)?Boolean.FALSE:Boolean.TRUE}
上面的表達式是version != null and version != '',這個方法是在一個for循環里調用的,分別是0和null的比較,0和""的比較,第一次比較肯定是true不用說了,第二次的時候v1就是0,v2就是"",繼續查看這里的OgnlOps.equal()方法
equal(Object v1Object v2) { v1 == ?v2 == :(v1 != v2 && !isEqual(v1v2)?(v1 Number && v2 Number?((Number)v1).doubleValue() == ((Number)v2).doubleValue():):)}
0和null判斷為false,執行:后面的,關鍵取決於isEqual()方法了,進去這方法里又能看到一個方法
result = compareWithConversion(object1, object2, true) == 0 || object1.equals(object2);
進去查看
compareWithConversion(Object v1Object v2equals) { result(v1 == v2) { result = } { t1 = getNumericType(v1)t2 = getNumericType(v2)type = getNumericType(t1t2)(type) { : result = bigIntValue(v1).compareTo(bigIntValue(v2)): result = bigDecValue(v1).compareTo(bigDecValue(v2)): (t1 == && t2 == ) { (v1 != && v2 != ) { (v1.getClass().isAssignableFrom(v2.getClass()) || v2.getClass().isAssignableFrom(v1.getClass())) { (v1 Comparable) { result = ((Comparable)v1).compareTo(v2)} (equals) { result = v1.equals(v2)?:} } (!equals) { IllegalArgumentException(+ v1.getClass().getName() + + v2.getClass().getName())} result = } { var10000 = v1 != v2} } : : dv1 = doubleValue(v1)dv2 = doubleValue(v2)dv1 == dv2?:(dv1 < dv2?-:): lv1 = longValue(v1)lv2 = longValue(v2)lv1 == lv2?:(lv1 < lv2?-:)} } result}
參數一個是0,一個是"",最終debug會走進case 8 里面,0和“”都會被轉成double進行比較,都會變成0.0,這就是mybati中if test 0!=""判定為false的原因