動的SQLの概要
MyBatisの動的SQL機能は、実行時の条件に基づいてSQL文を柔軟に組み立てるための仕組みです。Javaのコード内で文字列連結を行ってSQLを構築する手法に比べ、可読性が高く、保守性に優れた実装が可能となります。
条件分岐によるSQL構築(ifタグ)
<if>タグを使用すると、test属性に指定したOGNL式の評価結果がtrueの場合のみ、タグ内のSQL文が最終的なクエリに追加されます。これにより、検索条件が任意である場合のSQLを簡潔に記述できます。
WHERE句の問題と対処法
動的SQLを扱う際、全ての条件がfalseとなった場合にWHERE句が余分になる、あるいは条件の先頭にANDやORが残ってしまうといった課題が発生します。
恒等式(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>