4.1 if条件与where修剪


文档摘要

4.1 if 条件与 where 修剪 本节摘要:if 标签按 OGNL 表达式决定片段去留,where 标签自动修剪拼装残留的 AND 与 OR 前缀,trim 则是 where 的底层原形。本节从后台多条件筛选的需求出发,走一遍"手拼 SQL 出事故、if 加 where 修复、trim 原理还原"的完整过程。 进入手术中段。这一节处理动态 SQL 里使用频率最高的组合:可空条件怎么上台、多余的前缀怎么剪掉。 事故起点:一个筛选面板拼出的三种 SQL 背景:订单后台筛选面板有四个条件——订单号、客户名、状态、创建日期起点,全部可空。需求是"填了才参与过滤"。 手拼版本(示意,不用任何标签): 没有标签时开发者只能去 Java 里拼字符串或写 WHERE 1=1 兜底。

4.1 if 条件与 where 修剪

本节摘要:if 标签按 OGNL 表达式决定片段去留,where 标签自动修剪拼装残留的 AND 与 OR 前缀,trim 则是 where 的底层原形。本节从后台多条件筛选的需求出发,走一遍"手拼 SQL 出事故、if 加 where 修复、trim 原理还原"的完整过程。

进入手术中段。这一节处理动态 SQL 里使用频率最高的组合:可空条件怎么上台、多余的前缀怎么剪掉。

事故起点:一个筛选面板拼出的三种 SQL

背景:订单后台筛选面板有四个条件——订单号、客户名、状态、创建日期起点,全部可空。需求是"填了才参与过滤"。

手拼版本(示意,不用任何标签):

<!-- 手拼版本:三种输入形态,三种下场 --> <select id="searchOrders" resultType="Order"> SELECT id, order_no, customer_name, status, amount FROM orders WHERE <if>...</if> <!-- 先假设没有标签可用 --> </select>

没有标签时开发者只能去 Java 里拼字符串或写 WHERE 1=1 兜底。前者引入注入面,后者是把无条件也变成"有条件"的将就写法(部分数据库优化器要为此多做一次无谓判断,更重要的是它掩盖了"空条件"这个语义)。MyBatis 的答案是两个标签:if 管片段去留,where 管前后缀修剪。

if:片段的去留权

if 的 test 属性写 OGNL 表达式,成立则片段上台:

<select id="searchOrders" parameterType="OrderQuery" resultType="Order"> SELECT id, order_no, customer_name, status, amount FROM orders <where> <if test="orderNo != null and orderNo != ''"> AND order_no = #{orderNo} </if> <if test="customerName != null and customerName != ''"> AND customer_name LIKE CONCAT('%', #{customerName}, '%') </if> <if test="status != null"> AND status = #{status} </if> <if test="createdFrom != null"> AND created_time &gt;= #{createdFrom} </if> </where> </select>

test 表达式的几条书写要点:字符串判空要双段——先判 null 再判空串,少了后者,前端传空字符串照样拼进 SQL;数值只判 null——Integer 写成 status != null 即可;大于号要转义——XML 里写成 >= 或用 CDATA 包裹(写成 = ]]> 也行);对象属性直接点名——表达式里的名字对应参数对象的属性,2.2 节的命名规则在此照用。

四种输入组合下生成的 SQL(日志实证):

// 组合一:全空 mapper.searchOrders(new OrderQuery()); // ==> Preparing: SELECT id, order_no, ... FROM orders // 无 WHERE:where 标签发现内部一片空,整个子句不上台 // 组合二:只有状态 q.setStatus("PAID"); // ==> Preparing: SELECT ... FROM orders WHERE status = ? // 组合三:状态加客户名 q.setStatus("PAID"); q.setCustomerName("张"); // ==> Preparing: SELECT ... FROM orders // WHERE customer_name LIKE '%' ? '%' AND status = ? // 第一个条件的 AND 被 where 剪掉

where:自动修剪的范围

where 元素的行为规则只有三条:内部至少一个条件成立才输出 WHERE 关键字;输出前把开头多余的 AND 或 OR 剪掉;开头之外的 AND 不动(那是条件之间的合法连接)。

它不剪结尾、不剪中间。如果拼接习惯是"条件写在前、AND 写在后"(前缀式写法),where 完美适配;反过来用"后缀式"(每个条件尾部带 AND),结尾残留它不管——那是 set 标签或 trim 的业务。

trim:where 的底层原形

where 不是独立机制,它就是 trim 的一个预设。看 trim 的四个属性就能还原:

<!-- 手工还原 where:prefix 补关键字,prefixOverrides 剪残头 --> <select id="searchOrdersTrim" parameterType="OrderQuery" resultType="Order"> SELECT id, order_no, customer_name, status, amount FROM orders <trim prefix="WHERE" prefixOverrides="AND | OR"> <if test="orderNo != null and orderNo != ''"> AND order_no = #{orderNo} </if> <if test="status != null"> AND status = #{status} </if> </trim> </select>

四个属性两两配对:prefix 与 suffix 是"内容非空时补上的首尾";prefixOverrides 与 suffixOverrides 是"补上之前要剪掉的残头残尾"。where 等价于 prefix 为 WHERE、prefixOverrides 为 AND 或 OR;set 等价于 prefix 为 SET、suffixOverrides 为逗号(下一节展开)。

04-01-fig01-3

理解 trim 之后,where 与 set 不再需要死记:它们只是两种常用的预设参数。需要"值列表包在括号里、剪掉开头逗号"这类特殊修剪时,trim 随时可造。

全空兜底与两个习惯

条件全部为空时,where 整体消失,查询退化为全表扫描。若业务上不允许无条件拉全表,防护要自己做:

// 服务层防护:至少一个条件才放行 public List<Order> search(OrderQuery q) { if (q.isEmptyCondition()) { throw new IllegalArgumentException("至少填写一个筛选条件"); } return mapper.searchOrders(q); }

⚠️ 常见坑一:字符串条件只判 null 不判空串,用户清空输入框后拼出 AND order_no = '',查出空结果还以为是数据问题。坑二:where 内多个条件之间靠 AND 连接,写 if 时漏了 AND 或把 AND 写在 where 直接子级(不在 if 里),条件组合一变就语法报错。

💡 关键直觉:动态 SQL 的每个标签都对应一类历史事故——if 对应"条件硬编码"、where 对应"前缀残缺"、trim 对应"预设不够用"。按事故理解标签,比按语法背标签记得牢。

本节要点回顾

  • if 三要点:字符串双段判空、数值只判 null、test 里点名参数属性
  • where 三规则:有条件才输出、只剪开头 AND 或 OR、中间结尾不动
  • trim 四属性:prefix 补头、suffix 补尾、两个 Overrides 剪残头残尾,where 与 set 皆为预设
  • 全空退化:无条件即全表扫描,业务不允许就在服务层拦
  • 组合习惯:条件片段一律前缀式(AND 写头),与 where 的修剪范围对齐

条件上台与修剪的机制就位,下一节看互斥分支 choose 与更新专用的 set,顺带收下模糊查询的 bind 技巧。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U