MyBatis-Plus XML 中 if 标签单引号与双引号区别、踩坑问题完整讲解

MyBatis-Plus XML 中 if 标签单引号与双引号区别、踩坑问题完整讲解

前言

很多开发在写 MyBatis/MyBatis-Plus XML 动态 SQL 时,经常遇到<if test="xxx != ''">​、<if test='xxx != ""'>​两种写法,看似只是引号不同,运行时却出现判断失效、参数匹配异常、SQL 报错、空字符串误判等诡异 BUG。本文结合 OGNL 表达式底层原理,讲清单双引号本质区别、各自适用场景、高频踩坑案例与标准规范写法。

一、底层核心:MyBatis test 判断使用 OGNL 表达式

​<if test="判断条件">​ 内部不是普通 SQL,是 ​OGNL 表达式​,引号解析规则由 OGNL 控制:

  1. ​双引号 " "​:OGNL 中代表字符串常量

  2. ​单引号 ' '​:OGNL 中代表单个字符常量

这是所有坑的根源,很多人误以为单双引号只是包裹字符串,实际 OGNL 对二者定义完全不同。

二、两种写法基础对比

1. 双引号写法(推荐标准写法)

xml

<!-- 正确:"" 代表空字符串 "" -->
<if test="username != ''">
    AND username = #{username}
</if>

含义:判断入参username​不等于​空字符串​,符合日常业务判断逻辑。

2. 单引号写法(极易踩坑)

xml

<!-- 错误示范:'' 代表两个字符常量,不是空字符串 -->
<if test='username != ""'>
    AND username = #{username}
</if>

当你在外层用单引号包裹整个 test,内部双引号才是字符串;如果写成 <if test="username != ''">​ 内部单引号,逻辑完全错乱。

三、高频踩坑场景与问题复现

场景 1:<if test="name != ''">​ 单引号判断空字符串失效

错误代码:

xml

<!-- 致命问题:'' 在OGNL里是char字符,ASCII值32,不是空字符串 -->
<if test="name != ''">
    AND name = #{name}
</if>

问题现象:

  1. 前端传空字符串 ""​ 时,判断依然成立,SQL 拼接 AND name = ''​,查询结果不符合预期;

  2. 原理:

    • ​''​ = 两个单引号 = OGNL 识别为​字符对象​,类型为Character​;

    • ​""​ = 双引号 = OGNL 识别为字符串对象 String​;

    • ​字符串""​ 和 字符''​ 类型不同,永远不相等,判断恒为 true。

场景 2:外层单引号包裹 test,内部使用双引号(可行但不推荐)

xml

<!-- 语法可行,但可读性差,容易混淆 -->
<if test='phone != "" and phone != null'>
    AND phone = #{phone}
</if>

解析逻辑:

  • 外层' '​ 是 XML 属性包裹符;

  • 内层""​ 才是 OGNL 字符串常量,判断逻辑正常;
    缺点:多层引号嵌套,修改时极易写错引号,团队统一规范不建议。

场景 3:判断数字 / 字符常量混淆报错

需求:判断类型不等于字符1​错误写法:

xml

<!-- 报错:类型不匹配,int和Character无法对比 -->
<if test="type != '1'">

正确写法(数字用双引号字符串):

xml

<if test="type != "1">

报错原因:'1'​ 是单个字符,实体字段 type 为 Integer,数字与字符对比 OGNL 抛出类型转换异常。

场景 4:同时判 null 和空字符串,引号混用导致全量查询

错误代码:

xml

<if test="nickname != null and nickname != ''">
    AND nickname = #{nickname}
</if>

问题:nickname 传空字符串时,nickname != ''​ 恒成立,拼接无效条件,查出全部数据。

四、衍生业务问题案例

案例 1:模糊查询误匹配空字符串,全表扫描

业务:根据用户名模糊搜索,为空时不拼接条件错误 XML:

xml

<select id="listUser" resultType="User">
    SELECT id,username,phone FROM t_user
    <where>
        <if test="username != ''">
            username LIKE CONCAT('%',#{username},'%')
        </if>
    </where>
</select>

异常表现:前端不传用户名(空串),SQL 依然拼接 username LIKE '%%'​,查出所有用户,无分页性能灾难。修复:将''​改为""​。

案例 2:新增接口非必填字段,单引号判断导致更新覆盖空值

更新逻辑:昵称不为空才更新错误代码:

xml

<update id="updateUser">
    UPDATE t_user
    <set>
        <if test="nickname != ''">nickname = #{nickname},</if>
        age = #{age}
    </set>
    WHERE id = #{id}
</update>

问题:前端传 nickname="",代码误以为有值,执行nickname = ''​,清空原有昵称数据。

五、标准规范写法(统一团队代码,杜绝引号 BUG)

规范 1:统一使用双引号判断空字符串(最优)

xml

<!-- 标准通用写法,同时判null、空串 -->
<if test="username != null and username != ''">
    AND username = #{username}
</if>

规范 2:判断单个字符常量用单引号(仅字符场景)

xml

<!-- 判断字符类型字段,使用单引号 -->
<if test="sex != 'M'">
    AND sex = #{sex}
</if>

规范 3:判断数字统一双引号

xml

<if test="status != "0">
    AND status = #{status}
</if>

规范 4:外层禁止使用单引号包裹 test 属性

不推荐:

xml

<if test='name != ""'></if>

统一标准:所有test=""​外层固定双引号。

六、补充拓展:OGNL 引号额外小知识

  1. OGNL 中 'abc'​ 非法:单引号只能包裹​单个字符​,多字符必须用双引号 "abc"​;

  2. 如果需要字面量单引号,使用转义 \'​;

  3. MyBatis 先解析 XML 属性,再执行 OGNL 表达式,两层解析容易造成引号冲突;

  4. MyBatis-Plus 自带的QueryWrapper​代码块不存在该问题,仅 XML 动态 SQL 有引号坑。

七、总结避坑清单

  1. 判断​空字符串​:必须写 != ""​,禁止 != ''​;

  2. 判断​单个字符​:可用单引号 'Y'/'N'/'M'​;

  3. 判断​数字、多字符字符串​:统一双引号 "1"​、"admin"​;

  4. test 属性外层永远使用双引号 test=""​,不混用单引号外层;

  5. 所有字符串判断必须同时加 != null​,防止空指针;

  6. 历史代码批量排查:全局检索 != ''​ 统一替换为 != ""​。

文末

MyBatis 动态 SQL 引号问题属于高频隐形 BUG,不会直接抛异常,但会造成数据查询、更新逻辑错乱,线上极易产生业务故障。统一团队 XML 引号书写规范,能从根源规避这类低级问题,提升 SQL 稳定性。

MySQL 创建仅管理单个数据库的专属用户 2026-07-17