[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 语句带上了查询条件。

相关推荐
王者鳜錸21 分钟前
Vue3+SpringBoot全栈开发:从零实现增删改查与分页功能
vue.js·spring boot·后端
无妄-202421 分钟前
“刹车思维”:慢,是为了更快
java·经验分享
玉~你还好吗24 分钟前
【FreeRTOS#1】多任务处理&任务调度器&任务状态
java·开发语言
Yusei_052325 分钟前
C++ 模版复习
android·java·c++
ytttr87326 分钟前
k8s的出现解决了java并发编程胡问题了
java·容器·kubernetes
red润31 分钟前
奇怪?为什么 floor((n + t - 1) / t) 比 ceil(n / t) 更高效?(因为没有浮点转换带来的性能损耗)
前端·后端·算法
WindSearcher33 分钟前
关于ReAct Agent的实践
人工智能·后端
前端付豪34 分钟前
连夜睡服低管,后端回滚没你想的简单:网易如何用一套体系保障“无痛撤退”?
前端·后端·架构
纪元A梦1 小时前
分布式拜占庭容错算法——权益证明(PoS)算法详解
java·分布式·算法
老友@1 小时前
Spring Boot 应用中实现配置文件敏感信息加密解密方案
java·spring boot·后端·数据安全·加密·配置文件