MyBatis <sql> 标签详解:SQL 片段的定义与复用

MyBatis <sql> 标签详解:SQL 片段的定义与复用

一、概述

<sql> 是 MyBatis 中用于定义可复用的 SQL 片段 的标签。它允许你将一段常用的 SQL 语句(如字段列表、公共查询条件、通用 WHERE 子句等)抽取出来,在多个地方通过 <include> 标签引用,从而避免重复编写相同的 SQL 代码。

核心价值:提高 SQL 的可维护性,减少重复代码,遵循 DRY(Don't Repeat Yourself)原则。

二、基本语法

2.1 定义 SQL 片段

xml 复制代码
<sql id="唯一标识符">
    SQL 片段内容
</sql>

2.2 引用 SQL 片段

xml 复制代码
<include refid="sql片段的id"/>

三、使用示例

3.1 基础用法:复用字段列表

定义

xml 复制代码
<sql id="Base_Column_List">
    id, car_num, brand, guide_price, produce_time, car_type
</sql>

引用

xml 复制代码
<select id="selectById" resultType="car">
    SELECT <include refid="Base_Column_List"/>
    FROM t_car
    WHERE id = #{id}
</select>

<select id="selectAll" resultType="car">
    SELECT <include refid="Base_Column_List"/>
    FROM t_car
</select>

生成的 SQL

sql 复制代码
SELECT id, car_num, brand, guide_price, produce_time, car_type FROM t_car WHERE id = ?

3.2 复用公共查询条件

定义

xml 复制代码
<sql id="Common_Where_Condition">
    <where>
        <if test="brand != null and brand != ''">
            AND brand LIKE CONCAT('%', #{brand}, '%')
        </if>
        <if test="carType != null and carType != ''">
            AND car_type = #{carType}
        </if>
    </where>
</sql>

引用

xml 复制代码
<select id="selectByCondition" resultType="car">
    SELECT <include refid="Base_Column_List"/>
    FROM t_car
    <include refid="Common_Where_Condition"/>
</select>

<select id="selectWithOrder" resultType="car">
    SELECT <include refid="Base_Column_List"/>
    FROM t_car
    <include refid="Common_Where_Condition"/>
    ORDER BY id DESC
</select>

3.3 复用 INSERT 字段列表

定义

xml 复制代码
<sql id="Insert_Column_List">
    car_num, brand, guide_price, produce_time, car_type
</sql>

<sql id="Insert_Value_List">
    #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType}
</sql>

引用

xml 复制代码
<insert id="insertCar" parameterType="car">
    INSERT INTO t_car (<include refid="Insert_Column_List"/>)
    VALUES (<include refid="Insert_Value_List"/>)
</insert>

3.4 复用 UPDATE SET 子句

定义

xml 复制代码
<sql id="Update_Set_Clause">
    <set>
        <if test="carNum != null and carNum != ''">
            car_num = #{carNum},
        </if>
        <if test="brand != null and brand != ''">
            brand = #{brand},
        </if>
        <if test="guidePrice != null">
            guide_price = #{guidePrice},
        </if>
        <if test="produceTime != null and produceTime != ''">
            produce_time = #{produceTime},
        </if>
        <if test="carType != null and carType != ''">
            car_type = #{carType},
        </if>
    </set>
</sql>

引用

xml 复制代码
<update id="updateCar" parameterType="car">
    UPDATE t_car
    <include refid="Update_Set_Clause"/>
    WHERE id = #{id}
</update>

四、属性传递(参数化)

<sql> 标签支持通过 <include> 传递属性值,实现更灵活的复用。

4.1 基础属性传递

定义

xml 复制代码
<sql id="Select_By_Column">
    SELECT * FROM ${tableName}
    WHERE ${column} = #{value}
</sql>

引用

xml 复制代码
<select id="selectByBrand" resultType="car">
    <include refid="Select_By_Column">
        <property name="tableName" value="t_car"/>
        <property name="column" value="brand"/>
        <property name="value" value="宝马"/>
    </include>
</select>

生成的 SQL

sql 复制代码
SELECT * FROM t_car WHERE brand = ?

4.2 动态属性传递

定义

xml 复制代码
<sql id="Dynamic_Where">
    <where>
        <if test="${column} != null and ${column} != ''">
            AND ${column} LIKE CONCAT('%', #{keyword}, '%')
        </if>
    </where>
</sql>

引用

xml 复制代码
<select id="searchCars" resultType="car">
    SELECT * FROM t_car
    <include refid="Dynamic_Where">
        <property name="column" value="brand"/>
    </include>
</select>

4.3 多属性传递

xml 复制代码
<sql id="Pagination">
    SELECT * FROM ${tableName}
    WHERE ${condition}
    LIMIT #{offset}, #{size}
</sql>

<select id="getCarsByPage" resultType="car">
    <include refid="Pagination">
        <property name="tableName" value="t_car"/>
        <property name="condition" value="car_type = '燃油车'"/>
    </include>
</select>

五、与动态 SQL 标签的配合

<sql> 内部可以包含任意动态 SQL 标签(<if><where><foreach> 等)。

5.1 包含 <if> 的 SQL 片段

xml 复制代码
<sql id="Dynamic_Columns">
    <if test="includeId != null and includeId == true">
        id,
    </if>
    car_num, brand, guide_price, produce_time, car_type
</sql>
xml 复制代码
<select id="selectWithOption" resultType="car">
    SELECT <include refid="Dynamic_Columns"/>
    FROM t_car
</select>

5.2 包含 <foreach> 的 SQL 片段

xml 复制代码
<sql id="In_Condition">
    <if test="ids != null and ids.size() > 0">
        AND id IN
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </if>
</sql>
xml 复制代码
<select id="selectByIds" resultType="car">
    SELECT <include refid="Base_Column_List"/>
    FROM t_car
    <include refid="In_Condition"/>
</select>

六、多个 SQL 片段的组合复用

可以将多个小片段组合成更大的片段。

xml 复制代码
<!-- 基础字段 -->
<sql id="Base_Fields">
    id, car_num, brand
</sql>

<!-- 扩展字段 -->
<sql id="Extra_Fields">
    guide_price, produce_time, car_type
</sql>

<!-- 组合字段 -->
<sql id="All_Fields">
    <include refid="Base_Fields"/>,
    <include refid="Extra_Fields"/>
</sql>
xml 复制代码
<select id="selectAll" resultType="car">
    SELECT <include refid="All_Fields"/>
    FROM t_car
</select>

七、最佳实践与注意事项

7.1 命名规范建议

命名前缀 用途 示例
Base_ 基础字段列表 Base_Column_List
Insert_ 插入相关片段 Insert_Column_List
Update_ 更新相关片段 Update_Set_Clause
Where_ 条件片段 Where_Condition
Join_ 关联查询片段 Join_Clause

7.2 注意事项

注意事项 说明
全局唯一 id sql 标签的 id 在同一 XML 中必须唯一,不同 XML 可以重名
跨文件引用 引用其他文件的 sql 片段时,需要加 namespace 前缀:refid="namespace.sqlId"
${}#{} 片段中的 #{value} 引用的是传入的参数;${} 引用的是传入的属性
属性覆盖 同名属性会被 <include> 中的 <property> 覆盖
不能引用自身 避免循环引用导致栈溢出

7.3 跨文件引用示例

UserMapper.xml

xml 复制代码
<sql id="Base_Column_List">
    id, username, email
</sql>

CarMapper.xml

xml 复制代码
<sql id="Car_With_User">
    SELECT c.*, u.username
    FROM t_car c
    LEFT JOIN t_user u ON c.user_id = u.id
    WHERE c.id = #{id}
</sql>

<select id="selectCarWithUser" resultType="Car">
    <include refid="Car_With_User"/>
</select>

或者直接引用其他文件的片段:

xml 复制代码
<select id="getUserInfo" resultType="User">
    SELECT <include refid="com.xie.mapper.UserMapper.Base_Column_List"/>
    FROM t_user
    WHERE id = #{id}
</select>

八、完整实战示例

8.1 Mapper XML

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xie.mapper.CarMapper">

    <!-- ============ SQL 片段定义 ============ -->
    <!-- 基础字段列表 -->
    <sql id="Base_Column_List">
        id, car_num, brand, guide_price, produce_time, car_type
    </sql>

    <!-- 插入字段列表 -->
    <sql id="Insert_Column_List">
        car_num, brand, guide_price, produce_time, car_type
    </sql>

    <!-- 插入值列表 -->
    <sql id="Insert_Value_List">
        #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType}
    </sql>

    <!-- 动态更新 SET 子句 -->
    <sql id="Update_Set_Clause">
        <set>
            <if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
            <if test="brand != null and brand != ''">brand = #{brand},</if>
            <if test="guidePrice != null">guide_price = #{guidePrice},</if>
            <if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
            <if test="carType != null and carType != ''">car_type = #{carType},</if>
        </set>
    </sql>

    <!-- 公共 WHERE 条件 -->
    <sql id="Common_Where">
        <where>
            <if test="brand != null and brand != ''">
                AND brand LIKE CONCAT('%', #{brand}, '%')
            </if>
            <if test="carType != null and carType != ''">
                AND car_type = #{carType}
            </if>
            <if test="minPrice != null">
                AND guide_price >= #{minPrice}
            </if>
            <if test="maxPrice != null">
                AND guide_price &lt;= #{maxPrice}
            </if>
        </where>
    </sql>

    <!-- ============ 查询方法 ============ -->
    <!-- 根据 ID 查询 -->
    <select id="selectById" resultType="car">
        SELECT <include refid="Base_Column_List"/>
        FROM t_car
        WHERE id = #{id}
    </select>

    <!-- 查询所有 -->
    <select id="selectAll" resultType="car">
        SELECT <include refid="Base_Column_List"/>
        FROM t_car
    </select>

    <!-- 动态条件查询 -->
    <select id="selectByCondition" resultType="car">
        SELECT <include refid="Base_Column_List"/>
        FROM t_car
        <include refid="Common_Where"/>
    </select>

    <!-- ============ 插入方法 ============ -->
    <insert id="insertCar" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_car (<include refid="Insert_Column_List"/>)
        VALUES (<include refid="Insert_Value_List"/>)
    </insert>

    <!-- ============ 更新方法 ============ -->
    <update id="updateCar">
        UPDATE t_car
        <include refid="Update_Set_Clause"/>
        WHERE id = #{id}
    </update>

</mapper>

8.2 生成的 SQL 示例

查询方法

sql 复制代码
-- selectAll
SELECT id, car_num, brand, guide_price, produce_time, car_type FROM t_car

-- selectByCondition (brand = '宝马')
SELECT id, car_num, brand, guide_price, produce_time, car_type
FROM t_car
WHERE brand LIKE CONCAT('%', '宝马', '%')

插入方法

sql 复制代码
INSERT INTO t_car (car_num, brand, guide_price, produce_time, car_type)
VALUES (?, ?, ?, ?, ?)

更新方法

sql 复制代码
UPDATE t_car SET brand = ?, guide_price = ? WHERE id = ?

九、总结速查表

知识点 要点
定义 <sql id="唯一id">SQL片段</sql>
引用 <include refid="sql片段id"/>
属性传递 <property name="属性名" value="属性值"/>
跨文件引用 refid="namespace.sqlId"
适用范围 字段列表、查询条件、INSERT/UPDATE 片段、动态 SQL
主要优点 提高复用性、减少重复代码、易于维护
注意事项 避免循环引用;属性值用 ${} 获取;#{} 获取参数值

结语

<sql> 标签是 MyBatis 中实现 SQL 复用的核心工具。合理使用 <sql> 标签可以:

  1. 减少重复代码:将常用字段列表、条件、插入/更新片段抽取出来。
  2. 提高可维护性:修改一处,所有引用处自动生效。
  3. 增强可读性:将长 SQL 拆分为有意义的命名片段。
  4. 支持参数化:通过属性传递实现更灵活的复用。

掌握 <sql> 标签的使用,可以让你的 MyBatis 映射文件更加简洁、规范、易于维护。

相关推荐
想要成为糕糕手1 小时前
🏭 设计模式之工厂模式:从蜜雪冰城到 NestJS,把「new」外包出去
后端·nestjs
抓哇小菜鸡1 小时前
Spring Boot + 本地大模型(Ollama/DeepSeek) + MyBatis-Plus 企业级智能体数据分析系统从零到一源码全解析
spring boot·后端·mybatis
星火10241 小时前
【LangChain4j系列07】结构化输出与类型安全
人工智能·后端
用户298698530141 小时前
3 种方法,轻松将 PowerPoint 转换为 PDF 格式
人工智能·后端·c#
吃饱了得干活1 小时前
为什么你的Service越写越臃肿?三层架构的“业务逻辑层”是个黑盒
java·后端·架构
何时梦醒1 小时前
Docker 容器化入门:从「我电脑能跑」到「哪台机器都能跑」
后端·docker·面试
颜进强1 小时前
11 - 从需求拆解到 OpenSpec:为什么不要直接敲 /opsx:explore
前端·后端·ai编程
foggyprojects2 小时前
AI 说销售额下降了,哪些客户拖累了结果?
后端
用户852495071842 小时前
NestJS 架构实战:给后端代码请来一位“项目经理
后端