SQL 片段的提取与复用机制

提升 SQL 配置可维护性、减少代码冗余的核心

一、概述

在 MyBatis 的 Mapper 映射文件中,经常会出现重复的 SQL 内容(如查询字段列表、通用 WHERE 条件、表关联语句等),该机制的核心目的是将重复 SQL 片段进行统一提取、封装,实现跨 SQL 语句的复用,避免相同代码多次编写,后续修改只需调整统一的 SQL 片段,无需逐个修改所有相关 SQL,大幅提升配置的可维护性和开发效率

其中 <sql> 负责「定义 / 提取」可复用 SQL 片段,<include> 负责「引用 / 调用」已定义的 SQL 片段,两者相辅相成,构成完整的 SQL 片段复用体系,适用于所有包含重复 SQL 的场景

二、核心

1. <sql>:SQL 片段定义 / 提取标签

  • 核心作用 :专门用于封装、提取可复用的 SQL 片段,将重复出现的合法 SQL 内容(字段、条件、表名等)包裹在该标签内,形成独立的可引用单元

  • 核心特性与配置规则

    1. 必配 id 属性:作为 SQL 片段的唯一标识 ,在当前 Mapper 映射文件中不可重复,供后续 <include> 标签通过 refid 属性精准引用(如 <sql id="User_Column_List">

    2. 支持封装的 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
    3. 可嵌套动态 SQL 标签:片段内支持 <if><choose> 等动态 SQL 标签,实现「动态可复用 SQL」,适配复杂业务场景

    4. 无执行能力:<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。

  • 核心特性与配置规则

    1. 必配 refid 属性:指定要引用的 <sql> 片段的 id(如 <include refid="User_Column_List"/>),若引用其他 Mapper 文件中的 SQL 片段,可添加命名空间前缀(格式:命名空间.sql片段id,如 com.club.dao.UserDao.User_Column_List
    2. 支持动态传递参数:可通过 <include> 内部嵌套 <property> 标签,向 SQL 片段传递动态参数,实现片段的灵活适配(解决固定片段无法适配多场景的问题)
    3. 拼接无冗余:MyBatis 解析时,会将 <include> 替换为对应 <sql> 片段的内容,与主 SQL 无缝拼接,不会产生多余的空格或语法错误
    4. 可多次引用:同一个 <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>

三、 延伸

  1. SQL 片段的复用范围

    • 默认范围:当前 Mapper 映射文件内,直接通过 refid 引用 <sql> 片段的 id 即可;
    • 跨 Mapper 文件复用:需在 refid 中添加目标 Mapper 文件的 namespace 前缀(目标 Mapper 文件的 <mapper> 标签 namespace 属性值),格式为 namespace.sqlFragmentId,实现全局复用。
  2. 使用注意事项(避免语法错误)

    • 片段内不要添加多余的标点符号(如末尾的逗号):若片段是字段列表,末尾加逗号会导致主 SQL 拼接后出现 SELECT id, username, FROM user 的语法错误;
    • 保证片段的合法性:<sql> 封装的片段必须是可拼接的合法 SQL,单独存在可能无意义,但与主 SQL 拼接后需满足 SQL 语法规范;
    • 避免过度封装:仅对重复出现的 SQL 片段进行封装,简单的单次 SQL 无需封装,否则会增加配置复杂度,降低可读性。
  3. 与动态 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>

四、核心价值

  1. 减少代码冗余:避免相同 SQL 片段多次编写,简化 Mapper 映射文件的配置,提升文件可读性
  2. 提升可维护性:后续修改重复 SQL 内容时,只需调整统一的 <sql> 片段,无需逐个修改所有相关 SQL,降低维护成本和出错概率
  3. 增强灵活性:支持跨 Mapper 复用、动态传递参数,适配不同业务场景的需求,提升开发效率

总结

  1. 核心知识点:MyBatis SQL 片段提取与复用机制(<sql> 定义 + <include> 引用)
  2. 本题考点:<sql>(提取可复用 SQL 片段,必配 id)、<include>(引用片段,必配 refid
  3. 核心价值:减少冗余、提升可维护性,是 MyBatis 配置优化的常用技巧
  4. 关键注意:保证 SQL 拼接合法性,避免过度封装,支持与动态 SQL 结合使用
相关推荐
それども2 小时前
SQL NOT EXISTS理解
数据库·sql
brucelee1862 小时前
芋道 Spring Boot 框架 + AWS S3 图片上传显示
java·开发语言·数据库
七夜zippoe2 小时前
Java项目CI/CD实战:Jenkins与GitLab CI深度解析
java·ci/cd·gitlab·jenkins·groovy·pipline
拾贰_C2 小时前
【idea | knife4j | springboot2/3|接上篇】knife4j版本号与spring boot版本不兼容问题
java·spring boot·intellij-idea
蜡台2 小时前
IDEA 安装 Alibaba cloud toolkit 及配置使用
java·ide·intellij-idea
吴声子夜歌2 小时前
小程序——转发API
java·前端·小程序
利来利往2 小时前
skynet call可能引发的bug
java·junit·bug
jioulongzi2 小时前
mybatis映射mysql_json字段, 自定义typehandler返回null
mysql·json·mybatis
JTCC2 小时前
Java 设计模式西游篇 - 第三回:策略模式换法宝 三打白骨精变招
java·设计模式·策略模式