事件的原因是这样的,需求是按条件查数据然后给前端展示就行了,写的时候想着挺简单的,不就是使用 MyBatis 动态 SQL 去查询数据吗?

现实还是很残酷的,等我写完上完 UAT 后,前端同学说根据state查的数据与理想的数据不一致,这个state当时设计时只有两个值:01

/**
* 数据状态
*/
@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.stateInteger型的,仔细查看req.state != null没毛病,然后发现req.state != ''使用Integer与空字符串做比较。

前端在查询的时如果没有传递req.statereq.state != null 这里不会满足,但是前端传递了一个0过来的时候req.state != ''居然返回的是false也就是说在MyBatis的if语法中0是等于空字符串的

{
"state": 0
}

这样的比较没有报错,也是有点想不通了,没办法只能去看MyBatis源码找出这原因。

查看 MyBatis 源码

MyBatis 其他源码的查找过程就不详细说了,这里直接找到XMLScriptBuilder类,找到if语法的解析过程,然后一步步的探究0 == ''的原因。 XMLScriptBuilder会解析trimif等 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 != null and state != ''", objectMap);
System.out.println(value); }

上面程序输出的真的是true。。。

总结

真是脑袋抽筋啊,Integer还判断是否为空字符串。。。

记录此坑,希望对大家有所帮助。

推荐

欢迎关注公众号:架构文摘,获得独家整理120G的免费学习资源助力你的架构师学习之路!

公众号后台回复arch028获取资料:

MyBatis if 标签的坑,居然被我踩到了。。。的更多相关文章

  1. Mybatis foreach标签含义

    背景 考虑以下场景: InfoTable(信息表): Name Gender Age Score 张三 男 21 90 李四 女 20 87 王五 男 22 92 赵六 女 19 94 孙七 女 23 ...

  2. MyBatis 别名标签 & sql的复用

    1.MyBatis 别名标签 如果在映射文件中,大量使用类名比较长,可以在sqlMapConfig.xml声明别名, 在映射文件中可以使用别名缩短配置,注意此配置要放在最前面 sqlMapConfig ...

  3. mybatis <forEach>标签的使用

    MyBatis<forEach>标签的使用 你可以传递一个 List 实例或者数组作为参数对象传给 MyBatis.当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中,用名 ...

  4. mybatis resultmap标签type属性什么意思

    mybatis resultmap标签type属性什么意思? :就表示被转换的对象啊,被转换成object的类型啊 <resultMap id="BaseResultMap" ...

  5. MyBatis - 常用标签与动态Sql

    MyBatis常用标签 ● 定义sql语句:select.insert.delete.update ● 配置JAVA对象属性与查询结构及中列明对应的关系:resultMap ● 控制动态sql拼接:i ...

  6. mybatis : trim标签, “等于==”经验, CDATA标签 ,模糊查询CONCAT,LIKE

    一.My Batis trim标签有点类似于replace效果. trim 属性, prefix:前缀覆盖并增加其内容 suffix:后缀覆盖并增加其内容 prefixOverrides:前缀判断的条 ...

  7. MyBatis foreach标签遍历数组

    有时候开发中需要根据多个ID去查询,可以将ID封装为List或者数组然后使用MyBatis中的foreach标签构建in条件. 这里我将ID封装为String[]作为参数. <select id ...

  8. MyBatis <if>标签的一些问题

    1.常见错误: There is no getter for property named 'parentId' in 'class java.lang.Long'(或者String) org.myb ...

  9. mybatis include标签

    使用mybatis 的include标签达到SQL片段达到代码复用的目的 示例: xml文件 <sql id="paysql"> payid,p.oid,p.bdate ...

随机推荐

  1. tcp 输入 简析 转载

    正常来说 TCP 收消息过程会涉及三个队列: Backlog Queue sk->sk_backlog Prequeue tp->ucopy.prequeue Receive Queue  ...

  2. 理解 Linux 的硬链接与软链接(转)

    Linux 的文件与目录 现代操作系统为解决信息能独立于进程之外被长期存储引入了文件,文件作为进程创建信息的逻辑单元可被多个进程并发使用.在 UNIX 系统中,操作系统为磁盘上的文本与图像.鼠标与键盘 ...

  3. 美团面试官问我: ZGC 的 Z 是什么意思

    本文的阅读有一定的门槛,请先了解 GC 的基本只知识. 现代垃圾收集器的演进大部分都是往减少停顿方向发展. 像 CMS 就是分离出一些阶段使得应用线程可以和垃圾回收线程并发,当然还有利用回收线程的并行 ...

  4. 利用matlibplot绘制雷达图

    之前在一些数据分析案例中看到用 Go 语言绘制的雷达图,非常的漂亮,就想着用matlibplot.pyplot也照着画一个,遗憾的是matlibplot.pyplot模块中没有直接绘制雷达图的函数,不 ...

  5. phpstudy2016-2018漏洞验证

    影响版本 漏洞验证 查看目录下 php_xmlrpc.dll PHPTutorial\php\php-5.4.45\ext\php_xmlrpc.dll存在@eval(%s('%s'));即说明有后门 ...

  6. python-网络安全编程第五天(爬虫模块BeautifulSoup)

    前言 昨晚学的有点晚 睡得很晚了,今天早上10点多起来吃完饭看了会电视剧就瞌睡了一直睡到12.50多起来洗漱给我弟去开家长会 开到快4点多才回家.耽搁了不少学习时间,现在就把今天所学的内容总结下吧. ...

  7. 运维自动化之11 - 自动化部署之jenkins及简介

    https://www.cnblogs.com/jimmy-xuli/p/9020825.html

  8. 阿里面试官:小伙子,给我说一下Spring 和 Spring Boot 的区别吧

    前言 对于 Spring和 SpringBoot到底有什么区别,我听到了很多答案,刚开始迈入学习 SpringBoot的我当时也是一头雾水,随着经验的积累.我慢慢理解了这两个框架到底有什么区别,相信对 ...

  9. Elements-of-Python01_Input/Output

    (内容包括输入input,输出print,注释comment,预测类型转换eval,命名与赋值Name & Bestow) 输入Input 和 输出Print Python中利用input() ...

  10. 如何突出显示PDF文档中的一些重要文本信息

    PDF文档中如果存在着太多的文字时,阅读者会容易遗漏很多重要的信息.但如果,文档中存在着一些特殊标记的文字时,比如标黄.标红文本时,很多人都会给予特别关注. 因此,当大家在使用pdfFactory专业 ...