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 <= #{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> 标签可以:
- 减少重复代码:将常用字段列表、条件、插入/更新片段抽取出来。
- 提高可维护性:修改一处,所有引用处自动生效。
- 增强可读性:将长 SQL 拆分为有意义的命名片段。
- 支持参数化:通过属性传递实现更灵活的复用。
掌握 <sql> 标签的使用,可以让你的 MyBatis 映射文件更加简洁、规范、易于维护。