バッチ更新

DAO メソッドに @BatchUpdate を付けて、バッチ更新操作を実行します。

@Dao
public interface EmployeeDao {
    @BatchUpdate
    int[] update(List<Employee> employees);

    @BatchUpdate
    BatchResult<ImmutableEmployee> update(List<ImmutableEmployee> employees);
}

デフォルトでは、UPDATE ステートメントが自動生成されます。 @BatchUpdate アノテーション内の sqlFile プロパティに true を指定することで、任意の SQL ファイルをマッピングできます。

エンティティクラスにエンティティリスナーが指定されている場合、preUpdate メソッドは更新操作を実行する前にそれぞれのエンティティに対して呼び出されます。同様に、更新操作が完了した後に、それぞれのエンティティに対して postUpdate メソッドが呼び出されます。

戻り値

パラメータの要素がイミュータブルなエンティティクラスの場合、戻り値は org.seasar.doma.jdbc.BatchResult で、要素タイプとしてそのエンティティクラスが含まれていなければなりません。

上記の条件が満たされない場合、戻り値は int[] でなければならず、各要素は各更新操作によって影響を受けた行数を表します。

自動生成SQLによるバッチ更新

パラメータの型は、エンティティクラスを要素とする java.lang.Iterable のサブタイプでなければなりません。指定できるパラメータは1つだけで、パラメータは null であってはなりません。返値の配列の要素数は Iterable の要素数と等しく、それぞれの配列要素は該当する更新操作によって影響を受ける行の数を表します。

自動生成された SQL におけるバージョン番号と楽観的排他制御

以下の条件を満たした場合、楽観的排他制御が実行されます。

  • パラメータ java.lang.Iterable サブタイプ内の エンティティ に @Version アノテーションが付けられたプロパティがある

  • @BatchUpdate アノテーション内の ignoreVersion 要素が false である

楽観的排他制御が有効になっている場合、バージョン番号が識別子とともに更新条件に含まれ、1ずつ増加されます。更新数が0の場合、BatchOptimisticLockException がスローされ、楽観的排他制御の失敗を示します。更新数が1の場合、エンティティ内のバージョンプロパティは1増加され、例外はスローされません。

ignoreVersion

@BatchUpdate アノテーションの ignoreVersion プロパティが true に設定されている場合、バージョン番号は更新条件に含まれず、UPDATE 文の SET 句に含まれます。バージョン番号はアプリケーションで設定した値で更新されます。この場合、更新件数が 0 であっても BatchOptimisticLockException はスローされません。

@BatchUpdate(ignoreVersion = true)
int[] update(List<Employee> employees);

suppressOptimisticLockException

@BatchUpdate アノテーションの suppressOptimisticLockException プロパティが true の場合、@Version アノテーションが付けられたプロパティが存在する場合は、バージョン番号が更新条件に含まれ、1ずつ増分されますが、更新件数が0でも BatchOptimisticLockException はスローされません。しかし、エンティティ内のバージョン プロパティ値は1ずつ増分されます。

@BatchUpdate(suppressOptimisticLockException = true)
int[] update(List<Employee> employees);

更新対象プロパティ

更新可能

エンティティ@Column アノテーションが付けられたプロパティがある場合、 @Column アノテーション内の false が指定された updatable プロパティは更新対象から除外されます。 。

exclude

@BatchUpdate アノテーションの exclude プロパティに指定されたプロパティは、更新操作から除外されます。@Columnupdatable プロパティが true に設定されていても、exclude プロパティに記載されたプロパティは更新から除外されます。

@BatchUpdate(exclude = {"name", "salary"})
int[] update(List<Employee> employees);

include

@BatchUpdateinclude プロパティに指定されたプロパティのみが更新操作に含まれます。 @BatchUpdateinclude プロパティと exclude プロパティの両方にプロパティが指定された場合、そのプロパティは更新操作から除外されます。 たとえ include プロパティに指定されていても、 @Columnupdatable プロパティが false に設定されている場合、そのプロパティは更新操作から除外されます。

@BatchUpdate(include = {"name", "salary"})
int[] update(List<Employee> employees);

SQLファイルによるバッチ更新

SQLファイルによるバッチ更新を行うには、 @BatchUpdatesqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。

注釈

SQLファイルによるバッチ更新では、更新カラムリスト生成ディレクティブ を使用する場合と使用しない場合でルールが異なります。

更新カラムリスト生成ディレクティブを使用する場合

@BatchUpdate(sqlFile = true)
int[] update(List<Employee> employees);

@BatchUpdate
BatchResult<ImmutableEmployee> update(List<ImmutableEmployee> employees);

パラメータの型は要素として エンティティ を持つ java.lang.Iterable のサブタイプでなければなりません。指定できるパラメータは 1 つだけです。パラメータは null であってはなりません。戻り値の配列要素数は、 Iterable の要素数と等しくなります。配列のそれぞれの要素が更新された件数を表します。

例えば、上記のメソッドに対応するには以下のようなSQLを記述します。

update employee set /*%populate*/ id = id where name = /* employees.name */'hoge'

パラメータ名は、SQL ファイル内の Iterable の要素を示します。

更新対象プロパティの制御に関するルールは 自動生成SQLによるバッチ更新 と同等です。

更新カラムリスト生成ディレクティブを使用しない場合

@BatchUpdate(sqlFile = true)
int[] update(List<Employee> employees);

@BatchUpdate
BatchResult<ImmutableEmployee> update(List<ImmutableEmployee> employees);

