更多文章參考我的筆記:https://www.yuque.com/yinjianwei/vyrvkf
復現異常
我們先通過案例復現該類異常,測試項目地址:https://gitee.com/yin_jw/demo/tree/master/mybatis-demo/springboot-mybatis-demo,StudentMapper.xml 中根據條件獲取學生信息的 SQL 配置如下所示。
<!-- 根據條件獲取學生信息--> <select id="listByConditions" parameterType="studentQuery" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_student <where> <if test="ids != null and ids.size() > 0"> AND id IN <foreach collection="ids" item="item" open="(" close=")" separator=","> #{item} </foreach> </if> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="sex != null and sex != ''"> AND sex = #{sex} </if> <if test="selfcardNo != null"> AND selfcard_no = #{selfcardNo} </if> </where> </select>
該配置問題出在 <if test="sex != null and sex != ''"> 這段代碼,sex 在傳入的參數中是 Byte 類型,屬於數值類型,而 sex != '' 適用於字符串類型判斷,我們誤將數值類型當成字符串類型配置了。
當我們錯誤的配置了 sex 字段的判斷條件,傳入 sex = 0 時,sex != '' 會返回 false,傳入 sex = 1 時,sex != '' 會返回 true,同樣是傳入數字,為什么會出現兩種不同的結果呢?
sex = 0 的執行效果如下圖所示:
sex = 1 的執行效果如下圖所示:
sex = 0 的執行返回內容明顯不是我們需要的結果,下面我們通過源碼分析來解開這個謎題。
分析源碼
通過源碼分析篇中的“MyBatis 源碼篇-SQL 執行的流程”章節,我們知道 MyBatis 執行 SQL 最終都會交給 Executor 類執行。
前面的調試步驟省略,直接進入 CacheExecutor 類的 query 方法。
猜測 MyBatis 根據參數和映射配置文件生成 boundSql 的時候,出現了一些問題。我們一路往下 DEBUG,發現問題出在 MyBatis 對 OGNL 表達式處理上面。
org.apache.ibatis.ognl.OgnlOps#compareWithConversion(Object v1, Object v2) 該方法在比較 (Byte)0 和 "" 時,返回的是 true,也就是該方法認為兩者是相同的,執行結果如下圖所示。
所以,sex = 0 的條件沒有出現在生成的 SQL 中。
那么當 sex = 1 的時候,compareWithConversion(Object v1, Object v2) 方法的執行結果是怎樣的呢?
修改參數,調試測試接口,執行結果如下圖所示:
compareWithConversion(Object v1, Object v2) 方法返回的結果是 false,其實該問題的本質是,org.apache.ibatis.ognl.OgnlOps#doubleValue(Object value) 方法當 value 是空字符串時返回 0.0D。
結論
對於數值類型的參數,在配置 where 條件的時候要注意,不能把它當成字符串類型判斷,數值型只需要判斷 sex != null 就可以了。