検索
SELECTステートメントを使用して検索操作を実行するには、DAOメソッドに @Select を注釈として付けます。
@Dao
public interface EmployeeDao {
@Select
List<Employee> selectByDepartmentName(String departmentName);
// ...
}
@Select アノテーションには SQLテンプレート が必要です。SQLファイルまたは @Sql アノテーションでSQLテンプレートを記述してください。
検索条件
検索条件はメソッドパラメータを使用して定義されます。以下のパラメータタイプがサポートされています。
任意の型
java.util.OptionalInt
java.util.OptionalLong
java.util.OptionalDouble
パラメータータイプが基本タイプまたはドメインタイプである場合、引数として null を渡すことができます。それ以外のパラメータータイプでは、引数として null を渡すことはできません。
基本クラスまたはドメインクラスを使用したクエリ
メソッドのパラメータとして 基本クラス または ドメインクラス を宣言します。
@Select
List<Employee> selectByNameAndSalary(String name, Salary salary);
メソッドのパラメータをSQLにバインドするには、 バインド変数ディレクティブ を使用します。
select * from employee where employee_name = /* name */'hoge' and salary > /* salary */100
任意の型を使用したクエリ
メソッドのパラメータに任意の型を使用する場合は、バインド変数ディレクティブの中で . を使ってフィールドアクセスまたはメソッド呼び出しを行い結果をSQLにバインドします。
@Select
List<Employee> selectByExample(Employee employee);
select * from employee where employee_name = /* employee.name */'hoge' and salary > /* employee.getSalary() */100
複数のパラメータを指定できます。
@Select
List<Employee> selectByEmployeeAndDepartment(Employee employee, Department department);
IN句へのマッピング
IN句にバインドするには、パラメータとして java.lang.Iterable のサブタイプを使用します。
@Select
List<Employee> selectByNames(List<String> names);
select * from employee where employee_name in /* names */('aaa','bbb','ccc')
単一レコードの検索
単一のレコード検索では、メソッドの戻り値の型は次のいずれかでなければなりません:
java.util.Map<String, Object>
基本クラス、 ドメインクラス、 エンティティ、または java.util.Map<String, Object> を要素とするjava.util.Optional
java.util.OptionalInt
java.util.OptionalLong
java.util.OptionalDouble
@Select
Employee selectByNameAndSalary(String name, BigDecimal salary);
戻り値の型が Optional でなく、結果件数が 0 の場合、null が返されます。
検索結果が2つ以上ある場合は、NonUniqueResultException がスローされます。
複数レコードの検索
複数のレコードを検索する場合は、メソッドの戻り値の型に java.util.List を指定します。List の要素は以下の型になります。
java.util.Map<String, Object>
java.util.OptionalInt
java.util.OptionalLong
java.util.OptionalDouble
@Select
List<Employee> selectByNameAndSalary(String name, Salary salary);
検索結果がない場合、空のリストが返されます。
ストリーム検索
大量のレコードをインクリメンタルに処理するために、 java.util.stream.Stream を使ったストリーム検索を利用できます。
ストリーム検索を行う方法は2つあります:1つはストリームをjava.util.Functionへ渡す方法、もう1つはメソッドから直接Streamを返す方法です。
関数にストリームを渡す
@Select アノテーションの strategy プロパティを SelectType.STREAM に設定し、パラメータとして java.util.Function<Stream<TARGET, RESULT>> のサブタイプを追加します。
@Select(strategy = SelectType.STREAM)
BigDecimal selectByNameAndSalary(String name, BigDecimal salary, Function<Stream<Employee>, BigDecimal> mapper);
DAOメソッドの呼び出し元は、ストリームを受け取って結果を返すラムダ式を渡します。
void doSomething() {
EmployeeDao dao = new EmployeeDaoImpl();
BigDecimal result = dao.selectByNameAndSalary(name, salary, stream -> doSomething(name, salary, stream));
}
Function<Stream<TARGET>, RESULT> の型パラメータ TARGET は以下のいずれかである必要があります。
java.util.Map<String, Object>
java.util.OptionalInt
java.util.OptionalLong
java.util.OptionalDouble
型パラメータの RESULT は、DAO メソッドの戻り値の型と一致する必要があります。
ストリームを返す
メソッドの戻り値型を java.util.stream.Stream に定義します。Streamには次の型の要素が含まれる可能性があります:
java.util.Map<String, Object>
java.util.OptionalInt
java.util.OptionalLong
java.util.OptionalDouble
@Select
Stream<Employee> selectByNameAndSalary(String name, BigDecimal salary);
DAOメソッドの呼び出し元は以下のとおりです。
void doSomething() {
EmployeeDao dao = new EmployeeDaoImpl();
try (Stream<Employee> stream = dao.selectByNameAndSalary(name, salary)) {
// ...
}
}
警告
java.sql.ResultSet 、 java.sql.PreparedStatement 、 java.sql.Connection などのリソースを適切に閉じるために、必ず Stream を閉じてください。
注釈
値を返すときにリソースを解放することを忘れるリスクがあるため、Domaは警告メッセージを表示します。 警告メッセージを抑制するには、@Suppress を以下のように指定してください。
@Select
@Suppress(messages = { Message.DOMA4274 })
Stream<Employee> selectByNameAndSalary(String name, BigDecimal salary);
コレクター検索
検索結果は java.util.Collector を使用して処理できます。
Collector を使用して検索結果を処理するには、@Select の strategy プロパティに SelectType.COLLECT を設定し、java.stream.Collector<TARGET, ACCUMULATION, RESULT> または java.stream.Collector<TARGET, ?, RESULT> のサブタイプであるパラメータを追加します。
@Select(strategy = SelectType.COLLECT)
<RESULT> RESULT selectBySalary(BigDecimal salary, Collector<Employee, ?, RESULT> collector);
DAOメソッドの呼び出し元は Collector のインスタンスを渡します。
EmployeeDao dao = new EmployeeDaoImpl();
Map<Integer, List<Employee>> result =
dao.selectBySalary(salary, Collectors.groupingBy(Employee::getDepartmentId));
Function<TARGET, ACCUMULATION, RESULT>, RESULT> の型パラメータ TARGET は以下のいずれかである必要があります。
java.util.Map<String, Object>
java.util.OptionalInt
java.util.OptionalLong
java.util.OptionalDouble
Collector<TARGET, ACCUMULATION, RESULT> の型パラメータの RESULT は、DAOメソッドの戻り値の型と一致する必要があります。
注釈
コレクト検索はストリーム検索のFunctionに渡す方法のショートカットです。ストリーム検索で得られる Stream オブジェクトの collect メソッドを使って同等のことができます。
アグリゲート戦略
@Select の aggregateStrategy 要素は、事前に定義されたアグリゲート戦略に基づいて、クエリ結果を階層構造のエンティティにマッピングすることを可能にします。
@Select(aggregateStrategy = EmployeeStrategy.class)
Employee selectByName(String name);
詳細は、 アグリゲート戦略 を参照してください。
検索オプション
SelectOptions を使用することで、SELECTステートメントをページングや悲観的排他制御のためのSQLへ変換することが可能です。
SelectOptions はDAOメソッドのパラメータとして定義されます。
@Dao
public interface EmployeeDao {
@Select
List<Employee> selectByDepartmentName(String departmentName, SelectOptions options);
// ...
}
静的な get メソッドを使用して SelectOptions のインスタンスを取得できます。
SelectOptions options = SelectOptions.get();
ページング
ページネーションを実装するには、SelectOptions で取得するレコード数を指定するための limit メソッドと、開始位置を指定するための offset メソッドを使用します。この SelectOptions インスタンスを DAO メソッドに渡します。
SelectOptions options = SelectOptions.get().offset(5).limit(10);
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByDepartmentName("ACCOUNT", options);
ページングは、ファイルに記述されているオリジナルのSQLを書き換え実行することで実現されています。 オリジナルのSQLは次の条件を満たしていなければいけません。
SELECT ステートメントである
最上位のレベルでUNION、EXCEPT、INTERSECT等の集合演算を行っていない (サブクエリで利用している場合は可)
ページング処理を含んでいない
さらに、ダイアレクト に従って特定の条件を満たす必要があります。
ダイアレクト |
条件 |
|---|---|
Db2Dialect |
offset を指定する場合は、ORDER BY 句で指定されたすべての列が SELECT 句に含まれる |
Mssql2008Dialect |
offset を指定する場合は、ORDER BY 句で指定されたすべての列が SELECT 句に含まれる |
MssqlDialect |
offset を指定する場合は、ORDER BY 句が存在する |
StandardDialect |
ORDER BY 句があり、ORDER BY 句で指定されたすべての列が SELECT 句に含まれる |
悲観的排他制御
SelectOptions の forUpdate メソッドを使用すると、悲観的排他制御を指定できます。
SelectOptions options = SelectOptions.get().forUpdate();
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByDepartmentName("ACCOUNT", options);
SelectOptions は、 forUpdate で始まる名前を持つ悲観的排他制御のためのメソッドを提供します。 ロックされるテーブルや列のエイリアスを指定する forUpdate や ロックの取得を待機しない forUpdateNowait など。
Pessimistic concurrency control は、元の SQL を書き換えることによって達成されます。これは、以下の条件を満たす必要があります。
SELECT ステートメントである
最上位のレベルでUNION、EXCEPT、INTERSECT等の集合演算を行っていない (サブクエリで利用している場合は可)
楽観的排他制御の処理を含んでいない
ダイアレクトによっては、悲観的排他制御の一部またはすべての方法が使用できない場合があります。
ダイアレクト |
説明 |
|---|---|
Db2Dialect |
forUpdate()を使用できる |
H2Dialect |
forUpdate()を使用できる |
HsqldbDialect |
forUpdate()を使用できる |
Mssql2008Dialect |
forUpdate()とforUpdateNoWait()を使用できる。 ただし、オリジナルのSQLのFROM句は1つのテーブルだけから成らねばならない。 |
MysqlDialect |
forUpdate() を使用できる |
MysqlDialect (V8) |
forUpdate(), forUpdate(String... aliases), forUpdateNowait(), forUpdateNowait(String... aliases) を使用できる |
OracleDialect |
forUpdate()、forUpdate(String... aliases)、forUpdateNowait()、forUpdateNowait(String... aliases)、forUpdateWait(int waitSeconds)、forUpdateWait(int waitSeconds, String... aliases) を使用できる |
PostgresDialect |
forUpdate() および forUpdate(String... エイリアス) を使用できる |
StandardDialect |
悲観的排他制御用のメソッドすべてを使用できない |
カウント
SelectOptionsの count メソッドを使用して、レコードの総数を取得します。これは通常、ページネーションと共に使用され、ページネーションのフィルタリングが適用される前の総レコード数を取得します。
SelectOptions options = SelectOptions.get().offset(5).limit(10).count();
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByDepartmentName("ACCOUNT", options);
long count = options.getCount();
レコードの総数は、DAOメソッドを呼び出した後に SelectOptions の getCount メソッドを使用して取得されます。 count メソッドがDAO メソッドの呼び出しより前に実行されていない場合、getCount メソッドは -1 を返します。
検索結果の保証
検索結果が少なくとも1つ返されるようにするには、 @Select アノテーションの
ensureResult プロパティを true に設定します。
@Select(ensureResult = true)
Employee selectById(Integer id);
検索結果がない場合は、NoResultException がスローされます。
検索結果のマッピングの保証
結果セットのすべての列が欠落せずにエンティティのプロパティにマップされていることを確認したい場合、 @Select の ensureResultMapping 要素に true を指定します。
@Select(ensureResultMapping = true)
Employee selectById(Integer id);
結果セットのカラムにマッピングされていないプロパティがある場合、 ResultMappingException がスローされます。
クエリタイムアウト
@Select アノテーション内の queryTimeout プロパティにクエリタイムアウトを秒単位で指定できます。
@Select(queryTimeout = 10)
List<Employee> selectAll();
queryTimeout プロパティの値が設定されていない場合は、 設定 で指定されたクエリタイムアウトが使用されます。
フェッチサイズ
@Select アノテーションの fetchSize プロパティで、フェッチサイズを指定できます。
@Select(fetchSize = 20)
List<Employee> selectAll();
fetchSize プロパティの値が設定されていない場合、 設定 で指定されたフェッチサイズが使用されます。
最大行数
@Select アノテーションの maxRows プロパティで、最大行数を指定できます。
@Select(maxRows = 100)
List<Employee> selectAll();
maxRows プロパティの値が設定されていない場合、 設定 で指定された最大行数が使用されます。
マップのキーの命名規則
検索結果を java.util.Map<String, Object> にマッピングする場合、 @Select の mapKeyNaming プロパティでマップのキーの命名規則を指定できます。
@Select(mapKeyNaming = MapKeyNamingType.CAMEL_CASE)
List<Map<String, Object>> selectAll();
MapKeyNamingType.CAMEL_CASE は、カラム名がキャメルケースに変換されることを示します。カラム名を大文字または小文字に変換する規約もあります。
最終的な変換結果は、ここで指定された値と 設定 で指定された MapKeyNaming の実装によって決定されます。
SQLログの出力形式
@Select アノテーションの sqlLog プロパティでSQLログの出力形式を指定できます。
@Select(sqlLog = SqlLogType.RAW)
List<Employee> selectById(Integer id);
SqlLogType.RAW は、SQLステートメントとそのバインドパラメータをログに出力します。