1. 程式人生 > >MyBatis if 標籤的坑,居然被我踩到了。。。

MyBatis if 標籤的坑,居然被我踩到了。。。

事件的原因是這樣的,需求是按條件查資料然後給前端展示就行了,寫的時候想著挺簡單的,不就是使用 MyBatis 動態 SQL 去查詢資料嗎? ![](https://img-blog.csdnimg.cn/img_convert/b2dc78f451c7a2e563bc877c7caa7ecb.webp?x-oss-process=image/format,png) 現實還是很殘酷的,等我寫完上完 UAT 後,前端同學說根據`state`查的資料與理想的資料不一致,這個`state`當時設計時只有兩個值:`0`和`1`。 ``` /** * 資料狀態 */ @Range(min = 0, max = 1, message = "狀態只能為0(未處理),1(已處理)") private Integer state; ``` 理想情況下通過前端傳遞過來的值,然後進行sql查詢就可以了: ``` AND md.state = #{req.state} ``` 上面的sql首先判斷`state`不為空且**不為空字串**時,然後新增比較`state`欄位。初步看下來`if`判斷沒什麼問題,但是我傳遞進去的`req.state`是`Integer`型的,仔細檢視`req.state != null`沒毛病,然後發現`req.state != ''`使用`Integer`與空字串做比較。 前端在查詢的時如果沒有傳遞`req.state`那`req.state != null `這裡不會滿足,但是前端傳遞了一個`0`過來的時候`req.state != ''`居然返回的是`false`也就是說在**MyBatis的if語法中0是等於空字串的**: ``` { "state": 0 } ``` 這樣的比較沒有報錯,也是有點想不通了,沒辦法只能去看MyBatis原始碼找出這原因。 ### 檢視 MyBatis 原始碼 MyBatis 其他原始碼的查詢過程就不詳細說了,這裡直接找到`XMLScriptBuilder`類,找到`if`語法的解析過程,然後一步步的探究`0 == ''`的原因。 `XMLScriptBuilder`會解析`trim`、`if`等 MyBatis 支援的語法,它的解析原理是通過`NodeHandler`來分別解析不同的標籤: ``` private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); } ``` 由於是不正解的語法是`if`標籤,檢視`IfHandler`就好了,其他現在略過就好。 ``` private class IfHandler implements NodeHandler { public IfHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } } ``` MyBatis會將`if`標籤抽象成`IfSqlNode`: ``` public class IfSqlNode implements SqlNode { private final ExpressionEvaluator evaluator; private final String test; private final SqlNode contents; public IfSqlNode(SqlNode contents, String test) { this.test = test; this.contents = contents; this.evaluator = new ExpressionEvaluator(); } @Override public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } } ``` 終於有一點眉頭了, MyBatis 會將`if`標籤的`test`屬性使用`ExpressionEvaluator`測試一下是否為`true`或者為`false`: ``` public class ExpressionEvaluator { public boolean evaluateBoolean(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value instanceof Boolean) { return (Boolean) value; } if (value instanceof Number) { return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0; } return value != null; } public Iterable evaluateIterable(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value == null) { throw new BuilderException("The expression '" + expression + "' evaluated to a null value."); } if (value instanceof Iterable) { return (Iterable) value; } if (value.getClass().isArray()) { // the array may be primitive, so Arrays.asList() may throw // a ClassCastException (issue 209). Do the work manually // Curse primitives! :) (JGB) int size = Array.getLength(value); List answer = new ArrayList(); for (int i = 0; i < size; i++) { Object o = Array.get(value, i); answer.add(o); } return answer; } if (value instanceof Map) { return ((Map) value).entrySet(); } throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable."); } } ``` 最後得到結論:**Mybatis 使用的 `Ognl表示式` 來獲取 test 屬性的值** ### 最終論證 已經知道 MyBatis 內部是使用的 `Ognl表示式` ,是不是 `Ognl表示式` 的引起的呢? 實踐一下就知道了,先引入依賴: ```
ognl ognl 2.7.3 ``` 寫程式測試: ``` public static void main(String[] args) { Map objectMap = new HashMap<>(); objectMap.put("state", "0"); Object value = OgnlCache.getValue("state != null and state != ''", objectMap); System.out.println(value); } ``` 上面程式輸出的真的是`true`。。。 ### 總結 真是腦袋抽筋啊,`Integer`還判斷是否為空字串。。。 記錄此坑,希望對大家有所幫助。 ### 推薦 * [如果MySQL磁碟滿了,會發生什麼? ](http://www.javaobj.com/2020/12/what-happens-if-my-mysql-disk-is-full/) * [TCP 三次握手、四手揮手,這樣說你能明白吧! ](http://www.javaobj.com/2020/10/tcp-protocol/) * [Netflix 微服務架構設計解析](http://www.javaobj.com/2020/08/a-design-analysis-of-cloud-based-microservices-architecture-at-netflix/) > 歡迎關注公眾號:架構文摘,獲得獨家整理120G的免費學習資源助力你的架構師學習之路! > > **公眾號後臺回覆`arch028`獲取資料:**