事件的緣由是這樣的,需求是按條件查數據而後給前端展現就好了,寫的時候想着挺簡單的,不就是使用 MyBatis 動態 SQL 去查詢數據嗎?前端
現實仍是很殘酷的,等我寫完上完 UAT 後,前端同窗說根據state
查的數據與理想的數據不一致,這個state
當時設計時只有兩個值:0
和1
。java
/** * 數據狀態 */ @Range(min = 0, max = 1, message = "狀態只能爲0(未處理),1(已處理)") private Integer state;
理想狀況下經過前端傳遞過來的值,而後進行sql查詢就能夠了:node
<if test="req.state != null and req.state != ''"> AND md.state = #{req.state} </if>
上面的sql首先判斷state
不爲空且不爲空字符串時,而後添加比較state
字段。初步看下來if
判斷沒什麼問題,可是我傳遞進去的req.state
是Integer
型的,仔細查看req.state != null
沒毛病,而後發現req.state != ''
使用Integer
與空字符串作比較。mysql
前端在查詢的時若是沒有傳遞req.state
那req.state != null
這裏不會知足,可是前端傳遞了一個0
過來的時候req.state != ''
竟然返回的是false
也就是說在MyBatis的if語法中0是等於空字符串的:sql
{ "state": 0 }
這樣的比較沒有報錯,也是有點想不通了,沒辦法只能去看MyBatis源碼找出這緣由。express
MyBatis 其餘源碼的查找過程就不詳細說了,這裏直接找到XMLScriptBuilder
類,找到if
語法的解析過程,而後一步步的探究0 == ''
的緣由。 XMLScriptBuilder
會解析trim
、if
等 MyBatis 支持的語法,它的解析原理是經過NodeHandler
來分別解析不一樣的標籤:mybatis
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<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }
MyBatis會將if
標籤抽象成IfSqlNode
:app
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
:tcp
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<Object> answer = new ArrayList<Object>(); 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表達式
的引發的呢? 實踐一下就知道了,先引入依賴:
<!-- https://mvnrepository.com/artifact/ognl/ognl --> <dependency> <groupId>ognl</groupId> <artifactId>ognl</artifactId> <version>2.7.3</version> </dependency>
寫程序測試:
public static void main(String[] args) { Map<String, Object> objectMap = new HashMap<>(); objectMap.put("state", 0); Object value = OgnlCache.getValue("state == ''", objectMap); System.out.println(value); }
上面程序輸出的真的是true
。。。
真是腦殼抽筋啊,Integer
還判斷是否爲空字符串。。。
記錄此坑,但願對你們有所幫助。
歡迎關注公衆號:架構文摘,得到獨家整理120G的免費學習資源助力你的架構師學習之路!
公衆號後臺回覆
arch028
獲取資料: