事件的缘由是这样的,需求是按条件查数据而后给前端展现就好了,写的时候想着挺简单的,不就是使用 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
获取资料: