チェックルール(つづき)
絶対分かるMyBatis!MyBatisで覚えるべきチェックルール25(前半) のつづきになります。本来は残りのチェックルール(13~25)について説明するつもりでしたが、予想以上に長くなってしまったので3部構成に変更しました。今回はチェックルール(13~20)まで説明したいと思います。
13. Mapperファイル内で引数のJavaのデータを参照する場合、バインド変数#{...}
を利用すること
MyBatisのデフォルトではJavaのPreparedStatement
を利用してSQLを実行します。実はバインド変数#{...}
はPreparedStatement
のパラメータとして設定されます。つまりパラメータが利用できる箇所でのみ、利用することができます。
- SELECT文のWHERE句における値
- INSERT文のVALUES句における値
- UPDATE文のSET句における値、WHERE句における値
- DELETE文のWHERE句における値
バインド変数#{...}
の変数名は引数のJavaクラスのフィールド名、Mapの場合はキー名になります。
引数がプリミティブ型、ラッパークラス、Stringの場合は引数名にします。
public interface CustomerRepository {
void insert(Customer customer);
Customer findOne(String customerCode);
}
<mapper namespace="com.example.demo.mybatis3.domain.repostiory.CustomerRepostiory">
<!-- 引数のデータ型はCustomer -->
<insert id="insert" parameterType="Customer">
INSERT INTO customer (
customer_code,
customer_name,
customer_pass,
customer_birth,
customer_mail
) VALUES (
#{customerCode},
#{customerName},
#{customerPass},
#{customerBirth},
#{customerMail}
)
</insert>
<!-- 引数のデータ型はString-->
<!-- 戻り値のデータ型はCustomer -->
<select id="findOne" parameterType="string" resultType="Customer">
SELECT
customer_code,
customer_name,
customer_pass,
customer_birth,
customer_mail
FROM
customer
WHERE
customer_code = #{customerCode}
</select>
</mapper>
14. テーブル名やカラム名などPreparedStatement
では設定できない箇所にJavaのデータを設定する等、バインド変数#{...}
では対応できない場合に限り、置換変数${...}
を利用すること
置換変数${...}
はSQL文を作る際に文字列内の変数を置換するものです。
簡単に言うとSQL文の文字列のどこでも利用することができます。
(注意)置換変数${...}
を利用すると不正なSQL文を作ることも可能であり、SQLインジェクションが起こらないように変数の妥当性をチェックする必要があります。
public interface AnythingTableRepository {
Long count(String tableName);
Long deleteAll(String tableName);
}
<mapper namespace="com.example.demo.mybatis3.domain.repostiory.AnythingTableRepository">
<!-- 引数のデータ型はString -->
<!-- 戻り値のデータ型はLong -->
<select id="count" parameterType="string" resultType="long">
SELECT
COUNT(*)
FROM
${tableName}
</select>
<!-- 引数のデータ型はString -->
<!-- 戻り値のデータ型はLong -->
<delete id="deleteAll" parameterType="string" resultType="long">
DELETE FROM
${tableName}
</delete>
</mapper>
15. メソッドの引数が複数(2つ以上)の場合、@Param
アノテーションを引数に付与すること
メソッドの引数が複数(2つ以上)の場合、引数に@Param(org.apache.ibatis.annotations.Param)
アノテーションを付与してMyBatisの変数に名前を付与してください。
@Param
を付与しない場合、第一引数はparam1
、第二引数はparam2
のように「param+1からのインデックス
」という変数になります。
可読性や保守性を考慮し、引数が複数の場合は必ず@Param
アノテーションを付与するようにしてください。
(注意)mybatis-springを利用する場合、誤ってSpring frameworkの@Param(org.springframework.data.repository.query.Param)
アノテーションを付与してしまう方が多いので注意してください。
16. メソッドの引数が一つの場合、@Param
アノテーションを付与しないこと
メソッドの引数が一つの場合、@Param
アノテーションを付与しないでください。
付与した場合は変数名はネストした形で記述する必要があり、Mapperファイルの記述ミスを招く可能性があります。
そもそも@Param
アノテーションを付与しない方がシンプルなので、必要がない限り付与しないようにすべきです。
public interface ItemRepository {
void insert(@Param("item") Item item);
}
<insert id="insert" parameterType="Item">
INSERT INTO item (
item_code,
item_name,
status,
created_at
) VALUES (
<!-- @Param("item") を付与した場合、ネストで記述する必要がある -->
#{item.itemCode},
#{item.itemName},
#{item.status},
#{item.createdAt}
<!-- @Param("item") を付与しない場合
#{itemCode},
#{itemName},
#{status},
#{createdAt}
-->
)
</insert>
実は引数が一つだけの基本データ型(プリミティブ、ラッパークラス、日付型等)の場合、MyBatisの変数名はどんな名前でも参照可能です。ですので、わざわざ@Param
で変数名を指定する必要はありません。
ですが、可読性を考慮し、インターフェースの引数名と同じ変数名を利用してください。
public interface ItemRepository {
Item fineOne(String itemCode);
}
<select id="findOne" parameterType="string" resultType="Item">
SELECT
item_code,
item_name,
created_at
FROM
item
WHERE
item_code = #{itemCode}
<!-- 自動的に付与される変数名でもOK -->
<!-- item_code = #{param1} -->
<!-- 適当な変数名でもOK -->
<!-- item_code = #{hoge} -->
</select>
17. マッピングするクラスには空のコンストラクタが存在すること
マッピングするクラス(resultType属性、resultMapのtype属性で指定するクラス)に空のコンストラクタが存在することを確認してください。
MyBatisは空のコンストラクタを利用してインスタンスを生成します。そのため、空のコンストラクタが存在しない場合、マッピングの処理でエラーとなります。
引数のあるコンストラクタを定義した場合、空のコンストラクタも忘れずに定義してください。
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.executor.ExecutorException: No constructor found in com.example.demo.mybatis3.domain.model.Item matching [java.lang.String, java.lang.String, java.lang.Integer]
(補足)
正しくは「空のコンストラクタ」もしくは「ResultSetの結果のカラムに対応するコンストラクタ」を利用してインスタンスを生成します。
例えばselect item_code, item_name from item
ならitem_code, item_name
の二つの引数、select item_code, item_name, status from item
ならitem_code, item_name, status
の三つの引数をそれぞれ取るコンストラクタがあれば正常に処理されます。
処理結果に応じたコンストラクタを用意するのは効率的ではないので、常に明示的に空のコンストラクタを定義する方が統一しやすいかと思います。
18. SQL文の内容を条件に応じて変更したい場合はMyBatisの動的SQLを利用すること
MyBatisでは引数の内容に応じてSQL文を動的に変更することができます。
この機能を利用すると、例えばWHEREの条件が少しづつ違うSQL文を複数用意する必要がなくなり、1つのSQL文で対応できるようになります。
動的SQLとはMapperファイルで利用できるMyBatisが提供する専用のXMLタグで、以下に示すような処理を行います。詳細についてはMyBatisの公式サイトの動的SQLのページを参照ください。
- 条件や選択肢(if、choose、when、otherwise)
- 繰り返し(foreach)
- 余分な出力の抑止(trim、where、set)
public interface ItemRepository {
List<Item> searchItemByStatus(List<Integer> statusList);
List<Item> searchItemByCriteria(ItemCriteria criteria);
}
<select id="searchItemByCriteria" parameterType="ItemCriteria" resultType="Item">
SELECT
item_code,
item_name,
status,
created_at
FROM
item
WHERE
item_name like #{itemName}
<if test="afterCreated != null">
<![CDATA[
AND created_at > #{afterCreated}
]]>
</if>
</select>
<select id="searchItemByStatus" parameterType="list" resultType="Item">
SELECT
item_code,
item_name,
status,
created_at
FROM
item
WHERE
<foreach item="statusCode" collection="list"
open="status IN (" separator="," close=")">
#{statusCode}
</foreach>
</select>
19. >
、<
はエスケープ処理をすること
SQLを記述していると忘れがちですがMapperファイルはXMLファイルです。そのため>
、<
はエスケープ処理が必要となります。エスケープ処理の方法はXMLファイルを記述する際の方法と同じで、実体参照(>
、<
)か<![CDATA[...]]>
を利用します。
<![CDATA[...]]>
の中では動的SQLが有効になりません。<![CDATA[...]]>
は必要最低限の箇所に利用するようにしてください。
なお、以上以下については「PostgreSQLでは以上、以下(=>、=<)が使えない!?」も参照ください。
<select id="searchItemByCriteria" parameterType="ItemCriteria" resultType="Item">
<![CDATA[
SELECT
item_code,
item_name,
created_at
WHERE
item_name like #{itemName}
<if test="afterCreated != null">
AND created_at > #{afterCreated}
</if>
]]>
</select>
<select id="searchItemByCriteria" parameterType="ItemCriteria" resultType="Item">
SELECT
item_code,
item_name,
created_at
WHERE
item_name like #{itemName}
<if test="afterCreated != null">
<![CDATA[
AND created_at > #{afterCreated}
]]>
</if>
</select>
20. 正しくコメントアウトされているか
コメントアウトには二種類の方法があります。XMLファイルのコメントアウトかSQLのコメントアウトです。
XMLファイルのコメントアウトはMyBatisにコメントの内容が渡されません。
SQLのコメントはMyBatisに渡された後、SQLに含まれた形で実行されます。SQLのコメントアウトはSQLが記述できる場所(<select>、<insert>、<update>、<delete>、<sql>
タグの範囲内)でのみ有効です。
なおSQLのコメントアウトは利用するRDBMSに依存するので注意してください。(PostgreSQLのコメントアウトは--
)
<select id="findOne" parameterType="string" resultType="Item">
SELECT
item_code,
item_name,
created_at
WHERE
item_code = #{itemCode} <!-- これはXMLファイルのコメントアウト -->
</select>
<select id="findOne" parameterType="string" resultType="Item">
SELECT
item_code,
item_name,
created_at
WHERE
item_code = #{itemCode} -- これはSQLのコメントアウト
</select>
次回予告
今回はMyBatisを利用する際に覚えておくべきチェックルール(13~20)について説明しました。次回は残りのチェックルール(21~25)について説明したいと思います。なお、内容は以下を予定しています。
-
- ページネーション
-
- SQLの共通化(
<sql>
と<include>
)
- SQLの共通化(
-
- 採番(キー生成)
-
- N+1問題はJOIN
-
- 複数データを登録する場合、一括登録の適用を検討すること