事件的原因是這樣的,需求是按條件查資料然後給前端展示就行了,寫的時候想著挺簡單的,不就是使用 MyBatis 動態 SQL 去查詢資料嗎?
現實還是很殘酷的,等我寫完上完 UAT 後,前端同學說根據state
查的資料與理想的資料不一致,這個state
當時設計時只有兩個值:0
和1
。
/**
* 資料狀態
*/
@Range(min = 0, max = 1, message = "狀態只能為0(未處理),1(已處理)")
private Integer state;
理想情況下通過前端傳遞過來的值,然後進行sql查詢就可以了:
<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
與空字串做比較。
前端在查詢的時如果沒有傳遞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<SqlNode> 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<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
獲取資料: