クエリメソッド

このセクションでは、Spring Data JDBC の実装と使用に関する特定の情報を提供します。

通常、リポジトリでトリガーするデータアクセス操作のほとんどは、データベースに対して実行されるクエリになります。このようなクエリの定義は、次の例に示すように、リポジトリインターフェースでメソッドを宣言することです。

PersonRepository とクエリメソッド
interface PersonRepository extends PagingAndSortingRepository<Person, String> {

  List<Person> findByFirstname(String firstname);                                   (1)

  List<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); (2)

  Slice<Person> findByLastname(String lastname, Pageable pageable);                 (3)

  Page<Person> findByLastname(String lastname, Pageable pageable);                  (4)

  Person findByFirstnameAndLastname(String firstname, String lastname);             (5)

  Person findFirstByLastname(String lastname);                                      (6)

  @Query("SELECT * FROM person WHERE lastname = :lastname")
  List<Person> findByLastname(String lastname);                                     (7)
  @Query("SELECT * FROM person WHERE lastname = :lastname")
  Stream<Person> streamByLastname(String lastname);                                     (8)

  @Query("SELECT * FROM person WHERE username = :#{ principal?.username }")
  Person findActiveUser();															(9)
}
1 このメソッドは、指定された firstname を持つすべての人々のクエリを示します。クエリは、And および Or と連結できる制約のメソッド名を解析することによって導出されます。メソッド名は SELECT … FROM person WHERE firstname = :firstname のクエリ式になります。
2Pageable を使用して、オフセットと並べ替えのパラメーターをデータベースに渡します。
3Slice<Person> を返します。LIMIT+1 行を選択して、さらに消費するデータがあるかどうかを判断します。ResultSetExtractor のカスタマイズはサポートされていません。
4Page<Person> を返すページ分割されたクエリを実行します。指定されたページ境界内のデータのみを選択し、場合によっては合計数を決定するためのカウントクエリを選択します。ResultSetExtractor カスタマイズはサポートされていません。
5 指定された条件で単一のエンティティを検索します。一意でない結果の場合は、IncorrectResultSizeDataAccessException で完了します。
6<3> とは対照的に、クエリがより多くの結果ドキュメントを生成した場合でも、最初のエンティティは常に出力されます。
7findByLastname メソッドは、指定された lastname を持つすべての人のクエリを表示します。
8streamByLastname メソッドは Stream を返します。これにより、データベースから値が返されるとすぐに値が可能になります。
9Spring 式言語を使用して、パラメーターを動的に解決できます。サンプルでは、現在のユーザーのユーザー名を解決するために Spring Security が使用されています。

次の表は、クエリメソッドでサポートされるキーワードを示しています。

表 1: クエリメソッドでサポートされるキーワード
キーワード サンプル 論理的な結果

After

findByBirthdateAfter(Date date)

birthdate > date

GreaterThan

findByAgeGreaterThan(int age)

age > age

GreaterThanEqual

findByAgeGreaterThanEqual(int age)

age >= age

Before

findByBirthdateBefore(Date date)

birthdate < date

LessThan

findByAgeLessThan(int age)

age < age

LessThanEqual

findByAgeLessThanEqual(int age)

age <= age

Between

findByAgeBetween(int from, int to)

age BETWEEN from AND to

NotBetween

findByAgeNotBetween(int from, int to)

age NOT BETWEEN from AND to

In

findByAgeIn(Collection<Integer> ages)

age IN (age1, age2, ageN)

NotIn

findByAgeNotIn(Collection ages)

age NOT IN (age1, age2, ageN)

IsNotNull, NotNull

findByFirstnameNotNull()

firstname IS NOT NULL

IsNull, Null

findByFirstnameNull()

firstname IS NULL

Like, StartingWith, EndingWith

findByFirstnameLike(String name)

firstname LIKE name

NotLike, IsNotLike

findByFirstnameNotLike(String name)

firstname NOT LIKE name

Containing on String

findByFirstnameContaining(String name)

firstname LIKE '%' + name + '%'

NotContaining on String

