現(xiàn)有一項目,ORM框架使用MyBatis墨吓,在進(jìn)行列表查詢時球匕,選擇一狀態(tài)(值為0)通過動態(tài)SQL拼接where條件但無法返回正常的查詢結(jié)果,隨后進(jìn)行排查帖烘。
POJO
private Integer status;//狀態(tài)亮曹,可能為0、1、2乾忱、3讥珍。
//...省略其他
Mapper XML
<sql>
<trim prefix="where" prefixOverrides="and | or ">
//...省略其他
<if test="status != null and status !=''">and status = #{status}</if>
<trim prefix="where" prefixOverrides="and | or ">
</sql>
當(dāng)status
的值為 0時該where SQLand status = 0
并未正常拼接,也就是說test內(nèi)的表達(dá)式為false
窄瘟,從而導(dǎo)致查詢結(jié)果錯誤衷佃。但是,顯然該值(Integer :0)蹄葱!= null也氏义!= ' ',應(yīng)該為true
才對图云。
通過Debug MyBatis源碼順藤摸瓜找到了IfSqlNode
類惯悠,該類用來處理動態(tài)SQL的<if>節(jié)點,方法public boolean apply(DynamicContext context)
用來構(gòu)造節(jié)點內(nèi)的SQL語句竣况。if (evaluator.evaluateBoolean(test, context.getBindings())
該代碼便是解析<if test="status !=null and status !=''">
test內(nèi)表達(dá)式的關(guān)鍵克婶,如果表達(dá)式為true則拼接SQL,否則忽略丹泉。
public class IfSqlNode implements SqlNode {
private ExpressionEvaluator evaluator;
private String test;
private SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
打開ExpressionEvaluator 類情萤,發(fā)現(xiàn)解析表達(dá)式使用的是OGNL,如果你使用過古老的Struts框架你應(yīng)該對它不陌生摹恨。通過OgnlCache.getValue(expression, parameterObject);
可以看到表達(dá)式的值是從緩存中獲取的筋岛,由此可知MyBatis竟然對表達(dá)式也做了緩存,以提高性能晒哄。
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)).equals(BigDecimal.ZERO);
return value != null;
}
跟進(jìn)去看看睁宰,終于找到了解析表達(dá)式的方法private static Object parseExpression(String expression)
,該方法會先從緩存取值,如果沒有便進(jìn)行解析并放入緩存中寝凌,然后調(diào)用Ognl.getValue(parseExpression(expression), root)
獲得表達(dá)式的值柒傻。
public class OgnlCache {
private static final Map<String, ognl.Node> expressionCache = new ConcurrentHashMap<String, ognl.Node>();
public static Object getValue(String expression, Object root) throws OgnlException {
return Ognl.getValue(parseExpression(expression), root);
}
private static Object parseExpression(String expression) throws OgnlException {
try {
Node node = expressionCache.get(expression);
if (node == null) {
node = new OgnlParser(new StringReader(expression)).topLevelExpression();
expressionCache.put(expression, node);
}
return node;
} catch (ParseException e) {
throw new ExpressionSyntaxException(expression, e);
} catch (TokenMgrError e) {
throw new ExpressionSyntaxException(expression, e);
}
}
至于Ognl.getValue(parseExpression(expression), root)
是如何運作的,如果你有興趣可以自行跟下去一探究竟硫兰,本文就不贅述了诅愚。到此為止,我們已經(jīng)知道MyBatis的表達(dá)式是用OGNL處理的了劫映,這一點已經(jīng)夠了违孝。下面我們?nèi)?a target="_blank" rel="nofollow">OGNL官網(wǎng)看看是不是我們的表達(dá)式語法有問題從而導(dǎo)致該問題的發(fā)生。
Interpreting Objects as Booleans
Any object can be used where a boolean is required. OGNL interprets objects as booleans like this:
- If the object is a Boolean, its value is extracted and returned;
- If the object is a Number, its double-precision floating-point value is compared with zero; non-zero is treated as true, zero as false;
- If the object is a Character, its boolean value is true if and only if its char value is non-zero;
- Otherwise, its boolean value is true if and only if it is non-null.
果然泳赋,如果對象是一個Number類型雌桑,值為0時將被解析為false
,否則為true
祖今,浮點型0.00也是如此校坑。OGNL對于boolean的定義和JavaScript有點像拣技,即'' == 0 == false
。這也就不難理解<if test="status != null and status !=''">and status = #{status}</if>
當(dāng)status=0時出現(xiàn)的問題了耍目,顯然0膏斤!=''
是不成立的,導(dǎo)致表達(dá)式的值為false邪驮。
將表達(dá)式修改為<if test="status != null">and status = #{status}</if>
該問題便迎刃而解莫辨。該問題的根源還是來自編碼的不規(guī)范,只有String類型才需要判斷是否毅访!=''
沮榜,其他類型完全沒有這個必要,可能是開發(fā)人員為了省事直接復(fù)制上一行拿過來改一改或是所使用的MyBatis生成工具不嚴(yán)謹(jǐn)導(dǎo)致該問題的發(fā)生喻粹。
這里有必要再提一個“坑”蟆融,如果你有類似于String str ="A";
<if test="str!= null and str == 'A'">
這樣的寫法時,你要小心了守呜。因為單引號內(nèi)如果為單個字符時型酥,OGNL將會識別為Java 中的 char類型,顯然String 類型與char類型做==
運算會返回false
查乒,從而導(dǎo)致表達(dá)式不成立冕末。解決方法很簡單,修改為<if test='str!= null and str == "A"'>
即可侣颂。
推薦兩篇文章: