2.4 SQL片段与include复用


文档摘要

2.4 SQL 片段与 include:缝合线的标准库 本节摘要:sql 标签定义可复用片段,include 标签引用并展开,二者配合消灭列清单与查询条件的重复。本节讲片段的定义位置、refid 的解析规则、带参数的 include 用法,以及"过度抽象"的反面案例——什么时候一段 SQL 重复三遍也不该抽。 参数入端的最后一节,处理一个工程问题:SQL 写多了,重复就来了。 病例背景:二十个查询,一份列清单 用户表三十多个字段,后台有二十来个查询,每个 select 都抄一遍全部列名。某天加了一个字段,改了十九处,漏了第三十处——那个查询的结果对象里新字段永远是 null。这是映射文件最常见的腐化方式:重复的列清单加重复的过滤条件。 sql 与 include 就是为这两类重复准备的。

2.4 SQL 片段与 include:缝合线的标准库

本节摘要:sql 标签定义可复用片段,include 标签引用并展开,二者配合消灭列清单与查询条件的重复。本节讲片段的定义位置、refid 的解析规则、带参数的 include 用法,以及"过度抽象"的反面案例——什么时候一段 SQL 重复三遍也不该抽。

参数入端的最后一节,处理一个工程问题:SQL 写多了,重复就来了。

病例背景:二十个查询,一份列清单

用户表三十多个字段,后台有二十来个查询,每个 select 都抄一遍全部列名。某天加了一个字段,改了十九处,漏了第三十处——那个查询的结果对象里新字段永远是 null。这是映射文件最常见的腐化方式:重复的列清单加重复的过滤条件。

sql 与 include 就是为这两类重复准备的。

列清单片段:最基础的复用

<mapper namespace="com.example.mapper.UserMapper"> <!-- 片段定义:列清单只在这里维护一次 --> <sql id="userColumns"> id, username, email, status, created_time, last_login_time </sql> <!-- 引用:编译前原样展开 --> <select id="findAll" resultType="User"> SELECT <include refid="userColumns"/> FROM users ORDER BY id </select> <select id="findVip" resultType="User"> SELECT <include refid="userColumns"/> FROM users WHERE status = 'VIP' </select> </mapper>

展开发生在配置解析期(工厂构建时),运行期没有额外的拼接开销。加字段时只改片段,二十个查询同步生效——重复的列清单从此只有一个源头。

refid 的解析规则:先在本命名空间找,找不到再去全局找。引用其他命名空间的片段要写全名(如 refid="com.example.mapper.BaseMapper.userColumns");同命名空间内短名即可。跨命名空间引用会建立隐式耦合,除非有公共基片段的明确设计,否则少用。

条件片段:复用过滤器

比列清单更有价值的是查询条件。一套"有效用户"的判定(未删除且状态正常)散落在十几个查询的 WHERE 里,改一次判定规则就是十几次替换:

<!-- 条件片段:把业务判定集中 --> <sql id="activeUserCondition"> deleted = 0 AND status IN ('NORMAL', 'VIP') </sql> <select id="countActive" resultType="long"> SELECT COUNT(*) FROM users WHERE <include refid="activeUserCondition"/> </select> <select id="findActiveByEmail" resultType="User"> SELECT <include refid="userColumns"/> FROM users WHERE <include refid="activeUserCondition"/> AND email = #{email} </select>

业务规则(什么算"有效用户")从十几个 WHERE 收敛成一处定义。规则变更时改片段即可——这是把"业务语义"放进 SQL 层复用的标准手法。

带参数的片段:占位符展开

include 还能向片段传参,片段内用美元占位符接收。典型场景是动态表名或别名字典:

<!-- 动态片段:不同查询用不同表别名 --> <sql id="userColumnsWithAlias"> ${alias}.id, ${alias}.username, ${alias}.email </sql> <select id="findWithOrders" resultMap="userWithOrdersMap"> SELECT <include refid="userColumnsWithAlias"> <property name="alias" value="u"/> <!-- 向片段传别名 --> </include>, o.order_no, o.amount FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.id = #{id} </select>

注意这里的美元占位是安全的:值来自 XML 里写死的 property,不来自用户输入。美元拼接是否危险,取决于值的来源,不取决于符号本身——这一点呼应 2.2 节的分界线。

反面案例:过度抽象的教训

不是所有重复都该抽。见过一个映射文件,把"WHERE 关键字三个字的表都适用的动态条件"抽成了一个带七个参数的巨型片段,任何查询 include 它都要配齐七个 property,可读性比重复差得多。

判断标准三条:

  • 片段应当承载稳定的业务语义(列清单、有效数据判定),而不是"碰巧长得像的 SQL 文本"
  • 片段参数超过三个,说明它想复用的其实是两种不同的查询,拆开更清晰
  • 只出现两次、且演化方向不同的 SQL,重复优于抽象——等第三次出现再抽也不迟(三则重构在 SQL 层同样成立)
适合抽片段 不适合抽片段
列清单 只出现一次的复杂子查询
业务判定条件 结构相似但语义不同的 JOIN
全局排序兜底 参数爆炸的万能条件

⚠️ 常见坑:片段里的井号占位引用的是引用方语句的参数,不是片段自己的。片段不是函数,没有独立参数表——include 传进来的 property 只能用美元占位接收。把两种占位混着用时,先想清楚值从哪来。

💡 关键直觉:sql 片段解决的是"同一业务语义多处手抄"的问题,不是"SQL 文本去重"的问题。抽语义,不抽文本。

本节要点回顾

  • 两类高频片段:列清单与业务判定条件,一个消灭加字段漏改,一个收敛规则定义
  • 解析期展开:include 在工厂构建时展开,运行期零开销;refid 先本地后全局
  • 带参片段:property 配美元占位,值来自 XML 写死的配置,天然安全
  • 三则重构:语义重复出现三次再抽,参数超过三个就该拆
  • 片段非函数:井号占位引用的是引用方参数,property 只走美元通道

第 2 章到此收官:参数入端的术式、安全线、进阶场景与复用术都齐了。下一章调转镜头到手术的出口端——结果集如何被缝合回对象。


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