MYBATIS中if test中注意的事项

探讨了在MyBatis中使用动态SQL时遇到的一个常见问题:当查询条件为0时,动态SQL未能正确拼接where子句。文章深入解析了问题原因,并给出了正确的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.jianshu.com/p/91ed365c0fdd

现有一项目,ORM框架使用MyBatis,在进行列表查询时,选择一状态(值为0)通过动态SQL拼接where条件但无法返回正常的查询结果,随后进行排查。
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 SQLand status = 0并未正常拼接,也就是说test内的表达式为false,从而导致查询结果错误。但是,显然该值(Integer :0)!= null也!= ' ',应该为true才对。

通过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"'>即可。
<think>好的,我现在需要帮助用户解决关于MyBatis中<if test>使用样例的问题。用户已经提供了四个引用内容,我需要仔细分析这些内容并结合用户的上一次问题来给出合适的回答。 首先,用户的上一次问题是关于编程中if条件测试的语法和应用,特别是跨语言的差异和常见错误。现在用户转向MyBatis中的<if test>使用,这属于MyBatis动态SQL的一部分。根据用户提供的引用,引用[1]和[3]提到了MyBatis中的<choose>、<when>、<otherwise>以及<if>和<foreach>的使用,引用[3]还给出了一个更新操作的例子,其中使用了多个<if>标签来动态组装SQL语句。引用[4]则涉及MyBatis的配置文件,可能对理解上下文有帮助,但当前问题主要关注动态SQL中的<if test>。 我需要确保回答结构清晰,符合用户之前设定的格式,即分点说明,包含示例代码,并正确引用提供的参考资料。同时,根据用户的要求,需要生成相关问题,并在回答末尾添加引用标识。 首先,回答需要概述<if test>的基本用法,然后给出不同场景下的示例,比如条件查询、更新操作、结合其他标签使用等。每个示例应包含代码片段,并解释其作用。此外,需要提到注意事项,如OGNL表达式、空值处理、逗号处理等,这些都是用户可能遇到的常见问题。 接下来,检查引用内容:引用[3]中的更新示例非常适合作为动态更新的样例,展示如何根据参数是否存在来决定是否包含某个字段。引用[1]中的<choose>结构可以作为多条件分支的示例,但用户主要问的是<if test>,所以可能需要简要提到,但重点还是在单独的<if>使用上。此外,用户可能还需要知道如何避免SQL语法错误,比如在动态字段前处理逗号的问题,这在引用[3]的示例中是通过在每个条件块前加逗号,但第一个条件不加,这可能导致错误,所以需要指出正确的处理方式,比如使用<set>标签或trim标签。 另外,需要确保示例代码的语法正确,比如在MyBatis的XML映射文件中,使用<if test>的正确写法,参数如何引用(如使用#{}),以及OGNL表达式的正确使用,比如判断字符串是否为空时,需要同时检查null和空字符串,如引用[3]中的例子:test="id != null and id != ''"。 可能还需要补充一些其他常见用例,例如在where子句中使用<if>来动态添加条件,并提到<where>标签自动处理AND/OR的问题,避免SQL语法错误。此外,可以提到使用<if>进行插入操作时的动态列处理,但用户提供的引用中没有相关例子,需要根据知识库补充。 最后,生成的相关问题需要围绕MyBatis动态SQL的其他标签、常见错误处理、与注解方式的对比等,确保问题有针对性,并引导用户进一步学习。</think>### MyBatis 中 `<if test>` 使用详解与样例 MyBatis 的 `<if test>` 是动态 SQL 的核心标签之一,用于根据条件判断是否包含某段 SQL 代码。以下是具体使用场景和示例,结合引用内容优化说明: --- #### 1. **基础用法:条件查询** 在 `WHERE` 子句中动态拼接条件,避免无效查询字段。 **示例**:根据参数动态过滤数据 ```xml <select id="selectUser" parameterType="map" resultType="User"> SELECT * FROM user WHERE 1=1 <if test="name != null"> AND name = #{name} </if> <if test="age != null and age > 0"> AND age = #{age} </if> </select> ``` - **说明**:若 `name` 或 `age` 参数为空,则跳过对应条件[^3]。 --- #### 2. **动态更新字段** 在 `UPDATE` 语句中按需修改字段,避免全字段更新(引用[3]的优化案例)。 **示例**:选择性更新商品信息 ```xml <update id="updateGoods" parameterType="map"> UPDATE goods SET <if test="num != null"> num = #{num}, </if> <if test="price != null"> price = #{price}, </if> <if test="name != null"> name = #{name} </if> WHERE id = #{id} </update> ``` - **注意**:需处理末尾多余的逗号,推荐用 `<trim>` 或 `<set>` 标签(如 `<set>` 自动处理逗号)[^3]。 --- #### 3. **结合 `<choose>` 实现多分支** 类似 `if-else if-else` 结构(引用[1]的模板样例)。 **示例**:优先级条件判断 ```xml <select id="selectOrder" parameterType="map" resultType="Order"> SELECT * FROM order <where> <choose> <when test="status != null"> status = #{status} </when> <when test="userId != null"> user_id = #{userId} </when> <otherwise> create_time > '2023-01-01' </otherwise> </choose> </where> </select> ``` - **说明**:按条件优先级执行首个匹配的分支[^1]。 --- #### 4. **插入操作中的动态列** 动态生成插入字段(需配合 `<trim>` 处理逗号)。 **示例**:按需插入用户数据 ```xml <insert id="insertUser" parameterType="User"> INSERT INTO user <trim prefix="(" suffix=")" suffixOverrides=","> <if test="name != null">name,</if> <if test="email != null">email,</if> <if test="age != null">age</if> </trim> VALUES <trim prefix="(" suffix=")" suffixOverrides=","> <if test="name != null">#{name},</if> <if test="email != null">#{email},</if> <if test="age != null">#{age}</if> </trim> </insert> ``` --- #### 5. **常见问题与规避** - **OGNL 表达式语法**: 条件中直接使用参数属性,如 `test="user.age > 18"`。 - **空字符串判断**: 需同时检查 `null` 和空值,如 `test="name != null and name != ''"`[^3]。 - **数值零的陷阱**: 避免 `test="num != null"` 忽略 `num=0`,应明确判断 `num != null or num == 0`。 - **特殊符号转义**: XML 中 `<`、`>` 需写为 `<`、`>`。 --- ### 配置文件关联说明 动态 SQL 需在 MyBatis 映射文件(如 `mapper-locations` 配置的 `*.xml`)中编写,并确保参数类型(如 `parameterType="map"`)与接口一致[^2][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值