問題復現
假設你在 MyBatis 的 XML 配置中使用了如下代碼:
<if test="isCollect != null"><choose><when test="isCollect == 1">AND exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when><when test="isCollect == 0">AND not exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when></choose>
</if>
在這段代碼中,通過 <choose>
標簽對 isCollect
的值進行判斷。如果 isCollect
的值為 1
,則執行一個 exists
查詢;如果 isCollect
的值為 0
,則執行一個 not exists
查詢。然而,實際運行時,當 isCollect
為 空字符串 (""
) 時,代碼卻會意外地執行到 test="isCollect == 0"
這一條件。
具體分析后,得出如果傳入的 isCollect
是空字符串 ""
,由于 OGNL 類型轉換的原因,空字符串會被轉換為 0
,導致條件判斷意外地返回 true
,從而執行 SQL 分支。本文將詳細解析這個問題的根本原因,并提供有效的解決方案。
1. MyBatis 條件判斷的執行流程
在 MyBatis 中,<if test="...">
標簽的條件表達式通過 OGNL(Object-Graph Navigation Language)引擎來解析。OGNL 可以動態地訪問對象的屬性,并執行表達式。MyBatis 將 test
屬性中的表達式傳遞給 OGNL 引擎進行解析和計算,判斷條件是否成立。
關鍵流程:
test
表達式解析:test="isCollect == 0"
中的isCollect == 0
會被 OGNL 解析并執行。- OGNL 類型轉換:OGNL 在比較值時,會根據目標類型自動進行類型轉換。例如,空字符串
""
會被轉換為0
(數字),導致條件test="isCollect == 0"
被錯誤地評估為true
。
2. 源碼分析
要理解為什么空字符串 ""
被錯誤地轉換為 0
,我們需要查看 MyBatis 3.5.10 中的關鍵源碼,特別是 OGNL 引擎如何處理這種類型轉換。
a. IfSqlNode
類
IfSqlNode
負責解析 <if test="...">
標簽中的條件表達式。它會將 test
中的表達式交給 OGNL 引擎進行解析,然后根據條件結果決定是否生成 SQL 片段。
- 源碼路徑:
org.apache.ibatis.scripting.xmltags.IfSqlNode
- 主要方法:
apply(DynamicContext context)
# org.apache.ibatis.scripting.xmltags.IfSqlNode#apply@Overridepublic boolean apply(DynamicContext context) {if (evaluator.evaluateBoolean(test, context.getBindings())) {// 如果為true,追加SQL片段contents.apply(context);return true;}return false;}
# org.apache.ibatis.scripting.xmltags.OgnlCache#getValuepublic static Object getValue(String expression, Object root) {try {Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);// 獲取OGNL表達式的值return Ognl.getValue(parseExpression(expression), context, root);} catch (OgnlException e) {throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);}}private static Object parseExpression(String expression) throws OgnlException {Object node = expressionCache.get(expression);if (node == null) {node = Ognl.parseExpression(expression);expressionCache.put(expression, node);}return node;}
在這個方法中,Ognl.getValue
會評估 test
條件的結果。如果 test
返回 true
,那么相應的 SQL 片段就會被拼接到最終的查詢中。
b. OGNL 類型轉換:OgnlRuntime.convertValue
OGNL 在執行表達式時會對輸入的值進行類型轉換,尤其是在數字與字符串比較時。如果傳入的是一個空字符串,OGNL 會將其隱式地轉換為數字 0
,導致條件判斷被誤判為 true
。
- 源碼路徑:
ognl.OgnlRuntime
- 關鍵方法:
convertValue(Object value, Class targetType)
public static Object convertValue(Object value, Class targetType) {if (targetType == int.class || targetType == Integer.class) {if (value instanceof String) {return Integer.valueOf((String) value); // 將空字符串轉換為 0}}return value;
}
在這段代碼中,如果 value
是一個字符串,OGNL 會嘗試將其轉換為 Integer
。空字符串會被轉換為 0
,導致條件 isCollect == 0
被誤評估為 true
。
3. 解決方案
為了避免空字符串被錯誤地轉換為 0
,在 test
條件中顯式檢查 isCollect
是否為 null
或空字符串。
a. 顯式檢查 null
和空字符串
通過添加顯式的判斷條件,可以確保 isCollect
既不為 null
也不為空字符串,從而避免 test="isCollect == 0"
誤判。改寫后的 SQL 語句如下:
<if test="isCollect != null and isCollect != ''"><choose><when test="isCollect == 1">AND exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when><when test="isCollect == 0">AND not exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when></choose>
</if>
通過這種方式,我們可以確保只有在 isCollect
不為 null
且不為空字符串時,才會進行 test="isCollect == 0"
判斷,避免空字符串誤判為 0
。
4. 總結
- OGNL 表達式:在 MyBatis 中,
test="isCollect == 0"
會使用 OGNL 解析,空字符串會被隱式轉換為0
,導致條件判斷錯誤。 - 類型轉換:OGNL 會自動將空字符串
""
轉換為數字0
,從而導致test="isCollect == 0"
被誤判為true
。 - 解決方案:通過顯式檢查
isCollect != null && isCollect != ''
來避免空字符串被誤判為0
。
參考資料
- MyBatis 官方文檔:MyBatis