[Mybatis] 因 0 != null and 0 != '' 酿成的事故,害得我又过点啦!

问题引入

在一次需求开发中,涉及到了使用 Mybatis 编写 SQL 语句。其中,我使用到了 Mybatis 提供的 XML 文件中的 <if> 标签,众所周知,<if> 标签可以根据传入参数的值或表达式的结果,动态决定某段 SQL 是否被拼接进最终执行的 SQL 中,从而避免拼接无效 SQL、简化 SQL 逻辑:

xml 复制代码
<select id="findUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 1=1
  <if test="name != null and name != ''">
    AND name = #{name}
  </if>
  <if test="age != null">
    AND age = #{age}
  </if>
</select>
  • test 是一个 OGNL 表达式,可以判断参数是否为空、是否满足某个条件。
  • <if> 标签常常与 <where><choose><trim> 等一起使用,构建更复杂的动态 SQL。
  • 它只决定某部分 SQL 是否加入,不处理拼接符(比如 WHERE、AND、OR) ,这要靠 <where><trim> 来辅助。

test表达式的坑

我在开发那个需求时候需要对某个 Integer 类型的字段判断是否要拼接。于是我直接在原有的 SQL 语句后面加上如下片段:

xml 复制代码
<if test="status != null and status != ''">
    AND status = #{status}
</if>

结果我发现假如 status 字段值传入的是 0 时,那么这一个 SQL 不会拼接到最后的 SQL 语句中,只有传入的值是 1 时,才会拼接进去。

通过Debug复现

我使用了 Debug 模式检查了 Mybatis 最后生成的 SQL 语句,在 PreparedStatementHandler 类的 query 方法中打一个断点,然后运行单元测试:

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserMapperTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void testFindByCondition() {
        User conditionBean = new User();
        conditionBean.setStatus(0);
        List<User> users = userMapper.findByCondition(conditionBean);
        System.out.println(users);
    }

}

在 Debug 模式中可以看到最终生成的 SQL 语句:

我们的查询条件并没有拼接进去,但是 status 字段是并不为 null

OGNL表达式

要想知道为什么那个查询条件没有拼接进去,那么我们首先得了解一下什么是 OGNL 表达式。

OGNL(Object-Graph Navigation Language)是一种用于操作 Java 对象图的表达式语言,通常在 Java 应用程序中用于访问、修改对象属性和执行方法调用。OGNL 主要用于 Java 的框架中,比如 MyBatis、Spring、Struts 等,用于处理动态属性和动态 SQL。

在 MyBatis 中,OGNL 被用来在 SQL 中动态地处理输入参数、判断条件和计算结果。它允许你在 SQL 语句中对参数进行灵活的操作,比如条件判断、属性访问、集合遍历等。

OGNL 的基本功能

  1. 属性访问:可以通过 OGNL 表达式访问对象的属性。
  2. 方法调用:可以在 OGNL 表达式中调用对象的方法。
  3. 条件判断 :可以使用类似 ifandor 语法进行逻辑判断。
  4. 集合操作:可以对集合进行遍历、查找、过滤等操作。

通过 OGNL 可以访问 Java 对象的属性。假设你有一个 User 类:

java 复制代码
public class User {
    private String name;
    private int age;

    // getter and setter
}

可以使用 OGNL 表达式访问其属性:

  • user.name 访问 User 对象的 name 属性。
  • user.age 访问 age 属性。

测试一下表达式

在了解上面这些概念之后,我们可以使用 OGNL 库才来测试一下在 XML 文件中那个 test 表达式。

首先导入依赖项:

xml 复制代码
<dependency>
   <groupId>ognl</groupId>
   <artifactId>ognl</artifactId>
   <version>3.1.26</version>
</dependency>

粘贴一下单元测试代码:

java 复制代码
public class OGNLTest {

    @Test
    public void testOGNL() throws OgnlException {
        User user = new User();
        user.setStatus(0);
        OgnlContext context = new OgnlContext();
        context.put("user", user);
        String expression = "user.status != null and user.status != ''";
        Object result = Ognl.getValue(expression, context, context);
        System.out.println(result);

        String expression1 = "user.status != ''";
        Object result1 = Ognl.getValue(expression1, context, context);
        System.out.println(result1);

        String expression2 = "0 == ''";
        Object result2 = Ognl.getValue(expression2, context, context);
        System.out.println(result2);
    }

}

这充分证明了 0 != '' 值为 false,在 OGNL 表达式中 0 值被当做跟空字符串 '' 一样的东西。

那么问题就迎刃而解了,只要把 test 表达式中的 statis != '' 片段删除即可。

删除之后的效果

statis != '' 删除之后再 debug 显示如下:

问题完美解决,最终生成的 SQL 语句带上了查询条件。

相关推荐
我叫小白菜15 分钟前
【Java_EE】单例模式、阻塞队列、线程池、定时器
java·开发语言
Albert Edison1 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
超级小忍1 小时前
JVM 中的垃圾回收算法及垃圾回收器详解
java·jvm
weixin_446122461 小时前
JAVA内存区域划分
java·开发语言·redis
Piper蛋窝2 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
勤奋的小王同学~2 小时前
(javaEE初阶)计算机是如何组成的:CPU基本工作流程 CPU介绍 CPU执行指令的流程 寄存器 程序 进程 进程控制块 线程 线程的执行
java·java-ee
TT哇2 小时前
JavaEE==网站开发
java·redis·java-ee
2401_826097622 小时前
JavaEE-Linux环境部署
java·linux·java-ee
缘来是庄2 小时前
设计模式之访问者模式
java·设计模式·访问者模式
Bug退退退1233 小时前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq