提升 SQL 配置可维护性、减少代码冗余的核心
一、概述
在 MyBatis 的 Mapper 映射文件中,经常会出现重复的 SQL 内容(如查询字段列表、通用 WHERE 条件、表关联语句等),该机制的核心目的是将重复 SQL 片段进行统一提取、封装,实现跨 SQL 语句的复用,避免相同代码多次编写,后续修改只需调整统一的 SQL 片段,无需逐个修改所有相关 SQL,大幅提升配置的可维护性和开发效率
其中 <sql> 负责「定义 / 提取」可复用 SQL 片段,<include> 负责「引用 / 调用」已定义的 SQL 片段,两者相辅相成,构成完整的 SQL 片段复用体系,适用于所有包含重复 SQL 的场景
二、核心
1. <sql>:SQL 片段定义 / 提取标签
-
核心作用 :专门用于封装、提取可复用的 SQL 片段,将重复出现的合法 SQL 内容(字段、条件、表名等)包裹在该标签内,形成独立的可引用单元
-
核心特性与配置规则:
-
必配
id属性:作为 SQL 片段的唯一标识 ,在当前 Mapper 映射文件中不可重复,供后续<include>标签通过refid属性精准引用(如<sql id="User_Column_List">) -
支持封装的 SQL 内容:可封装任意合法 SQL 片段,无特殊限制,最常用场景包括:
- 查询 / 插入 / 更新的字段列表(如
id, username, age, mobile) - 通用 WHERE 查询条件(如
WHERE del_flag = 0,逻辑删除标识) - 多表关联的 JOIN 语句(如
JOIN role r ON u.role_id = r.id) - 表名、排序语句(如
ORDER BY create_time DESC)
- 查询 / 插入 / 更新的字段列表(如
-
可嵌套动态 SQL 标签:片段内支持
<if>、<choose>等动态 SQL 标签,实现「动态可复用 SQL」,适配复杂业务场景 -
无执行能力:
<sql>标签仅用于定义片段,自身不会被 MyBatis 执行,必须通过<include>引用后,才会随主 SQL 一起解析执行
-
-
典型示例
<!-- 定义可复用的用户字段列表片段 --> <sql id="User_Column_List"> id, username, age, mobile, create_time, del_flag </sql>
2. <include>:SQL 片段引用 / 调用标签
-
核心作用 :专门用于引用
<sql>标签定义的可复用 SQL 片段,将片段内容嵌入到当前主 SQL 语句的对应位置,与主 SQL 拼接为完整的可执行 SQL。 -
核心特性与配置规则:
- 必配
refid属性:指定要引用的<sql>片段的id(如<include refid="User_Column_List"/>),若引用其他 Mapper 文件中的 SQL 片段,可添加命名空间前缀(格式:命名空间.sql片段id,如com.club.dao.UserDao.User_Column_List) - 支持动态传递参数:可通过
<include>内部嵌套<property>标签,向 SQL 片段传递动态参数,实现片段的灵活适配(解决固定片段无法适配多场景的问题) - 拼接无冗余:MyBatis 解析时,会将
<include>替换为对应<sql>片段的内容,与主 SQL 无缝拼接,不会产生多余的空格或语法错误 - 可多次引用:同一个
<sql>片段可被多个<select>、<insert>、<update>等标签中的<include>引用,实现最大化复用
- 必配
-
典型示例:
<!-- 引用已定义的字段列表片段,拼接完整查询SQL --> <select id="getUserList" resultType="User"> SELECT <include refid="User_Column_List"/> FROM user WHERE del_flag = 0 </select> <!-- 动态传递参数的进阶示例 --> <sql id="Table_Column_List"> ${tableAlias}.id, ${tableAlias}.username </sql> <select id="getUserWithRole" resultType="User"> SELECT <include refid="Table_Column_List"> <property name="tableAlias" value="u"/> </include> FROM user u JOIN role r ON u.role_id = r.id </select>
三、 延伸
-
SQL 片段的复用范围
- 默认范围:当前 Mapper 映射文件内,直接通过
refid引用<sql>片段的id即可; - 跨 Mapper 文件复用:需在
refid中添加目标 Mapper 文件的namespace前缀(目标 Mapper 文件的<mapper>标签namespace属性值),格式为namespace.sqlFragmentId,实现全局复用。
- 默认范围:当前 Mapper 映射文件内,直接通过
-
使用注意事项(避免语法错误)
- 片段内不要添加多余的标点符号(如末尾的逗号):若片段是字段列表,末尾加逗号会导致主 SQL 拼接后出现
SELECT id, username, FROM user的语法错误; - 保证片段的合法性:
<sql>封装的片段必须是可拼接的合法 SQL,单独存在可能无意义,但与主 SQL 拼接后需满足 SQL 语法规范; - 避免过度封装:仅对重复出现的 SQL 片段进行封装,简单的单次 SQL 无需封装,否则会增加配置复杂度,降低可读性。
- 片段内不要添加多余的标点符号(如末尾的逗号):若片段是字段列表,末尾加逗号会导致主 SQL 拼接后出现
-
与动态 SQL 的结合使用
<sql>片段内可嵌套<if>、<where>、<foreach>等动态 SQL 标签,实现「动态可复用片段」,例如封装通用的多条件查询条件:<!-- 定义动态可复用的查询条件片段 --> <sql id="User_Query_Condition"> <where> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <if test="age != null"> AND age = #{age} </if> </where> </sql> <!-- 引用动态条件片段,拼接完整查询SQL --> <select id="queryUser" resultType="User"> SELECT <include refid="User_Column_List"/> FROM user <include refid="User_Query_Condition"/> </select>
四、核心价值
- 减少代码冗余:避免相同 SQL 片段多次编写,简化 Mapper 映射文件的配置,提升文件可读性
- 提升可维护性:后续修改重复 SQL 内容时,只需调整统一的
<sql>片段,无需逐个修改所有相关 SQL,降低维护成本和出错概率 - 增强灵活性:支持跨 Mapper 复用、动态传递参数,适配不同业务场景的需求,提升开发效率
总结
- 核心知识点:MyBatis SQL 片段提取与复用机制(
<sql>定义 +<include>引用) - 本题考点:
<sql>(提取可复用 SQL 片段,必配id)、<include>(引用片段,必配refid) - 核心价值:减少冗余、提升可维护性,是 MyBatis 配置优化的常用技巧
- 关键注意:保证 SQL 拼接合法性,避免过度封装,支持与动态 SQL 结合使用