MYBATIS中if test中注意的事项

http://www.jianshu.com/p/91ed365c0fdd 现有一项目,ORM框架使用MyBatis,在进行列表查询时,选择一状态(值为0)经过动态SQL拼接where条件但没法返回正常的查询结果,随后进行排查。 POJO private Integer status;//状态,可能为0、一、二、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时该where SQLand status = 0并未正常拼接,也就是说test内的表达式为false,从而致使查询结果错误。可是,显然该值(Integer :0)!= null也!= ' ',应该为true才对。 经过Debug MyBatis源码顺藤摸瓜找到了IfSqlNode类,该类用来处理动态SQL的<if>节点,方法public boolean apply(DynamicContext context)用来构造节点内的SQL语句。if (evaluator.evaluateBoolean(test, context.getBindings())该代码即是解析<if test="status !=null and status !=''">test内表达式的关键,若是表达式为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官网看看是否是咱们的表达式语法有问题从而致使该问题的发生。 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>当status=0时出现的问题了,显然0!=''是不成立的,致使表达式的值为false。 将表达式修改成<if test="status != null">and status = #{status}</if>该问题便迎刃而解。该问题的根源仍是来自编码的不规范,只有String类型才须要判断是否!='',其余类型彻底没有这个必要,多是开发人员为了省事直接复制上一行拿过来改一改或是所使用的MyBatis生成工具不严谨致使该问题的发生。 这里有必要再提一个“坑”,若是你有相似于String str ="A"; <if test="str!= null and str == 'A'">这样的写法时,你要当心了。由于单引号内若是为单个字符时,OGNL将会识别为Java 中的 char类型,显然String 类型与char类型作==运算会返回false,从而致使表达式不成立。解决方法很简单,修改成<if test='str!= null and str == "A"'>便可。