首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 動態 SQL 核心標籤實戰:if、choose、trim、where、set、foreach、bind 完全指南

     編輯:Java 相關知識

動態 SQL 本質:OGNL 表達式驅動的 XML 模板引擎

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, separatorIN 列表、批量 INSERT、多條件 OR
<bind>定義上下文變量name, value="OGNL 表達式"複雜表達式復用、性能優化

實戰範例:電商訂單多維查詢

1. 基礎條件查詢 (<where> + <if>)

<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。

2. 互斥排序分支 (<choose>)

<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>

3. 動態 UPDATE (<set>)

<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 語法錯誤),需業務保證至少一字段更新。

4. 批量插入 (<foreach>)

<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>

5. 復雜 IN 條件 (<foreach> + <bind>)

<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 層預處理。

6. 通用 <trim> 替代 <where>/<set> (全控制)

<!-- 等價 <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>

OGNL 表達式進階用法

表達式含義示例
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'"

性能與安全最佳實踐

  1. 預編譯參數化:所有 #{property} 皆為預編譯參數,**防止 SQL 注入**,絕不使用 ${property} 拼接業務值。
  2. <bind> 緩存昂貴計算:複雜表達式 (如正則、日期計算) 用 <bind> 計算一次,多處復用。
  3. 避免巨大的 <choose>/<if> 嵌套:超過 3 層建議拆分為多個 Mapper 方法或用 SqlProvider 類編寫 Java 代碼構建 SQL。
  4. 批量操作控制規模:<foreach> 批量 INSERT/IN 建議 < 500 條/500 個元素,超過分批執行,防止 SQL 過長、解析耗時、參數佔用過多內存。
  5. 測試覆蓋分支:每個 <if>/<when> 分支至少一條單元測試,確保 SQL 語法正確。

SqlProvider 類編寫複雜動態 SQL (Java 代碼替代 XML)

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**。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved