现有一项目,ORM框架使用的MyBatis,在进行列表查询时,选择一状态(值为0)通过动态SQL拼接其中条件但无法返回正常的查询结果,随后进行排查。

POJO

  1. private Integer status;//状态,可能为0、1、2、3。
  2. //...省略其他

Mapper XML

  1. <sql>
  2. <trim prefix="where" prefixOverrides="and | or ">
  3. //...省略其他
  4. <if test="status != null and status !=''">and status = #{status}</if>
  5. <trim prefix="where" prefixOverrides="and | or ">
  6. </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,否则忽略。

  1. public class IfSqlNode implements SqlNode {
  2. private ExpressionEvaluator evaluator;
  3. private String test;
  4. private SqlNode contents;
  5. public IfSqlNode(SqlNode contents, String test) {
  6. this.test = test;
  7. this.contents = contents;
  8. this.evaluator = new ExpressionEvaluator();
  9. }
  10. public boolean apply(DynamicContext context) {
  11. if (evaluator.evaluateBoolean(test, context.getBindings())) {
  12. contents.apply(context);
  13. return true;
  14. }
  15. return false;
  16. }
  17. }

打开ExpressionEvaluator类,发现解析表达式使用的是OGNL,如果你使用过古老的Struts框架你应该对它不陌生。通过OgnlCache.getValue(expression, parameterObject);可以看到表达式的值是从缓存中获取的,由此可知MyBatis竟然对表达式也做了缓存,以提高性能。

  1. public class ExpressionEvaluator {
  2. public boolean evaluateBoolean(String expression, Object parameterObject) {
  3. Object value = OgnlCache.getValue(expression, parameterObject);
  4. if (value instanceof Boolean) return (Boolean) value;
  5. if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
  6. return value != null;
  7. }

跟进去看看,终于找到了解析表达式的方法private static Object parseExpression(String expression),该方法会先从缓存取值,如果没有便进行解析并放入缓存中,调用然后Ognl.getValue(parseExpression(expression), root)获得表达式的值。

  1. public class OgnlCache {
  2. private static final Map<String, ognl.Node> expressionCache = new ConcurrentHashMap<String, ognl.Node>();
  3. public static Object getValue(String expression, Object root) throws OgnlException {
  4. return Ognl.getValue(parseExpression(expression), root);
  5. }
  6. private static Object parseExpression(String expression) throws OgnlException {
  7. try {
  8. Node node = expressionCache.get(expression);
  9. if (node == null) {
  10. node = new OgnlParser(new StringReader(expression)).topLevelExpression();
  11. expressionCache.put(expression, node);
  12. }
  13. return node;
  14. } catch (ParseException e) {
  15. throw new ExpressionSyntaxException(expression, e);
  16. } catch (TokenMgrError e) {
  17. throw new ExpressionSyntaxException(expression, e);
  18. }
  19. }

至于Ognl.getValue(parseExpression(expression), root)的英文如何运作的,如果你有兴趣可以自行跟下去一探究竟,本文就不赘述了。到此为止,我们已经知道的MyBatis的表达式是用OGNL处理的了,这一点已经够了。下面我们去OGNL网官看看的英文不是我们的表达式语法有问题从而导致该问题的发生。

将对象解释为布尔值

任何对象都可以在需要布尔值的地方使用。OGNL将对象解释为这样的布尔值:

  1. 如果该对象是一个布尔值,则会提取并返回其值;
  2. 如果该对象是一个Number,则将其双精度浮点值与零进行比较; 非零被视为真,零为假;
  3. 如果对象是一个字符,那么它的布尔值是true,当且仅当它的char值非零时;
  4. 否则,当且仅当它非空时,其布尔值才为真。

果然,如果对象是一个数类型,值为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"'>即可。

  1. 作者:简单的土豆
  2. 链接:HTTP//www.jianshu.com/p/91ed365c0fdd
  3. 来源:简书
  4. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

使用MyBatis的动态SQL表达式时遇到的“坑”(integer)的更多相关文章

  1. mybatis 的动态sql语句是基于OGNL表达式的。

    mybatis 的动态sql语句是基于OGNL表达式的.可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:1. if 语句 (简单的条件判断)2. c ...

  2. MyBatis的动态SQL详解

    MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑,本文详解mybatis的动态sql,需要的朋友可以参考下 MyBatis 的一个强大的特性之一通常是它 ...

  3. mybatis 使用动态SQL

    RoleMapper.java public interface RoleMapper { public void add(Role role); public void update(Role ro ...

  4. MyBatis框架——动态SQL、缓存机制、逆向工程

    MyBatis框架--动态SQL.缓存机制.逆向工程 一.Dynamic SQL 为什么需要动态SQL?有时候需要根据实际传入的参数来动态的拼接SQL语句.最常用的就是:where和if标签 1.参考 ...

  5. MyBatis探究-----动态SQL详解

    1.if标签 接口中方法:public List<Employee> getEmpsByEmpProperties(Employee employee); XML中:where 1=1必不 ...

  6. mybatis中的.xml文件总结——mybatis的动态sql

    resultMap resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功. 如果sql查询字段名和pojo的属性名不一致,可以通过re ...

  7. MyBatis的动态SQL详解-各种标签使用

    MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑. MyBatis中用于实现动态SQL的元素主要有: if choose(when,otherwise) ...

  8. Java-MyBatis:MyBatis 3 动态 SQL

    ylbtech-Java-MyBatis:MyBatis 3 动态 SQL 1.返回顶部 1. 动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其它类似框架 ...

  9. Mybatis解析动态sql原理分析

    前言 废话不多说,直接进入文章. 我们在使用mybatis的时候,会在xml中编写sql语句. 比如这段动态sql代码: <update id="update" parame ...

随机推荐

  1. python常用模块:标准文件及模块练习

    1.请写出规范目录 并解释各文件夹的作用 bin 执行文件core 核心业务逻辑conf 配置文件lib 库.公共代码.第三方模块db 数据分析log 日志文件readme 文本文档 2.改造atm+ ...

  2. @font-face字图标问题

    链接:http://www.exp99.com/htmlcss/htmlcss_229.html

  3. php的lareval框架配置出错

    前两天,在学习php的 lareval 框架时,从官网上下载完lareval的安装包后,按照网上的配置教程一点一点的配置完后,当点击public 文件运行时就出现一个让我很头痛的问题,我自己外加两个大 ...

  4. MyBatis中<![CDATA[ ]]>的使用

    原文地址:https://www.cnblogs.com/catgatp/p/6403382.html <![CDATA[]]>和转义字符 被<![CDATA[]]>这个标记所 ...

  5. TETP服务和PXE功能

    PXE PXE:Preboot Excution Environment, Intel公司研发,没有任何操作系统的主机,能够基于网络完成系统的安装工作.

  6. 02-SQLServer数据库附加后处于只读模式

    一.总结 附加数据库后,遇到只读,并且取消只读的时候报错操作系统错误,一般都是附加的时候,使用的是SQLServer用户登录附加的,只要使用windows用户登录数据库重新附加一下就ok了. 二.处理 ...

  7. puppet完全攻略(一)puppet应用原理及安装部署

    puppet完全攻略(一)puppet应用原理及安装部署 2012-06-06 18:27:56 标签:puppet puppet应用原理 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出 ...

  8. 【备忘录】ORACLE数据库每日计划EXPDP备份

        1.OracleBackup_expdp版本|oracle.bat文件 还需手动更改的内容如下: 调用格式需改成call %~dp0\OracleBackup 数据库 用户名 密码 文件夹名称 ...

  9. convert:图片转pdf失败

     造冰箱的大熊猫@cnblogs 2019/1/21 1.环境 操作系统/Ubuntu 16.04.5 LTS,内核/4.15.0-43-generic,convert/ImageMagick 6.8 ...

  10. CF1155D Beautiful Array 贪心,dp

    CF115DBeautiful Array 题目大意:给一个有n个元素的a数组,可以选择其中一个区间的所有数都乘上x,也可以不选,求最大子序列和. 如果没有前面的操作,就是只求最大子序列和,我们都知道 ...