问题引入
在一次需求开发中,涉及到了使用 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 的基本功能
- 属性访问:可以通过 OGNL 表达式访问对象的属性。
- 方法调用:可以在 OGNL 表达式中调用对象的方法。
- 条件判断 :可以使用类似
if
、and
、or
语法进行逻辑判断。 - 集合操作:可以对集合进行遍历、查找、过滤等操作。
通过 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 语句带上了查询条件。