パラメータの型は、任意の型を要素として持つ java.lang.Iterable でなければなりません。指定できるパラメータは 1 つだけです。パラメータは null であってはなりません。戻り値の配列要素数は、 Iterable の要素数と等しくなります。配列のそれぞれの要素が更新された件数を表します。

例えば、上記のメソッドに対応するには以下のようなSQLを記述します。

update employee set name = /* employees.name */'hoge', salary = /* employees.salary */100
where id = /* employees.id */0

パラメータ名は、SQL ファイル内の Iterable の要素を示します。

SQLファイルによるバッチ更新では、バージョン番号の自動更新は行われません。 また、 @BatchUpdateexclude 要素、 include 要素は参照されません。

SQLファイルにおけるバージョン番号と楽観的排他制御

以下の条件を満たした場合、楽観的排他制御が実行されます。

  • パラメータ内の java.lang.Iterable の要素は エンティティ であり、 @Version アノテーションが付けられたプロパティが エンティティ に存在する

  • @BatchUpdate アノテーション内のignoreVersion プロパティが false である

ただし、楽観的排他制御のSQLの記述はアプリケーション開発者の責任となります。たとえば、以下の SQL のように、WHERE 句でバージョン番号を指定し、SET 句でバージョン番号を 1 ずつインクリメントする必要があります。

update EMPLOYEE set DELETE_FLAG = 1, VERSION = /* employees.version */1 + 1
where ID = /* employees.id */1 and VERSION = /* employees.version */1

このSQLの更新件数が0件または複数件の場合、楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。 更新件数が1件の場合、 BatchOptimisticLockException はスローされず、 エンティティのバージョンプロパティの値が1増分されます。

楽観的排他制御が有効であれば、バージョン番号は識別子とともに更新条件に含まれ、 1増分して更新されます。 このときの更新件数が0件または複数件の場合、楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。 一方、更新件数が1件の場合、 BatchOptimisticLockException はスローされず、エンティティのバージョンプロパティの値が1増分されます。

ignoreVersion

@BatchUpdate アノテーション内の ignoreVersion プロパティが true の場合、更新件数が何件であっても BatchOptimisticLockException はスローされません。また、エンティティのバージョン プロパティは変更されません。

@BatchUpdate(sqlFile = true, ignoreVersion = true)
int[] update(List<Employee> employees);
suppressOptimisticLockException

@BatchUpdatesuppressOptimisticLockException 要素が true の場合、更新件数が 0 であっても BatchOptimisticLockException はスローされません。 ただし、エンティティのバージョンプロパティの値は 1 増分されます。

@BatchUpdate(sqlFile = true, suppressOptimisticLockException = true)
int[] update(List<Employee> employees);

一意制約違反

一意制約違反が発生した場合は、SQLファイルの使用の有無に関係なく UniqueConstraintException がスローされます。

クエリタイムアウト

@BatchUpdate アノテーション内の queryTimeout プロパティにクエリタイムアウトの秒数を指定できます。

@BatchUpdate(queryTimeout = 10)
int[] update(List<Employee> employees);

この指定はSQLファイルの使用の有無に関わらず適用されます。 queryTimeout プロパティに値が設定されていない場合は、config クラスで指定されたクエリタイムアウトが使用されます。

バッチサイズ

バッチサイズは @BatchUpdate アノテーション内の batchSize プロパティに指定できます。

@BatchUpdate(batchSize = 10)
int[] update(List<Employee> employees);

この指定はSQLファイルの使用有無に関わらず適用されます。 batchSize プロパティに値を指定しない場合は、設定 クラスで指定されたバッチサイズが適用されます。

非常に大きなバッチでメモリ使用量を抑える

デフォルトでは、バッチに含まれるすべての PreparedSql がデータベースへの送信前にあらかじめ構築されます。数十万件規模の非常に大きなエンティティリストでは、batchSize を設定していてもヒープを使い切る可能性があります。batchSizeexecuteBatch() 1 回あたりに JDBC ドライバへ送る件数を決めるだけで、メモリ上に同時に存在する PreparedSql の数を制御するものではないからです。

ピーク時のメモリ使用量を抑えるには、ConfigQuery implementors をオーバーライドして ChunkedAutoBatchUpdateQuery をオプトインで利用できます:

public class MyConfig implements Config {
    private final QueryImplementors queryImplementors = new QueryImplementors() {
        @Override
        public <ENTITY> AutoBatchUpdateQuery<ENTITY> createAutoBatchUpdateQuery(
                Method method, EntityType<ENTITY> entityType) {
            return new ChunkedAutoBatchUpdateQuery<>(entityType);
        }
    };

    @Override
    public QueryImplementors getQueryImplementors() {
        return queryImplementors;
    }
    // ... other Config methods ...
}

このオーバーライドを適用すると、自動生成 SQL を使うすべての @BatchUpdate Dao メソッドは、エンティティ 1 件分の準備済み SQL を順次構築し、バインド・JDBC バッチへの追加を行ったのち、次の SQL を構築する前に GC 対象になります。JDBC バッチのフラッシュ境界は引き続き batchSize に従いますが、メモリ上の SQL リスト自体は O(1) に保たれます。

これは完全なオプトインです。QueryImplementors を差し替えない限り、挙動は変わりません。

SQLログの出力形式

@BatchUpdate アノテーション内の sqlLog プロパティに SQL ログの出力形式を指定できます。

@BatchUpdate(sqlLog = SqlLogType.RAW)
int[] update(List<Employee> employees);

SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。