4.2 choose分支与set动态更新


文档摘要

4.2 choose 分支与 set 动态更新 本节摘要:choose、when、otherwise 实现多路互斥的条件选择,语义对应 Java 的 switch;set 标签专为 UPDATE 服务,自动剪掉拼接残留的尾逗号。本节用"会员等级路由"讲 choose,用"只更新非空字段"的完整病例讲 set,收尾补上模糊查询的 bind 技巧。 上一节的 if 是"各自独立上台",这一节的 choose 是"一群里挑一个"。两者语义不同,混用的代价是查询行为与预期不符。 choose:一群条件里挑一个 背景:运营的导出规则优先级很明确——填了订单号就按订单号精确导出;没填就看客户名,按客户模糊导出;都没有就导出全部已支付订单。三个策略互斥,优先级从上到下。

4.2 choose 分支与 set 动态更新

本节摘要:choose、when、otherwise 实现多路互斥的条件选择,语义对应 Java 的 switch;set 标签专为 UPDATE 服务,自动剪掉拼接残留的尾逗号。本节用"会员等级路由"讲 choose,用"只更新非空字段"的完整病例讲 set,收尾补上模糊查询的 bind 技巧。

上一节的 if 是"各自独立上台",这一节的 choose 是"一群里挑一个"。两者语义不同,混用的代价是查询行为与预期不符。

choose:一群条件里挑一个

背景:运营的导出规则优先级很明确——填了订单号就按订单号精确导出;没填就看客户名,按客户模糊导出;都没有就导出全部已支付订单。三个策略互斥,优先级从上到下。

用 if 写这个需求要靠精心排列的条件表达式(比如第二项要写 orderNo 为空且 customerName 非空),条件一多就是灾难。choose 直接表达互斥:

<select id="exportByStrategy" parameterType="OrderQuery" resultType="Order"> SELECT id, order_no, customer_name, status, amount FROM orders <where> <choose> <!-- when 按顺序判定,命中一个即停 --> <when test="orderNo != null and orderNo != ''"> order_no = #{orderNo} </when> <when test="customerName != null and customerName != ''"> customer_name LIKE CONCAT('%', #{customerName}, '%') </when> <!-- 所有 when 都未命中时的兜底 --> <otherwise> status = 'PAID' </otherwise> </choose> </where> </select>
// 三种输入的实际 SQL(日志实证) mapper.exportByStrategy(queryWithOrderNo()); // WHERE order_no = ? mapper.exportByStrategy(queryWithCustomerName()); // WHERE customer_name LIKE '%' ? '%' mapper.exportByStrategy(emptyQuery()); // WHERE status = 'PAID' otherwise 兜底

与 Java 的对照一张表记牢:

Java MyBatis 语义
switch choose 多路选择的容器
case when 按序判定、命中即停
default otherwise 全未命中时兜底
if if 独立判定、可叠加

判断口诀:条件之间是"并且可组合"用 if,是"优先级互斥"用 choose。筛选面板属于前者,策略路由属于后者。另注意 otherwise 可以省略——省略且全部 when 未命中时,choose 输出空串,配上 where 则 WHERE 消失,效果等同查全表。

set:UPDATE 的专用修剪

背景:编辑用户资料,表单里只改了邮箱和昵称。更新语句按直觉写会踩坑——把没填的字段也 UPDATE 一遍,把数据库里原有的值冲成 null。

第一版错误写法与修正过程如下。

<!-- 错误版本:字段固定,未传的属性被冲成 null --> <update id="updateProfile" parameterType="User"> UPDATE users SET nickname = #{nickname}, email = #{email}, phone = #{phone} WHERE id = #{id} </update>

表单只传了昵称与邮箱时,phone 是 null,这条 UPDATE 把电话列清空。改成"只更新非空字段":

<!-- 修正版本:set 配 if,只拼非空字段 --> <update id="updateProfile" parameterType="User"> UPDATE users <set> <if test="nickname != null and nickname != ''"> nickname = #{nickname}, </if> <if test="email != null and email != ''"> email = #{email}, </if> <if test="phone != null"> phone = #{phone}, </if> </set> WHERE id = #{id} </update>
User form = new User(); form.setId(1); form.setNickname("新昵称"); form.setEmail("new@example.com"); // form.phone 未设置,保持 null int rows = mapper.updateProfile(form); session.commit(); // 日志:UPDATE users SET nickname = ?, email = ? WHERE id = ? // phone 片段未上台,数据库原值得以保留 System.out.println(rows); // 1

set 的修剪规则:内部有内容才输出 SET 关键字,并剪掉结尾多余的逗号(与 where 剪开头 AND 镜像对称)。用 trim 还原它:prefix 为 SET、suffixOverrides 为逗号。

全空防线:所有字段都为 null 时,set 输出空串,SQL 变成 UPDATE users WHERE id = 1 的残缺语句,直接语法报错。宁可报错也别静默全表——但更体面的做法是服务层先拦:

// 防线:至少一个可更新字段才发 UPDATE if (form.hasNoUpdatableField()) { return 0; // 或抛出明确的业务异常 } return mapper.updateProfile(form);

变式(乐观锁版本):并发编辑场景给表加 version 列,更新带上版本条件,行数为 0 即提示重试:

<update id="updateProfileWithVersion" parameterType="User"> UPDATE users <set> <if test="nickname != null and nickname != ''">nickname = #{nickname},</if> <if test="email != null and email != ''">email = #{email},</if> version = version + 1, </set> WHERE id = #{id} AND version = #{version} </update>

version 片段不带 if、永远上台,既保证每次编辑推进版本,又让 SET 永不为空,顺带消灭了全空残缺问题——这是生产上最常见的完整形态(呼应 2.1 节"行数即断言")。

bind:给模糊查询打个结

各数据库的字符串拼接函数不同(CONCAT、双竖线、LIKE 语法差异),跨库时拼百分号容易写出方言。bind 在语句内部先算一个变量,SQL 引用它:

<select id="searchByKeyword" parameterType="string" resultType="User"> <!-- 先把关键词包上百分号,命名 pattern --> <bind name="pattern" value="'%' + keyword + '%'"/> SELECT id, nickname, email FROM users WHERE nickname LIKE #{pattern} </select>
mapper.searchByKeyword("admin"); // ==> Parameters: %admin%(String)

bind 的 value 是 OGNL 表达式,能调用参数对象的属性做字符串运算;算出的值用井号引用,走预编译通道——比在 Java 侧手工拼百分号干净,也比在 SQL 里写方言函数可移植。

⚠️ 常见坑:choose 里嵌 if 或者在 when 的 test 里写复合优先级,都是把互斥语义悄悄改坏的路径。互斥逻辑交给 choose 的结构表达,when 里只写单条件的判定,可读性和正确性都更高。

💡 关键直觉:set 的尾逗号问题源于"每个字段片段自带逗号"的书写约定——约定让片段独立可删,修剪让拼接合法。所有动态 SQL 标签都在做同一件事:让片段可独立增删,再自动收拾增删留下的残局

本节要点回顾

  • choose 三件套:when 按序判定命中即停、otherwise 兜底、可省略但省略等于放行全表
  • if 与 choose 的分界:可叠加的组合条件用 if,优先级互斥的策略路由用 choose
  • set 专管 UPDATE:只拼非空字段、剪尾逗号,避免把未传字段冲成 null
  • 全空两道防线:服务层拦截是最体面的;乐观锁版本片段是生产最常用的"永久非空"手法
  • bind 算变量:模糊查询的百分号在语句内打好结,井号引用仍走预编译

条件缝合的两板斧齐了,下一节处理集合:foreach 如何把一个列表拆开缝进 IN 括号与批量语句,以及它的两条警戒线。


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