mybatis 标签不能直接写 sql 关键字,因其仅为条件包裹器,不处理语法连贯性;若前面无 where,and name = #{name} 会孤立报错,须配合 自动剔除首冗余 and 并补 where。

MyBatis <if></if> 标签里为什么不能直接写 SQL 关键字
因为 <if></if> 只是条件包裹器,它不会自动处理 SQL 语法连贯性。比如你写:
<if test="name != null"> AND name = #{name} </if>,一旦前面没 WHERE 或其他条件,这句就会变成孤立的 AND,数据库直接报错。
常见错误现象:MySQLSyntaxErrorException: You have an error in your SQL syntax; near 'AND name = ?'
- 必须确保
<if></if>插入的位置本身处于合法 SQL 上下文中(比如已有WHERE) - 更稳妥的做法是把整个 WHERE 子句交给
<where></where>管理,而不是手写WHERE+ 多个<if></if> -
<if></if>内部不要写空格开头或结尾的 SQL 片段,MyBatis 不会帮你 trim —— 容易多出空格导致语法错
为什么一定要用 <where></where> 包裹 <if></if>,而不是自己拼 WHERE
<where></where> 不是“加个 WHERE”那么简单,它的核心作用是:自动剔除第一个匹配 <if></if> 前多余的 AND / OR,并补上 WHERE 关键字(如果需要)。
使用场景:查询条件完全动态(可能全为空、可能只传 id、也可能传 name 和 status)
- 不用
<where></where>:得自己判断是否要加WHERE,还要手动删掉第一个AND,逻辑臃肿易错 -
<where></where>会扫描所有子标签,只要有一个<if></if>生效,就插入WHERE;再把第一个生效<if></if>的内容前缀(如AND)自动去掉 - 注意:
<where></where>只识别以AND或OR开头的行首空白+关键字,所以<if></if>里写name = #{name}是无效的,必须写成AND name = #{name}
示例正确写法:
<where><br> <if test="id != null"> AND id = #{id} </if><br> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if><br></where>
<if></if> 的 test 表达式里哪些写法容易出错
MyBatis 的 OGNL 表达式看着像 Java,但规则更严格,很多日常写法在这里会静默失败或抛异常。
- 字符串判空别只写
name != null,要加name != ''或用!StringUtils.isBlank(name)(前提是已引入工具类且配置了 static method 支持) - 对象属性访问必须可 getter:
user.name要求User有getName(),不能直接用字段名 - 集合判非空别用
list.size > 0,应写list != null and list.size() > 0(OGNL 中size是方法,不是属性) - 数值比较慎用
==:对包装类型建议统一用eq(如status eq 1),避免null == 1返回true的陷阱
多个 <if></if> 嵌套或组合时性能与可读性怎么平衡
嵌套 <if></if>(比如在 <if test="type == 'user'"></if> 里面再套 <if test="name != null"></if>)会让 XML 难读难调,而且 MyBatis 不做短路求值 —— 所有 test 表达式都会执行,哪怕外层已为 false。
- 优先拆成扁平结构:用逻辑运算符合并条件,比如
test="type == 'user' and name != null" - 复杂判断抽到 DAO 层预处理:比如把多个状态映射成一个查询标志位,XML 里只判一个字段
- 别在
test里调用耗时方法(如 DB 查询、IO),OGNL 执行无上下文控制,容易拖慢整个 SQL 构建 - 调试时可在日志中打开
org.apache.ibatis.logging.stdout.StdOutImpl,看最终生成的 SQL 是什么样子 —— 很多问题其实一眼就能发现多出了空格或漏了括号
真正麻烦的不是语法怎么写,而是当 <if></if> 套了三层、每个还带 OR 分支时,你根本没法靠肉眼确认所有路径都覆盖到了。这时候不如改用 <choose><when></when></choose>,或者干脆让业务代码分发不同 mapper 方法。










