MyBatisにおける動的SQLの実装パターンと活用法

動的SQLの概要

MyBatisの動的SQL機能は、実行時の条件に基づいてSQL文を柔軟に組み立てるための仕組みです。Javaのコード内で文字列連結を行ってSQLを構築する手法に比べ、可読性が高く、保守性に優れた実装が可能となります。

条件分岐によるSQL構築(ifタグ)

<if>タグを使用すると、test属性に指定したOGNL式の評価結果がtrueの場合のみ、タグ内のSQL文が最終的なクエリに追加されます。これにより、検索条件が任意である場合のSQLを簡潔に記述できます。

WHERE句の問題と対処法

動的SQLを扱う際、全ての条件がfalseとなった場合にWHERE句が余分になる、あるいは条件の先頭にANDORが残ってしまうといった課題が発生します。

恒等式(1=1)を利用した回避

古典的な解決策として、WHERE句に常に真となる恒等式を記述する方法があります。

<select id="findActiveProductsWithPrice" resultType="Product">
    SELECT * FROM products
    WHERE 1=1
    <if test="price != null">
        AND price > #{price}
    </if>
    <if test="name != null and name != ''">
        AND product_name LIKE #{name}
    </if>
</select>

WHEREタグによる自動化

よりスマートな解決策として<where>タグが用意されています。このタグは、内部にコンテンツが含まれる場合のみWHEREキーワードを付与します。また、内容の先頭にあるANDまたはORを自動的に削除します。ただし、末尾のキーワードは削除されません。

<select id="findActiveProductsWithPrice" resultType="Product">
    SELECT * FROM products
    <where>
        <if test="price != null">
            price > #{price}
        </if>
        <if test="name != null and name != ''">
            AND product_name LIKE #{name}
        </if>
        <if test="stock != null">
            AND stock <= #{stock}
        </if>
    </where>
</select>

TRIMタグによる詳細なカスタマイズ

<trim>タグを使用すると、SQLフラグメントの前後に任意の文字を追加したり、特定の文字を削除したりする細かい制御が可能です。

  • prefix: 内容の前に追加する文字列(例: "WHERE")
  • suffix: 内容の後に追加する文字列
  • prefixOverrides: 内容の前にある特定の文字列を削除(例: "AND"|"OR")
  • suffixOverrides: 内容の後ろにある特定の文字列を削除

以下の例では、条件の最後に余分なANDが付与されることを想定し、suffixOverridesで削除するように設定しています。

<select id="findActiveProductsWithPrice" resultType="Product">
    SELECT * FROM products
    <trim prefix="WHERE" suffixOverrides="AND">
        <if test="id != null">
            product_id = #{id} AND
        </if>
        <if test="name != null">
            product_name = #{name} AND
        </if>
    </trim>
</select>

選択的分岐(choose, when, otherwise)

Javaのswitch文のように、複数の条件の中で最初に一致した1つのみを採用したい場合に使用します。<choose>タグの中に、1つ以上の<when>と、オプションとして1つの<otherwise>を配置します。

<select id="findProductByCondition" resultType="Product">
    SELECT * FROM products WHERE 1=1
    <choose>
        <when test="id != null">
            AND product_id = #{id}
        </when>
        <when test="name != null">
            AND product_name = #{name}
        </when>
        <otherwise>
            AND status = 'ACTIVE'
        </otherwise>
    </choose>
</select>

繰り返し処理(foreachタグ)

配列やリストなどのコレクションに対して繰り返し処理を行い、SQLを組み立てる際に使用します。IN句での複数条件指定や、一括登録(バッチインサート)において非常に有用です。

  • collection: ループ対象のコレクション(MapのキーやList@param名など)
  • item: 各要素を参照するための変数名
  • separator: 各繰り返しの間に挿入される区切り文字
  • open: ループ開始時に挿入される文字列
  • close: ループ終了時に挿入される文字列

配列を用いた一括削除

<delete id="deleteProductsByIds">
    DELETE FROM products
    WHERE product_id IN
    <foreach collection="idArray" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

リストを用いた一括登録

<insert id="bulkInsertProducts">
    INSERT INTO products (product_name, price, stock)
    VALUES
    <foreach collection="productList" item="prod" separator=",">
        (#{prod.name}, #{prod.price}, #{prod.stock})
    </foreach>
</insert>

SQLフラグメントの再利用(sql, includeタグ)

共通のカラムリストや条件式など、複数のSQLで使い回したい部分がある場合、<sql>タグで定義し、<include>タグで参照することでコードの重複を防ぐことができます(DRY原則)。

  • <sql id="...">: 再利用可能なフラグメントを定義します。
  • <include refid="...">: 定義されたフラグメントを参照します。
<!-- 共通カラム定義 -->
<sql id="productColumns">
    product_id, product_name, price, stock
</sql>

<!-- 参照元 -->
<select id="selectAllProducts" resultType="Product">
    SELECT
    <include refid="productColumns" />
    FROM products
</select>

タグ: MyBatis DynamicSQL XML Java ORM

5月31日 22:38 投稿