findByFirstnameNotContaining(String name)

firstname NOT LIKE '%' + name + '%'

(No keyword)

findByFirstname(String name)

firstname = name

Not

findByFirstnameNot(String name)

firstname != name

IsTrue, True

findByActiveIsTrue()

active IS TRUE

IsFalse, False

findByActiveIsFalse()

active IS FALSE

クエリの派生は、結合を使用せずに WHERE 句で使用できるプロパティに制限されています。

クエリ検索戦略

JDBC モジュールは、@Query アノテーションの文字列またはプロパティファイルの名前付きクエリとしてクエリを手動で定義することをサポートします。

メソッドの名前からクエリを取得することは、現在、単純なプロパティに制限されています。つまり、プロパティは集約ルートに直接存在します。また、このアプローチでは、select クエリのみがサポートされています。

@Query を使用する

次の例は、@Query を使用してクエリメソッドを宣言する方法を示しています。

@Query を使用してクエリメソッドを宣言する
interface UserRepository extends CrudRepository<User, Long> {

  @Query("select firstName, lastName from User u where u.emailAddress = :email")
  User findByEmailAddress(@Param("email") String email);
}

クエリ結果をエンティティに変換する場合、デフォルトでは、Spring Data JDBC が生成するクエリと同じ RowMapper が使用されます。指定するクエリは、RowMapper が予期する形式と一致する必要があります。エンティティのコンストラクターで使用されるすべてのプロパティの列を提供する必要があります。setter を介して設定されるプロパティの列、ウィザーまたはフィールドアクセスはオプションです。結果に一致する列がないプロパティは設定されません。クエリは、集約ルート、埋め込みエンティティ、SQL 配列型として保存およびロードされるプリミティブ型の配列を含む 1 対 1 の関連を設定するために使用されます。エンティティのマップ、リスト、セット、配列に対して個別のクエリが生成されます。

プロパティ 1 対 1 の関連の名前には、関連の名前と _ のプレフィックスを付ける必要があります。たとえば、上記の例の User にプロパティ city を持つ address がある場合、その city の列には address_city というラベルを付ける必要があります。

文字列ベースのクエリは、ページネーションをサポートしておらず、クエリパラメーターとして SortPageRequestLimit を受け入れないことに注意してください。これらのクエリの場合、クエリを書き直す必要があるからです。制限を適用したい場合は、SQL を使用してこの意図を表現し、適切なパラメーターをクエリにバインドしてください。

クエリには SpEL 式を含めることができます。評価方法が異なる 2 つのバリアントがあります。

最初のバリアントでは、SpEL 式に : というプレフィックスが付けられ、バインド変数のように使用されます。このような SpEL 式はバインド変数に置き換えられ、変数は SpEL 式の結果にバインドされます。

クエリで SpEL を使用する
@Query("SELECT * FROM person WHERE id = :#{#person.id}")
Person findWithSpEL(PersonRef person);

上の例で示したように、これを使用してパラメーターのメンバーにアクセスできます。より複雑なユースケースでは、EvaluationContextExtension をアプリケーションコンテキストで使用できるようにすることができ、これにより、SpEL で任意のオブジェクトを使用できるようになります。

他のバリアントはクエリ内のどこでも使用でき、クエリを評価した結果によってクエリ文字列内の式が置き換えられます。

クエリで SpEL を使用する
@Query("SELECT * FROM #{tableName} WHERE id = :id")
Person findWithSpEL(PersonRef person);

これは最初の実行前に一度評価され、2 つの変数 tableName と qualifiedTableName が追加された StandardEvaluationContext を使用します。この使用箇所は、テーブル名自体が動的な場合に最も便利です。テーブル名自体が SpEL 式も使用するためです。

Spring は、-parameters コンパイラーフラグに基づく Java 8 のパラメーター名の検出を完全にサポートしています。デバッグ情報の代替としてビルドでこのフラグを使用することにより、名前付きパラメーターの @Param アノテーションを省略できます。
Spring Data JDBC は、名前付きパラメーターのみをサポートします。

