現有一項目,ORM框架使用的MyBatis,在進行列表查詢時,選擇一狀態(值為0)通過動態SQL拼接其中條件但無法返回正常的查詢結果,隨后進行排查。
POJO
private Integer status;//狀態,可能為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>
當status
值為0時該SQL在and status = 0
並行正常拼接,也就是說測試的表達式為false
,從而導致查詢結果錯誤。但是,顯然該值(Integer:0)!= null也!='',應該為true
才對。
通過調試MyBatis源碼順藤摸瓜找到了IfSqlNode
類,該類用來處理動態SQL的<if>節點,方法public boolean apply(DynamicContext context)
用來構造節點內的SQL語句。if (evaluator.evaluateBoolean(test, context.getBindings())
該代碼便是解析<if test="status !=null and status !=''">
測試內表達式的關鍵,如果表達式為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類,發現解析表達式使用的是OGNL,如果你使用過古老的Struts框架你應該對它不陌生。通過OgnlCache.getValue(expression, parameterObject);
可以看到表達式的值是從緩存中獲取的,由此可知MyBatis竟然對表達式也做了緩存,以提高性能。
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;
}
跟進去看看,終於找到了解析表達式的方法private static Object parseExpression(String expression)
,該方法會先從緩存取值,如果沒有便進行解析並放入緩存中,調用然后Ognl.getValue(parseExpression(expression), root)
獲得表達式的值。
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)
的英文如何運作的,如果你有興趣可以自行跟下去一探究竟,本文就不贅述了。到此為止,我們已經知道的MyBatis的表達式是用OGNL處理的了,這一點已經夠了。下面我們去OGNL網官看看的英文不是我們的表達式語法有問題從而導致該問題的發生。
將對象解釋為布爾值
任何對象都可以在需要布爾值的地方使用。OGNL將對象解釋為這樣的布爾值:
- 如果該對象是一個布爾值,則會提取並返回其值;
- 如果該對象是一個Number,則將其雙精度浮點值與零進行比較; 非零被視為真,零為假;
- 如果對象是一個字符,那么它的布爾值是true,當且僅當它的char值非零時;
- 否則,當且僅當它非空時,其布爾值才為真。
果然,如果對象是一個數類型,值為0時將被解析為false
,否則為true
,浮點型0.00也是如此.OGNL對於布爾的定義和JavaScript的有點像,即'' == 0 == false
。也就這不難理解<if test="status != null and status !=''">and status = #{status}</if>
當狀態= 0時出現的問題了,顯然0!=''
的英文不成立的,導致表達式的值為假。
表達式將修改為<if test="status != null">and status = #{status}</if>
該問題便迎刃而解。該問題的根源還是來自編碼的不規范,只有字符串類型才需要判斷是否!=''
,其他類型完全沒有這個必要,可能是開發人員為了省事直接復制上一行拿過來改一改或是所使用的MyBatis的生成工具不嚴謹導致該問題的發生。
這里有必要再提一個“坑”,如果有你類似於String str ="A";
<if test="str!= null and str == 'A'">
這樣的寫法時,你要小心了。單因為引號內如果為單個字符時,OGNL將會識別為的Java中的字符類型,顯然字符串類型與焦炭做類型==
運算會報道查看false
,從而導致表達式不成立。解決方法很簡單,為修改<if test='str!= null and str == "A"'>
即可。
作者:簡單的土豆 鏈接:HTTP://www.jianshu.com/p/91ed365c0fdd 來源:簡書 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。