mybatis中的if-test判断解读

 更新时间:2024年11月04日 08:59:17   作者:xinlianluohan  
在使用MyBatis进行条件判断时,如果条件中涉及到字符与数字的比较,需要特别注意比较方式,例如,在<if>标签中,比较数字“1”时,应将其写在双引号中,或者使用.toString()方法,避免直接使用字符'1'进行比较

mybatis if-test判断解读

<if test="takeWay == '0'">】mybatis的if判断

单个的字符要写到双引号里面才行,改为<if test='takeWay == "1"'>或者改为<if test="takeWay == '1'.toString() ">

.xml文件的部分代码

<insert id="insertDelivery" parameterType="com.zuci.request.DeliveryPreferenceReq">     
    insert cx_customer_deliverypreference     
    <trim prefix="(" suffix=")" suffixOverrides=",">           
        .... 此处省略       
        <if test="takeWay == '1' and workday != null ">         
            WORKDAY,       
        </if>       
        ....     
    </trim>
         
    <trim prefix="values (" suffix=")" suffixOverrides=",">          
        .... 此处省略          
        <if test="takeWay == '1' and workday != null ">            
            #{workday, jdbcType=VARCHAR},     
        </if>      
        ....   
    </trim>
</insert>

takeWay == “1”处出错,导致不执行if判断中的sql,运行程序不报错,没有任何提示。去掉takeWay == “1” and 则可执行。对此我百思不得其解,

因为自己有写过如下代码,是没错的。

<if test="messageType == 'senderReceiveSuccess' ">
      ......
</if>
  • 把<if test="takeWay == '1' and workday != null ">
  • 改为<if test='takeWay == "1" and workday != null '>
  • 或改为<if test="takeWay == '1'.toString() and workday != null ">即可。

原因是:mybatis是用OGNL表达式来解析的,在OGNL的表达式中,’1’会被解析成字符,java是强类型的,char 和 一个string 会导致不等,所以if标签中的sql不会被解析。

总结下使用方法:单个的字符要写到双引号里面或者使用.toString()才行!

使用Mybatis时,常常会判断属性是否为空

  • 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时该where SQL and status = 0并未正常拼接,也就是说test内的表达式为false,从而导致查询结果错误。但是,显然该值(Integer :0)!= null也!= ' ',应该为true才对。

当status为Integer类型,并且status值为0时,该if判断却为false。

当status为0时,Mybatis会解析成'' 空字符串。

为了避免这个问题,改成下面这样写,去掉对空字符的判断,就解决了该问题

<if test="status != null">and status = #{status}</if> 

原因分析

通过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"'>即可。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot和前端Vue的跨域问题及解决方案

    SpringBoot和前端Vue的跨域问题及解决方案

    所谓跨域就是从 A 向 B 发请求,如若他们的地址协议、域名、端口都不相同,直接访问就会造成跨域问题,跨域是非常常见的现象,这篇文章主要介绍了解决SpringBoot和前端Vue的跨域问题,需要的朋友可以参考下
    2023-11-11
  • Java中创建ZIP文件的方法

    Java中创建ZIP文件的方法

    本文通过一段简单代码给大家介绍了java中创建zip文件的方法,代码超简单,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-06-06
  • SpringBoot加载不出来application.yml文件的解决方法

    SpringBoot加载不出来application.yml文件的解决方法

    这篇文章主要介绍了SpringBoot加载不出来application.yml文件的解决方法,文中通过示例代码讲解的非常详细,对大家的学习或者工作有一定的帮助,需要的朋友跟着小编来一起来学习吧
    2023-12-12
  • Java多线程并发开发之DelayQueue使用示例

    Java多线程并发开发之DelayQueue使用示例

    这篇文章主要为大家详细介绍了Java多线程并发开发之DelayQueue使用示例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • Spring实战之Bean的后处理器操作示例

    Spring实战之Bean的后处理器操作示例

    这篇文章主要介绍了Spring实战之Bean的后处理器操作,结合实例形式详细分析了Bean的后处理器相关配置、操作方法及使用注意事项,需要的朋友可以参考下
    2019-12-12
  • Java 二分查找算法的实现

    Java 二分查找算法的实现

    这篇文章主要介绍了Java 如何实现二分查找算法,帮助大家更好的理解和学习Java 算法,感兴趣的朋友可以了解下
    2020-09-09
  • Java 多线程的同步代码块详解

    Java 多线程的同步代码块详解

    这篇文章主要介绍了Java 多线程的同步代码块,使用synchronized关键字创建线程同步方法是实现线程同步的关键,需要的朋友可以参考下
    2021-10-10
  • Java二叉树路径和代码示例

    Java二叉树路径和代码示例

    这篇文章主要介绍了Java二叉树路径和代码示例,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • java类和对象原理与用法分析

    java类和对象原理与用法分析

    这篇文章主要介绍了java类和对象原理与用法,结合实例形式分析了java类和对象的相关概念、功能、原理、使用技巧与操作注意事项,需要的朋友可以参考下
    2020-02-02
  • 大模型chat/completions和completions区别解析

    大模型chat/completions和completions区别解析

    OpenAI的completions和chat/completions是两个不同的端点,completions用于单次文本补全,而chat/completions用于多轮对话生成,选择哪个端点取决于你的具体需求,本文介绍大模型chat/completions和completions区别,感兴趣的朋友一起看看吧
    2025-03-03

最新评论