名前付きクエリ

前のセクションで説明したように、アノテーションにクエリが指定されていない場合、Spring Data JDBC は名前付きクエリを見つけようとします。クエリの名前を決定する方法は 2 つあります。デフォルトでは、クエリのドメインクラス、つまりリポジトリの集約ルートを取得し、その単純名を取得して、. で区切られたメソッドの名前を追加します。あるいは、@Query アノテーションには、検索するクエリの名前を指定するために使用できる name 属性があります。

名前付きクエリは、クラスパスのプロパティファイル META-INF/jdbc-named-queries.properties で提供されることが期待されています。

そのファイルの場所は、値を @EnableJdbcRepositories.namedQueriesLocation に設定することにより変更できます。

名前付きクエリは、アノテーションによって提供されるクエリと同じ方法で処理されます。

クエリメソッドのカスタマイズ

ストリーミング結果

クエリメソッドの戻り値の型として Stream を指定すると、Spring Data JDBC は要素が使用可能になるとすぐに要素を返します。大量のデータを処理する場合、これは遅延とメモリ要件を削減するのに適しています。

ストリームには、データベースへのオープン接続が含まれています。メモリリークを回避するには、ストリームを閉じて、最終的にその接続を閉じる必要があります。そのための推奨される方法は try-with-resource clause です。また、データベースへの接続が閉じられると、ストリームはそれ以上の要素を取得できず、例外をスローする可能性があることも意味します。

カスタム RowMapper または ResultSetExtractor

@Query アノテーションを使用すると、使用するカスタム RowMapper または ResultSetExtractor を指定できます。属性 rowMapperClass および resultSetExtractorClass を使用すると、使用するクラスを指定できます。これらのクラスは、デフォルトのコンストラクターを使用してインスタンス化されます。あるいは、Spring アプリケーションコンテキストから rowMapperClassRef または resultSetExtractorClassRef を Bean 名に設定することもできます。

単一のメソッドだけでなく、特定の型を返すカスタムクエリを持つすべてのメソッドに対して特定の RowMapper を使用する場合は、メソッドの戻り値の型ごとに RowMapperMap Bean を登録し、RowMapper を登録できます。次の例は、DefaultQueryMappingConfiguration を登録する方法を示しています。

@Bean
QueryMappingConfiguration rowMappers() {
  return new DefaultQueryMappingConfiguration()
    .register(Person.class, new PersonRowMapper())
    .register(Address.class, new AddressRowMapper());
}

メソッドに使用する RowMapper を決定するときは、メソッドの戻り値の型に基づいて、次の手順に従います。

  1. 型が単純型の場合、RowMapper は使用されません。

    代わりに、クエリは単一の列を持つ単一の行を返すことが期待され、戻り値の型への変換がその値に適用されます。

  2. QueryMappingConfiguration のエンティティクラスは、問題の戻り値型のスーパークラスまたはインターフェースであるものが見つかるまで繰り返されます。そのクラスに登録された RowMapper が使用されます。

    反復は登録順に行われるため、特定の型の後に、より一般的な型を登録するようにしてください。

該当する場合、コレクションや Optional などのラッパー型はアンラップされます。戻り値の型 Optional<Person> は、前のプロセスで Person 型を使用します。

カスタム RowMapper から QueryMappingConfiguration@Query(rowMapperClass= …)、カスタム ResultSetExtractor を使用すると、結果マッピングが必要に応じて独自のイベント / コールバックを発行できるため、エンティティのコールバックとライフサイクルイベントが無効になります。

変更クエリ

次の例に示すように、query メソッドで @Modifying を使用して、クエリを変更クエリとしてマークできます。

@Modifying
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
boolean updateName(@Param("id") Long id, @Param("name") String name);

以下の戻り値の型を指定できます。

  • void

  • int (更新されたレコード数)

  • boolean (レコードが更新されたかどうか)

変更クエリは、データベースに対して直接実行されます。イベントやコールバックは呼び出されません。アノテーション付きクエリで更新されない場合、監査アノテーションを含むフィールドも更新されません。