MyBatis 根據參數對象屬性值,動態拼接 SQL 片段。核心上下文對象:_parameter (傳入參數)、_databaseId (數據庫標識)。
| 標籤 | 作用 | 關鍵屬性 | 典型場景 |
|---|---|---|---|
| <if> | 條件包含 | test="OGNL 表達式" | 可選查詢條件 |
| <choose> <when> <otherwise> | 互斥分支 (類似 switch-case) | test | 單一條件分支、默認兜底 |
| <where> | 智能 WHERE 子句 | 無 | 自動處理 AND/OR 前綴、無條件時不生成 WHERE |
| <trim> | 通用前後綴處理 | prefix, suffix, prefixOverrides, suffixOverrides | 自定義 WHERE/SET/VALUES 等複雜拼接 |
| <set> | 智能 SET 子句 | 無 | UPDATE 動態列,自動去除尾部逗號 |
| <foreach> | 集合遍歷 | collection, item, index, open, close, separator | IN 列表、批量 INSERT、多條件 OR |
| <bind> | 定義上下文變量 | name, value="OGNL 表達式" | 複雜表達式復用、性能優化 |
<select id="queryOrders" resultType="OrderVO">
SELECT o.id, o.order_no, o.user_id, o.total_amount, o.status, o.create_time
FROM t_order o
<where>
<if test="orderNo != null and orderNo != ''">
AND o.order_no LIKE CONCAT('%', #{orderNo}, '%')
</if>
<if test="userId != null">
AND o.user_id = #{userId}
</if>
<if test="statusList != null and statusList.size() > 0">
AND o.status IN
<foreach collection="statusList" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
</if>
<if test="startTime != null">
AND o.create_time >= #{startTime}
</if>
<if test="endTime != null">
AND o.create_time <= #{endTime}
</if>
</where>
ORDER BY o.create_time DESC
<if test="limit != null"> LIMIT #{limit} </if>
</select>
<where> 魔法:若所有 <if> 均不滿足,不生成 WHERE 關鍵字;若生成,自動去除第一個 AND/OR。
<select id="queryOrders" resultType="OrderVO">
SELECT * FROM t_order
<where>
<!-- 同前 -->
</where>
<choose>
<when test="sortField == 'amount'">
ORDER BY total_amount <if test="sortOrder == 'desc'">DESC<else>ASC</if>
</when>
<when test="sortField == 'time'">
ORDER BY create_time <if test="sortOrder == 'desc'">DESC<else>ASC</if>
</when>
<otherwise>
ORDER BY create_time DESC
</otherwise>
</choose>
</select>
<update id="updateOrder">
UPDATE t_order
<set>
<if test="status != null"> status = #{status}, </if>
<if test="payTime != null"> pay_time = #{payTime}, </if>
<if test="refundAmount != null"> refund_amount = #{refundAmount}, </if>
<if test="remark != null"> remark = #{remark} </if>
</set>
WHERE id = #{id}
</update>
<set> 魔法:自動去除最後一個逗號,若所有 <if> 不滿足則不生成 SET 子句 (SQL 語法錯誤),需業務保證至少一字段更新。
<insert id="batchInsertOrderItems">
INSERT INTO t_order_item (order_id, sku_id, quantity, price, create_time)
VALUES
<foreach collection="items" item="item" separator=",">
(#{item.orderId}, #{item.skuId}, #{item.quantity}, #{item.price}, NOW())
</foreach>
</insert>
<select id="queryBySkuIds" resultType="OrderItemVO">
<bind name="skuIdList" value="@java.util.Arrays@asList(skuIds.split(','))"/>
SELECT * FROM t_order_item
WHERE sku_id IN
<foreach collection="skuIdList" item="skuId" open="(" close=")" separator=",">
#{skuId}
</foreach>
</select>
<bind> 將字符串分割為 List,避免在 Java 層預處理。
<!-- 等價 <where> -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="a != null"> AND a = #{a} </if>
<if test="b != null"> OR b = #{b} </if>
</trim>
<!-- 等價 <set> -->
<trim prefix="SET" suffixOverrides=",">
<if test="a != null"> a = #{a}, </if>
<if test="b != null"> b = #{b}, </if>
</trim>
<!-- 自定義: 動態 VALUES 列 -->
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="a != null"> a, </if>
<if test="b != null"> b, </if>
</trim>
| 表達式 | 含義 | 示例 |
|---|---|---|
property != null | 非空判斷 | test="name != null" |
property != null and property != '' | 字符串非空非空串 | test="name != null and name != ''" |
collection != null and collection.size() > 0 | 集合非空 | test="ids != null and ids.size() > 0" |
map != null and map.containsKey('key') | Map 包含鍵 | test="params != null and params.containsKey('type')" |
obj.property | 嵌套屬性訪問 | test="user.age > 18" |
@Class@staticMethod() | 調用靜態方法 | @bind name="list" value="@Arrays@asList(ids.split(','))" |
_parameter | 根參數對象 | test="_parameter != null" |
_databaseId | 數據庫標識 (需配置 databaseIdProvider) | test="_databaseId == 'mysql'" |
#{property} 皆為預編譯參數,**防止 SQL 注入**,絕不使用 ${property} 拼接業務值。<bind> 計算一次,多處復用。SqlProvider 類編寫 Java 代碼構建 SQL。<foreach> 批量 INSERT/IN 建議 < 500 條/500 個元素,超過分批執行,防止 SQL 過長、解析耗時、參數佔用過多內存。<if>/<when> 分支至少一條單元測試,確保 SQL 語法正確。public class OrderSqlProvider {
public String queryOrders(Map params) {
SQL sql = new SQL() {{
SELECT("o.id, o.order_no, o.user_id, o.total_amount, o.status, o.create_time");
FROM("t_order o");
if (params.get("orderNo") != null) {
WHERE("o.order_no LIKE CONCAT('%', #{orderNo}, '%')");
}
if (params.get("userId") != null) {
WHERE("o.user_id = #{userId}");
}
// 複雜邏輯可任意 Java 代碼
List statusList = (List) params.get("statusList");
if (statusList != null && !statusList.isEmpty()) {
WHERE("o.status IN (" + String.join(",", Collections.nCopies(statusList.size(), "?")) + ")");
}
}};
return sql.toString();
}
}
// Mapper 接口
@SelectProvider(type = OrderSqlProvider.class, method = "queryOrders")
List queryOrders(@Param("params") Map params);
MyBatis 動態 SQL 標籤 (if/choose/where/trim/set/foreach/bind) 構建了強大的 **聲明式 SQL 模板能力**。掌握 <where> 智能去首 AND、<set> 智能去尾逗號、<foreach> 批量拼接、<bind> 變量復用、OGNL 表達式進階用法、SqlProvider Java 化複雜邏輯**,即可應對 99% 動態查詢構建場景。鐵律:**業務值只用 #{}, 結構拼接慎用 ${}, 複雜邏輯移至 Java Provider**。