© 2018-2022 The original authors.
| このドキュメントのコピーは、あなた自身の使用および他者への配布のために作成することができますが、そのようなコピーに料金を請求しないこと、さらに、印刷物または電子的に配布されるかどうかにかかわらず、各コピーにこの著作権表示が含まれていることを条件とします。 |
序文
Spring Data JDBC プロジェクトは、Spring のコアコンセプトを、ドメイン駆動設計 (DDD) の原則に合わせた JDBC データベースを使用するソリューションの開発に適用します。集約を格納および照会するための高レベルの抽象化として「テンプレート」を提供します。
このドキュメントは、Spring Data JDBC サポートのリファレンスガイドです。概念とセマンティクスと構文について説明します。
このセクションでは、いくつかの基本的な導入を行います。このドキュメントの残りの部分では、Spring Data JDBC 機能についてのみ言及し、ユーザーが SQL および Spring の概念に精通していることを前提としています。
1. Spring の学習
Spring Data は、以下を含む Spring フレームワークのコア機能を使用します。
Spring API を知る必要はありませんが、その背景となる概念を理解することは重要です。最低限、Inversion of Control (IoC) の背景となる概念は知っておくべきであり、使用する IoC コンテナーにも精通している必要があります。
JDBC Aggregate サポートのコア機能は直接使用でき、Spring コンテナーの IoC サービスを呼び出す必要はありません。これは、Spring コンテナーの他のサービスなしで「スタンドアロン」で使用できる JdbcTemplate によく似ています。リポジトリのサポートなど、Spring Data JDBC のすべての機能を活用するには、Spring を使用するようにライブラリの一部を構成する必要があります。
Spring の詳細については、Spring Framework を詳細に説明する包括的なドキュメントを参照してください。このテーマに関する記事、ブログのエントリ、本はたくさんあります。詳細については、Spring フレームワークのホームページを参照してください。
2. 要件
Spring Data JDBC バイナリには、JDK レベル 8.0 以降および Spring Framework 6.0.9 以降が必要です。
データベースに関しては、Spring Data JDBC には、ベンダー固有のフレーバーよりも一般的な SQL 機能を抽象化するためのダイアレクトが必要です。Spring Data JDBC は、以下のデータベースを直接サポートしています。
DB2
H2
HSQLDB
MariaDB
Microsoft SQL Server
MySQL
Oracle
Postgres
別のデータベースを使用すると、アプリケーションは起動しません。ダイアレクトのセクションには、そのような場合の対処方法の詳細が含まれています。
3. 追加のヘルプリソース
新しいフレームワークを学ぶことは必ずしも簡単ではありません。このセクションでは、Spring Data JDBC モジュールから始めるためのわかりやすいガイドを提供することを試みます。ただし、問題が発生した場合やアドバイスが必要な場合は、次のリンクのいずれかを使用してください。
- コミュニティフォーラム
Stack Overflow (英語) 上の Spring Data は、すべての Spring Data(ドキュメントだけではない)ユーザーが情報を共有し、互いに助け合うためのタグです。登録は投稿にのみ必要です。
- 専門サポート
Spring Data および Spring を開発している Pivotal Sofware, Inc では、レスポンス時間が保証されたプロフェッショナルなソースからのサポートを提供しています。
4. 開発のフォロー
Spring Data JDBC のソースコードリポジトリ、ナイトリービルド、スナップショットアーティファクトについては、Spring Data JDBC のホームページをを参照してください。Stack Overflow (英語) のコミュニティを通じて開発者と交流することで、Spring Data を Spring コミュニティのニーズに最適に対応させることができます。バグに遭遇した場合、または改善を提案したい場合は、Spring Data 課題追跡システム [GitHub] (英語) でチケットを作成してください。Spring エコシステムの最新ニュースやアナウンスメントを入手するには、Spring コミュニティのポータルを参照してください。また、Spring ブログ (英語) やプロジェクトチームの Twitter ( SpringData (英語) ) をフォローすることもできます。
5. プロジェクトメタデータ
リリースリポジトリ: https://repo.spring.io/libs-release (英語)
マイルストーンリポジトリ: https://repo.spring.io/libs-milestone (英語)
スナップショットリポジトリ: https://repo.spring.io/libs-snapshot (英語)
6. Spring Data のアップグレード
Spring Data の以前のバージョンからアップグレードする方法については、プロジェクトの wiki [GitHub] (英語) で説明されています。リリースノートセクション [GitHub] (英語) のリンクに従って、アップグレードするバージョンを見つけます。
バージョンアップ手順は、常にリリースノートの最初の項目です。複数のバージョンを飛び越えてバージョンアップする場合は、途中のバージョンのリリースノートも確認してください。
7. 依存関係
個々の Spring Data モジュールの開始日が異なるため、それらのほとんどは異なるメジャーバージョン番号とマイナーバージョン番号を持っています。互換性のあるものを見つける最も簡単な方法は、互換性のあるバージョンが定義された状態で提供される Spring Data リリーストレイン BOM に依存することです。Maven プロジェクトでは、次のように POM の <dependencyManagement /> セクションでこの依存関係を宣言します。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-bom</artifactId>
<version>2022.0.6</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement> 現在のリリーストレインバージョンは 2022.0.6 です。トレインバージョンでは、パターン YYYY.MINOR.MICRO の calver (英語) を使用しています。バージョン名は、GA リリースとサービスリリースでは ${calver} に従い、他のすべてのバージョンでは次のパターンに従います。${calver}-${modifier}。modifier は次のいずれかになります。
SNAPSHOT: 現在のスナップショットM1、M2など: マイルストーンRC1、RC2など: リリース候補
Spring Data サンプルリポジトリ [GitHub] (英語) で BOM の使用例を見つけることができます。これが適切な場所にあると、次のように、<dependencies /> ブロックでバージョンなしで使用する Spring Data モジュールを宣言できます。
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependencies>7.1. Spring Boot を使用した依存関係管理
Spring Boot は、Spring Data モジュールの最新バージョンを選択します。それでも新しいバージョンにアップグレードする場合は、spring-data-bom.version プロパティを、使用するトレーニングバージョンとイテレーションに設定します。
詳細については、Spring Boot のドキュメント ( "Spring Data Bom" で検索) を参照してください。
8. Spring Data リポジトリの操作
Spring Data リポジトリの抽象化のゴールは、さまざまな永続ストアのデータアクセスレイヤーを実装するために必要な定型コードの量を大幅に削減することです。
Spring Data リポジトリのドキュメントとモジュール この章では、Spring Data リポジトリの中心的な概念とインターフェースについて説明します。この章の情報は、Spring Data Commons モジュールから取得されます。Jakarta Persistence API (JPA) モジュールの構成とコードサンプルを使用します。"リポジトリクエリキーワード" は、リポジトリの抽象化によって一般的にサポートされているクエリメソッドのキーワードをカバーしています。モジュールの特定の機能の詳細については、このドキュメントのそのモジュールに関する章を参照してください。 |
8.1. コアコンセプト
Spring Data リポジトリ抽象化の中心的なインターフェースは Repository です。管理するドメインクラスと、ドメインクラスの識別子の型を型引数として取ります。このインターフェースは主に、操作する型をキャプチャーし、このインターフェースを継承するインターフェースを検出するのに役立つマーカーインターフェースとして機能します。CrudRepository (Javadoc) および ListCrudRepository (Javadoc) インターフェースは、管理されているエンティティクラスに高度な CRUD 機能を提供します。
CrudRepository インターフェース public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity); (1)
Optional<T> findById(ID primaryKey); (2)
Iterable<T> findAll(); (3)
long count(); (4)
void delete(T entity); (5)
boolean existsById(ID primaryKey); (6)
// … more functionality omitted.
}
| 1 | 指定されたエンティティを保存します。 |
| 2 | 指定された ID で識別されるエンティティを返します。 |
| 3 | すべてのエンティティを返します。 |
| 4 | エンティティの数を返します。 |
| 5 | 指定されたエンティティを削除します。 |
| 6 | 指定された ID のエンティティが存在するかどうかを示します。 |
このインターフェースで宣言されたメソッドは、一般に CRUD メソッドと呼ばれます。ListCrudRepository は同等のメソッドを提供しますが、CrudRepository メソッドが Iterable を返すのに対し、それらは List を返します。
また、JpaRepository や MongoRepository などの永続化技術固有の抽象化も提供します。これらのインターフェースは CrudRepository を継承し、CrudRepository などのかなり汎用的な永続化テクノロジーにとらわれないインターフェースに加えて、基礎となる永続化テクノロジーの機能を公開します。 |
CrudRepository に加えて、エンティティへのページ指定されたアクセスを容易にする追加のメソッドを追加する PagingAndSortingRepository (Javadoc) 抽象化があります。
PagingAndSortingRepository インターフェース public interface PagingAndSortingRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
20 のページサイズで User の 2 番目のページにアクセスするには、次のようなことができます。
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
クエリメソッドに加えて、カウントクエリと削除クエリの両方のクエリ派生を使用できます。次のリストは、派生カウントクエリのインターフェース定義を示しています。
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
次のリストは、派生削除クエリのインターフェース定義を示しています。
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
8.2. クエリメソッド
通常、標準の CRUD 機能リポジトリには、基になるデータストアに対するクエリがあります。Spring Data では、これらのクエリを宣言することは 4 ステップのプロセスになります。
次の例に示すように、リポジトリまたはそのサブインターフェースの 1 つを継承するインターフェースを宣言し、処理するドメインクラスと ID 型に入力します。
interface PersonRepository extends Repository<Person, Long> { … }インターフェースでクエリメソッドを宣言します。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }JavaConfig または XML 構成を使用して、Spring をセットアップして、これらのインターフェースのプロキシインスタンスを作成します。
Java@EnableJpaRepositories class Config { … }+ アノテーション付きクラスのパッケージがデフォルトで使用されるため、JavaConfig バリアントはパッケージを明示的に構成しないことに注意してください。スキャンするパッケージをカスタマイズするには、データストア固有のリポジトリの
@EnableJpaRepositories-annotation のbasePackage …属性のいずれかを使用します。次の例に示すように、リポジトリインスタンスを挿入して使用します。
class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
以下のセクションでは、各ステップについて詳しく説明します。
8.3. リポジトリインターフェースの定義
リポジトリインターフェースを定義するには、最初にドメインクラス固有のリポジトリインターフェースを定義する必要があります。インターフェースは Repository を継承し、ドメインクラスと ID 型に入力する必要があります。そのドメイン型の CRUD メソッドを公開する場合は、CrudRepository、または Repository の代わりにそのバリアントの 1 つを継承できます。
8.3.1. リポジトリ定義の微調整
リポジトリインターフェースを使い始める方法にはいくつかのバリエーションがあります。
典型的なアプローチは、CrudRepository を継承することです。これにより、CRUD 機能のメソッドが提供されます。CRUD は、Create、Read、Update、Delete の略です。バージョン 3.0 では、CrudRepository と非常によく似た ListCrudRepository も導入されましたが、複数のエンティティを返すメソッドでは、使いやすい Iterable ではなく List が返されます。
リアクティブストアを使用している場合は、使用しているリアクティブフレームワークに応じて ReactiveCrudRepository または RxJava3CrudRepository を選択できます。
Kotlin を使用している場合は、Kotlin のコルーチンを利用する CoroutineCrudRepository を選択できます。
さらに、Sort 抽象化、または最初の場合は Pageable 抽象化を指定できるメソッドが必要な場合は、PagingAndSortingRepository、ReactiveSortingRepository、RxJava3SortingRepository または CoroutineSortingRepository を継承できます。さまざまな並べ替えリポジトリは、3.0 より前の Spring Data バージョンのようにそれぞれの CRUD リポジトリを継承しなくなったことに注意してください。両方の機能が必要な場合は、両方のインターフェースを継承する必要があります。
Spring Data インターフェースを継承したくない場合は、リポジトリインターフェースに @RepositoryDefinition でアノテーションを付けることもできます。CRUD リポジトリインターフェースの 1 つを継承すると、エンティティを操作するためのメソッドの完全なセットが公開されます。公開するメソッドを選択したい場合は、公開するメソッドを CRUD リポジトリからドメインリポジトリにコピーします。その際、メソッドの戻り値の型を変更できます。Spring Data は、可能であれば戻り値の型を尊重します。例: 複数のエンティティを返すメソッドの場合、Iterable<T>、List<T>、Collection<T> または VAVR リストを選択できます。
アプリケーション内の多くのリポジトリに同じメソッドのセットが必要な場合は、継承元の独自のベースインターフェースを定義できます。このようなインターフェースには、@NoRepositoryBean のアノテーションを付ける必要があります。これにより、Spring Data はインスタンスを直接作成しようとして失敗し、そのリポジトリのエンティティを特定できないために失敗します。これは、ジェネリクス型変数がまだ含まれているためです。
次の例は、CRUD メソッド(この場合は findById および save)を選択的に公開する方法を示しています。
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
前の例では、すべてのドメインリポジトリと公開された findById(…) および save(…) に共通のベースインターフェースを定義しました。これらのメソッドは、Spring Data が提供する選択のストアのベースリポジトリ実装にルーティングされます(たとえば、JPA を使用する場合、実装は SimpleJpaRepository です)。これは、それらが CrudRepository のメソッドシグネチャーと一致するためです。そのため、UserRepository はユーザーを保存し、ID で個々のユーザーを検索し、メールアドレスで Users を検索するクエリをトリガーできるようになりました。
中間リポジトリインターフェースには @NoRepositoryBean のアノテーションが付けられています。Spring Data が実行時にインスタンスを作成してはならないすべてのリポジトリインターフェースに、そのアノテーションを必ず追加してください。 |
8.3.2. 複数の Spring Data モジュールでリポジトリを使用する
定義済みスコープ内のすべてのリポジトリインターフェースが Spring Data モジュールにバインドされているため、アプリケーションで一意の Spring Data モジュールを使用すると、物事が簡単になります。アプリケーションによっては、複数の Spring Data モジュールを使用する必要がある場合があります。そのような場合、リポジトリ定義は永続化テクノロジーを区別する必要があります。クラスパスで複数のリポジトリファクトリを検出すると、Spring Data は厳密なリポジトリ構成モードに入ります。厳密な構成では、リポジトリまたはドメインクラスの詳細を使用して、リポジトリ定義の Spring Data モジュールバインディングについて決定します。
リポジトリ定義がモジュール固有のリポジトリを継承する場合、特定の Spring Data モジュールの有効な候補です。
ドメインクラスにモジュール固有の型アノテーションが付けられている場合、そのクラスは特定の Spring Data モジュールの有効な候補となります。Spring Data モジュールは、サードパーティのアノテーション (JPA の
@Entityなど) を受け入れるか、独自のアノテーション (Spring Data MongoDB および Spring Data Elasticsearch の@Documentなど) を提供します。
次の例は、モジュール固有のインターフェース(この場合は JPA)を使用するリポジトリを示しています。
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository および UserRepository は、型階層で JpaRepository を継承します。それらは、Spring Data JPA モジュールの有効な候補です。
次の例は、汎用インターフェースを使用するリポジトリを示しています。
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository と AmbiguousUserRepository は、型階層内の Repository と CrudRepository のみを継承します。一意の Spring Data モジュールを使用する場合はこれで問題ありませんが、複数のモジュールでは、これらのリポジトリをどの特定の Spring Data にバインドする必要があるかを区別できません。
次の例は、アノテーション付きのドメインクラスを使用するリポジトリを示しています。
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepository は、JPA @Entity アノテーションが付けられた Person を参照しているため、このリポジトリは明らかに Spring Data JPA に属しています。UserRepository は、Spring Data MongoDB の @Document アノテーションでアノテーションが付けられた User を参照します。
次の悪い例は、アノテーションが混在するドメインクラスを使用するリポジトリを示しています。
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
この例は、JPA アノテーションと Spring Data MongoDB アノテーションの両方を使用するドメインクラスを示しています。JpaPersonRepository と MongoDBPersonRepository の 2 つのリポジトリを定義します。1 つは JPA 用で、もう 1 つは MongoDB での使用を目的としています。Spring Data はリポジトリを区別できなくなり、未定義の動作につながります。
リポジトリ型の詳細および識別ドメインクラスアノテーションは、特定の Spring Data モジュールのリポジトリ候補を識別するための厳密なリポジトリ構成に使用されます。同じドメイン型で複数の永続化テクノロジ固有のアノテーションを使用することが可能であり、複数の永続化テクノロジでドメイン型を再利用できます。ただし、Spring Data は、リポジトリをバインドする一意のモジュールを決定できなくなります。
リポジトリを区別する最後の方法は、リポジトリベースパッケージをスコープすることです。ベースパッケージは、リポジトリインターフェース定義のスキャンの開始点を定義します。これは、適切なパッケージにリポジトリ定義があることを意味します。デフォルトのアノテーション駆動型の構成では、構成クラスのパッケージが使用されます。XML ベースの構成の基本パッケージは必須です。
次の例は、基本パッケージのアノテーション駆動型の構成を示しています。
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
8.4. クエリメソッドの定義
リポジトリプロキシには、メソッド名からストア固有のクエリを派生させる 2 つの方法があります。
メソッド名から直接クエリを導出します。
手動で定義されたクエリを使用します。
利用可能なオプションは、実際のストアによって異なります。ただし、作成する実際のクエリを決定する戦略が必要です。次のセクションでは、使用可能なオプションについて説明します。
8.4.1. クエリ検索戦略
リポジトリインフラストラクチャがクエリを解決するために、次の戦略を使用できます。Java 構成の場合、EnableJpaRepositories アノテーションの queryLookupStrategy 属性を使用できます。特定のデータストアでは、一部の戦略がサポートされていない場合があります。
CREATEは、クエリメソッド名からストア固有のクエリを作成しようとします。一般的なアプローチは、メソッド名から既知のプレフィックスの特定のセットを削除し、メソッドの残りを解析することです。クエリ構築の詳細については、"クエリ作成" を参照してください。USE_DECLARED_QUERYは、宣言されたクエリを見つけようとし、見つからない場合は例外をスローします。クエリは、どこかのアノテーションによって定義することも、他の方法で宣言することもできます。そのストアで利用可能なオプションを見つけるには、特定のストアのドキュメントを参照してください。リポジトリインフラストラクチャがブートストラップ時にメソッドに対して宣言されたクエリを見つけられない場合、失敗します。CREATE_IF_NOT_FOUND(デフォルト)は、CREATEとUSE_DECLARED_QUERYを組み合わせたものです。最初に宣言されたクエリを検索し、宣言されたクエリが見つからない場合は、カスタムメソッド名ベースのクエリを作成します。これはデフォルトのルックアップ戦略であるため、明示的に何も構成しない場合に使用されます。メソッド名によるクエリ定義をすばやく行うだけでなく、必要に応じて宣言されたクエリを導入することにより、これらのクエリをカスタム調整することもできます。
8.4.2. クエリ作成
Spring Data リポジトリインフラストラクチャに組み込まれているクエリビルダーメカニズムは、リポジトリのエンティティに対して制約クエリを構築できます。
次の例は、いくつかのクエリを作成する方法を示しています。
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
クエリメソッド名の解析は、主語と述語に分けられます。最初の部分(find … By、exists … By)はクエリのサブジェクトを定義し、2 番目の部分は述語を形成します。導入句(主語)には、さらに式を含めることができます。find (または他の導入キーワード)と By の間のテキストは、Distinct などの結果を制限するキーワードの 1 つを使用して、作成するクエリまたは Top/First は、クエリ結果を制限しますに個別のフラグを設定しない限り、説明的であると見なされます。
付録には、クエリメソッドのサブジェクトキーワードとクエリメソッドの述語キーワードの完全なリストが含まれています。これには、並べ替えや大文字小文字の修飾子が含まれます。ただし、最初の By は、実際の条件述語の開始を示す区切り文字として機能します。非常に基本的なレベルでは、エンティティプロパティの条件を定義し、And および Or と連結できます。
メソッドの解析の実際の結果は、クエリを作成する永続ストアによって異なります。ただし、注意すべき一般的な事項がいくつかあります。
式は通常、連結可能な演算子と組み合わせたプロパティトラバーサルです。プロパティ式を
ANDおよびORと組み合わせることができます。プロパティ式のBetween、LessThan、GreaterThan、Likeなどの演算子もサポートされます。サポートされている演算子はデータストアによって異なる可能性があるため、リファレンスドキュメントの適切な部分を参照してください。メソッドパーサーは、個々のプロパティ(たとえば
findByLastnameIgnoreCase(…))または大文字と小文字の区別をサポートする型のすべてのプロパティ(通常はStringインスタンス -findByLastnameAndFirstnameAllIgnoreCase(…)など)のIgnoreCaseフラグの設定をサポートします。ケースの無視がサポートされているかどうかはストアによって異なるため、ストア固有のクエリメソッドについては、リファレンスドキュメントの関連セクションを参照してください。プロパティを参照するクエリメソッドに
OrderBy句を追加し、並べ替え方向(AscまたはDesc)を提供することにより、静的な順序を適用できます。動的な並べ替えをサポートするクエリメソッドを作成するには、"特別なパラメーター処理" を参照してください。
8.4.3. プロパティ式
前の例に示すように、プロパティ式は管理対象エンティティの直接プロパティのみを参照できます。クエリの作成時に、解析されたプロパティが管理対象ドメインクラスのプロパティであることをすでに確認しています。ただし、ネストされたプロパティを走査して制約を定義することもできます。次のメソッドシグネチャーを検討してください。
List<Person> findByAddressZipCode(ZipCode zipCode);
Person に ZipCode を含む Address があると仮定します。その場合、メソッドは x.address.zipCode プロパティトラバーサルを作成します。解決アルゴリズムは、パーツ全体(AddressZipCode)をプロパティとして解釈することから始まり、ドメインクラスでその名前(大文字でない)のプロパティをチェックします。アルゴリズムが成功すると、そのプロパティが使用されます。そうでない場合、アルゴリズムはキャメルケース部分のソースを右側から頭と尾に分割し、対応するプロパティ(この例では、AddressZip と Code)を見つけようとします。アルゴリズムがそのヘッドを持つプロパティを見つけると、テールを取得し、そこからツリーを構築し続け、今説明したメソッドでテールを分割します。最初の分割が一致しない場合、アルゴリズムは分割ポイントを左に移動し(Address、ZipCode)、続行します。
これはほとんどの場合に機能するはずですが、アルゴリズムが間違ったプロパティを選択する可能性があります。Person クラスにも addressZip プロパティがあるとします。アルゴリズムは最初の分割ラウンドですでに一致し、間違ったプロパティを選択して失敗します(addressZip の型にはおそらく code プロパティがないため)。
このあいまいさを解決するには、メソッド名内で _ を使用して、トラバーサルポイントを手動で定義します。メソッド名は次のようになります。
List<Person> findByAddress_ZipCode(ZipCode zipCode);
アンダースコア文字を予約文字として扱うため、標準の Java 命名規則に従うことを強くお勧めします(つまり、プロパティ名にアンダースコアを使用せず、代わりにキャメルケースを使用します)。
8.4.4. 特別なパラメーター処理
クエリでパラメーターを処理するには、前の例ですでに見たようにメソッドパラメーターを定義します。それに加えて、インフラストラクチャは Pageable や Sort などの特定の型を認識し、ページネーションとソートをクエリに動的に適用します。次の例は、これらの機能を示しています。
Pageable、Slice、Sort の使用 Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
Sort および Pageable を使用する API は、非 null 値がメソッドに渡されることを想定しています。並べ替えやページ付けを適用したくない場合は、Sort.unsorted() と Pageable.unpaged() を使用してください。 |
最初のメソッドでは、org.springframework.data.domain.Pageable インスタンスをクエリメソッドに渡して、静的に定義されたクエリにページングを動的に追加できます。Page は、使用可能な要素とページの総数を認識しています。これは、インフラストラクチャがカウントクエリをトリガーして全体の数を計算することによって行われます。これは(使用するストアによっては)高額になる可能性があるため、代わりに Slice を返すことができます。Slice は、次の Slice が使用可能かどうかのみを認識します。これは、より大きな結果セットをウォークスルーする場合に十分な場合があります。
並べ替えオプションも Pageable インスタンスを介して処理されます。並べ替えのみが必要な場合は、メソッドに org.springframework.data.domain.Sort パラメーターを追加します。ご覧のとおり、List を返すことも可能です。この場合、実際の Page インスタンスを構築するために必要な追加のメタデータは作成されません(つまり、必要だったはずの追加のカウントクエリは発行されません)。むしろ、指定された範囲のエンティティのみを検索するようにクエリを制限します。
| クエリ全体で取得するページ数を調べるには、追加のカウントクエリをトリガーする必要があります。デフォルトでは、このクエリは実際にトリガーするクエリから派生します。 |
ページングとソート
プロパティ名を使用して、簡単な並べ替え式を定義できます。式を連結して、複数の条件を 1 つの式に集めることができます。
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
ソート式を定義するためのより型安全な方法については、ソート式を定義する型から始め、メソッド参照を使用してソートするプロパティを定義します。
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
TypedSort.by(…) は、(通常)CGlib を使用してランタイムプロキシを利用します。これは、Graal VMNative などのツールを使用するときにネイティブイメージのコンパイルを妨げる可能性があります。 |
ストアの実装が Querydsl をサポートしている場合は、生成されたメタモデル型を使用して、並べ替え式を定義することもできます。
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
8.4.5. クエリ結果の制限
first または top キーワードを使用して、クエリメソッドの結果を制限できます。これらのキーワードは、同じ意味で使用できます。オプションの数値を top または first に追加して、返される最大結果サイズを指定できます。数値が省略されている場合、結果サイズは 1 と見なされます。次の例は、クエリサイズを制限する方法を示しています。
Top および First を使用したクエリの結果サイズの制限 User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
制限式は、個別のクエリをサポートするデータストアの Distinct キーワードもサポートします。また、結果セットを 1 つのインスタンスに制限するクエリの場合、結果を Optional キーワードでラップすることがサポートされています。
ページネーションまたはスライスが制限クエリページネーション(および使用可能なページ数の計算)に適用される場合、制限された結果内で適用されます。
Sort パラメーターを使用して動的ソートと組み合わせて結果を制限すると、"K" 最小エレメントと "K" 最大エレメントの照会メソッドを表現できます。 |
8.4.6. コレクションまたはイテラブルを返すリポジトリメソッド
複数の結果を返すクエリメソッドは、標準の Java Iterable、List、Set を使用できます。さらに、Spring Data の Streamable、Iterable のカスタム拡張、Vavr (英語) によって提供されるコレクション型を返すことをサポートします。考えられるすべてのクエリメソッドの戻り値の型について説明している付録を参照してください。
Streamable をクエリメソッドの戻り値の型として使用する
Iterable または任意のコレクション型の代わりに Streamable を使用できます。これは、非並列 Stream (Iterable にはない)にアクセスするための便利なメソッドと、要素を介して … .filter(…) および … .map(…) を直接アクセスし、Streamable を他の要素に連結する機能を提供します。
interface PersonRepository extends Repository<Person, Long> {
Streamable<Person> findByFirstnameContaining(String firstname);
Streamable<Person> findByLastnameContaining(String lastname);
}
Streamable<Person> result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
カスタムのストリーミング可能なラッパー型を返す
コレクション専用のラッパー型を提供することは、複数の要素を返すクエリ結果の API を提供するために一般的に使用されるパターンです。通常、これらの型は、コレクションのような型を返すリポジトリメソッドを呼び出し、ラッパー型のインスタンスを手動で作成することによって使用されます。Spring Data では、次の条件を満たす場合、これらのラッパー型をクエリメソッドの戻り値の型として使用できるため、この追加の手順を回避できます。
型は
Streamableを実装します。この型は、
Streamableを引数として取るof(…)またはvalueOf(…)という名前のコンストラクターまたは静的ファクトリメソッドのいずれかを公開します。
次のリストに例を示します。
class Product { (1)
MonetaryAmount getPrice() { … }
}
@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> { (2)
private final Streamable<Product> streamable;
public MonetaryAmount getTotal() { (3)
return streamable.stream()
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
@Override
public Iterator<Product> iterator() { (4)
return streamable.iterator();
}
}
interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text); (5)
}
| 1 | 製品の価格にアクセスするための API を公開する Product エンティティ。 |
| 2 | Products.of(…) (Lombok アノテーションを使用して作成されたファクトリメソッド)を使用して構築できる Streamable<Product> のラッパー型。Streamable<Product> を使用する標準のコンストラクターも同様に機能します。 |
| 3 | ラッパー型は追加の API を公開し、Streamable<Product> で新しい値を計算します。 |
| 4 | Streamable インターフェースを実装し、実際の結果に委譲します。 |
| 5 | そのラッパー型 Products は、クエリメソッドの戻り値の型として直接使用できます。Streamable<Product> を返し、リポジトリクライアントでクエリの後に手動でラップする必要はありません。 |
Vavr コレクションのサポート
Vavr (英語) は、Java の関数型プログラミングの概念を取り入れたライブラリです。次の表に示すように、クエリメソッドの戻り値の型として使用できるコレクション型のカスタムセットが付属しています。
| Vavr コレクション型 | 使用される Vavr 実装型 | 有効な Java ソース型 |
|---|---|---|
|
|
|
|
|
|
|
|
|
実際のクエリ結果の Java 型(3 番目の列)に応じて、最初の列の型(またはそのサブ型)をクエリメソッドの戻り値の型として使用し、実装型として使用される 2 番目の列の型を取得できます。または、Traversable (Vavr Iterable と同等)を宣言して、実際の戻り値から実装クラスを導出することもできます。つまり、java.util.List は Vavr List または Seq に変換され、java.util.Set は Vavr LinkedHashSet Set に変換されます。
8.4.7. リポジトリメソッドの null 処理
Spring Data 2.0 以降、個々の集約インスタンスを返すリポジトリ CRUD メソッドは、Java 8 の Optional を使用して、値が存在しない可能性があることを示します。さらに、Spring Data はクエリメソッドで次のラッパー型を返すことをサポートしています。
com.google.common.base.Optionalscala.Optionio.vavr.control.Option
または、クエリメソッドは、ラッパー型をまったく使用しないことを選択できます。null を返すことにより、クエリ結果がないことが示されます。コレクション、コレクションの代替、ラッパー、ストリームを返すリポジトリメソッドは、null ではなく、対応する空の表現を返すことが保証されています。詳細については、"リポジトリクエリの戻り値の型" を参照してください。
Nullability アノテーション
Spring Framework の nullability アノテーションを使用して、リポジトリメソッドの nullability 制約を表現できます。これらは、次のように、実行時にツールに優しいアプローチとオプトイン null チェックを提供します。
@NonNullApi(Javadoc) : パッケージレベルで使用され、パラメーターと戻り値のデフォルトの動作が、それぞれnull値を受け入れることも生成することもないことを宣言します。@NonNull(Javadoc) :nullであってはならないパラメーターまたは戻り値で使用されます(@NonNullApiが適用されるパラメーターおよび戻り値では不要です)。@Nullable(Javadoc) :nullの可能性があるパラメーターまたは戻り値で使用されます。
Spring アノテーションは、JSR 305 (英語) アノテーション(休止中ですが広く使用されている JSR)でメタアノテーションが付けられています。JSR 305 メタアノテーションにより、ツールベンダー(IDEA (英語) 、Eclipse (英語) 、Kotlin (英語) など)は、Spring アノテーションのサポートをハードコードすることなく、一般的な方法で null-safety サポートを提供できます。クエリメソッドの null 可能性制約のランタイムチェックを有効にするには、次の例に示すように、package-info.java で Spring の @NonNullApi を使用して、パッケージレベルで非 null 可能性をアクティブ化する必要があります。
package-info.java で非 null 可能性を宣言する @org.springframework.lang.NonNullApi
package com.acme;
null 以外のデフォルトが設定されると、リポジトリクエリメソッドの呼び出しは、実行時に null 可能性の制約について検証されます。クエリ結果が定義された制約に違反している場合、例外がスローされます。これは、メソッドが null を返すが、null 許容ではないと宣言されている場合に発生します(リポジトリが存在するパッケージで定義されたアノテーションのデフォルト)。null 許容の結果に再度オプトインする場合は、個々のメソッドで @Nullable を選択的に使用します。このセクションの冒頭で説明した結果ラッパー型を使用すると、引き続き期待どおりに機能します。空の結果は、不在を表す値に変換されます。
次の例は、今説明したいくつかの手法を示しています。
package com.acme; (1)
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress); (2)
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress); (3)
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); (4)
}
| 1 | リポジトリは、null 以外の動作を定義したパッケージ(またはサブパッケージ)にあります。 |
| 2 | クエリで結果が生成されない場合は、EmptyResultDataAccessException をスローします。メソッドに渡された emailAddress が null の場合、IllegalArgumentException をスローします。 |
| 3 | クエリが結果を生成しない場合、null を返します。emailAddress の値として null も受け入れます。 |
| 4 | クエリが結果を生成しない場合、Optional.empty() を返します。メソッドに渡された emailAddress が null の場合、IllegalArgumentException をスローします。 |
Kotlin ベースのリポジトリの Nullability
Kotlin には、言語に組み込まれた null 可能性制約 (英語) の定義があります。Kotlin コードはバイトコードにコンパイルされます。これは、メソッドシグネチャーではなく、コンパイルされたメタデータを通じて nullability 制約を表現しません。kotlin-reflect JAR をプロジェクトに含めて、Kotlin の nullability 制約のイントロスペクションを有効にしてください。Spring Data リポジトリは、言語メカニズムを使用してこれらの制約を定義し、次のように同じランタイムチェックを適用します。
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User (1)
fun findByFirstname(firstname: String?): User? (2)
}
| 1 | このメソッドは、パラメーターと結果の両方を null 不可(Kotlin のデフォルト)として定義します。Kotlin コンパイラーは、null をメソッドに渡すメソッド呼び出しを拒否します。クエリで空の結果が得られた場合、EmptyResultDataAccessException がスローされます。 |
| 2 | このメソッドは、firstname パラメーターに null を受け入れ、クエリで結果が生成されない場合は null を返します。 |
8.4.8. クエリ結果のストリーミング
戻り値の型として Java 8 Stream<T> を使用することにより、クエリメソッドの結果を段階的に処理できます。次の例に示すように、クエリ結果を Stream でラップする代わりに、データストア固有のメソッドを使用してストリーミングを実行します。
Stream<T> を使用したクエリの結果のストリーミング @Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
Stream は潜在的に基礎となるデータストア固有のリソースをラップするため、使用後に閉じる必要があります。次の例に示すように、close() メソッドを使用するか、Java 7 try-with-resources ブロックを使用して、Stream を手動で閉じることができます。 |
Stream<T> を操作すると、try-with-resources ブロックが生成されます try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
現在、すべての Spring Data モジュールが戻り型として Stream<T> をサポートしているわけではありません。 |
8.4.9. 非同期クエリ結果
Spring の非同期メソッド実行機能を使用すると、リポジトリクエリを非同期で実行できます。これは、Spring TaskExecutor に送信されたタスクで実際のクエリが発生している間、メソッドは呼び出し直後に戻ることを意味します。非同期クエリはリアクティブクエリとは異なるため、混在させないでください。リアクティブサポートの詳細については、ストア固有のドキュメントを参照してください。次の例は、いくつかの非同期クエリを示しています。
@Async
Future<User> findByFirstname(String firstname); (1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
| 1 | 戻り値の型として java.util.concurrent.Future を使用します。 |
| 2 | 戻り値の型として Java 8 java.util.concurrent.CompletableFuture を使用します。 |
8.5. リポジトリインスタンスの作成
このセクションでは、定義されたリポジトリインターフェースのインスタンスと Bean 定義を作成する方法について説明します。
8.5.1. Java 構成
Java 構成クラスでストア固有の @EnableJpaRepositories アノテーションを使用して、リポジトリのアクティブ化の構成を定義します。Spring コンテナーの Java ベースの構成の概要については、Spring リファレンスドキュメントの JavaConfig を参照してください。
Spring Data リポジトリを有効にするサンプル構成は次のようになります。
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
上記の例では、JPA 固有のアノテーションを使用しています。これは、実際に使用するストアモジュールに応じて変更します。同じことが EntityManagerFactory Bean の定義にも当てはまります。ストア固有の構成について説明しているセクションを参照してください。 |
8.5.2. フィルターの使用
デフォルトでは、インフラストラクチャは、構成された基本パッケージにある永続化テクノロジー固有の Repository サブインターフェースを継承するすべてのインターフェースを選択し、そのための Bean インスタンスを作成します。ただし、どのインターフェースに Bean インスタンスが作成されているかをより細かく制御したい場合があります。これを行うには、リポジトリ宣言内でフィルター要素を使用します。セマンティクスは、Spring のコンポーネントフィルターの要素とまったく同じです。詳細については、これらの要素の Spring リファレンスドキュメントを参照してください。
例: 特定のインターフェースをリポジトリ Bean としてインスタンス化から除外するには、次の構成を使用できます。
@Configuration
@EnableJpaRepositories(basePackages = "com.acme.repositories",
includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeRepository") },
excludeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeOtherRepository") })
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前の例では、SomeRepository で終わるすべてのインターフェースをインスタンス化から除外し、SomeOtherRepository で終わるインターフェースを含めます。
8.5.3. スタンドアロンの使用箇所
Spring コンテナーの外部(CDI 環境など)でリポジトリインフラストラクチャを使用することもできます。クラスパスにはまだいくつかの Spring ライブラリが必要ですが、通常は、プログラムでリポジトリを設定することもできます。リポジトリサポートを提供する Spring Data モジュールには、次のように使用できる永続化テクノロジ固有の RepositoryFactory が付属しています。
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
8.6. Spring Data リポジトリのカスタム実装
Spring Data は、コーディングをほとんど行わずにクエリメソッドを作成するためのさまざまなオプションを提供します。ただし、これらのオプションがニーズに合わない場合は、リポジトリメソッドの独自のカスタム実装を提供することもできます。このセクションでは、その方法について説明します。
8.6.1. 個々のリポジトリのカスタマイズ
カスタム機能でリポジトリを強化するには、最初に、次のように、フラグメントインターフェースとカスタム機能の実装を定義する必要があります。
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
フラグメントインターフェースに対応するクラス名の最も重要な部分は、Impl 後置です。 |
実装自体は Spring Data に依存せず、通常の Spring Bean にすることができます。そのため、標準の依存性注入動作を使用して、他の Bean(JdbcTemplate など)への参照を注入したり、アスペクトに参加したりすることができます。
次に、次のように、リポジトリインターフェースにフラグメントインターフェースを継承させることができます。
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
リポジトリインターフェースでフラグメントインターフェースを拡張すると、CRUD とカスタム機能が組み合わされ、クライアントで使用できるようになります。
Spring Data リポジトリは、リポジトリ構成を形成するフラグメントを使用して実装されます。フラグメントは、基本リポジトリ、機能面(QueryDsl など)、カスタムインターフェースとその実装です。リポジトリインターフェースにインターフェースを追加するたびに、フラグメントを追加して構成を強化します。ベースリポジトリとリポジトリアスペクトの実装は、各 Spring Data モジュールによって提供されます。
次の例は、カスタムインターフェースとその実装を示しています。
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
次の例は、CrudRepository を継承するカスタムリポジトリのインターフェースを示しています。
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
リポジトリは、宣言の順序でインポートされる複数のカスタム実装で構成されます。カスタム実装は、基本実装およびリポジトリの側面よりも優先度が高くなります。この順序付けにより、ベースリポジトリおよびアスペクトメソッドをオーバーライドし、2 つのフラグメントが同じメソッドシグネチャーを提供する場合のあいまいさを解決できます。リポジトリフラグメントは、単一のリポジトリインターフェースでの使用に限定されません。複数のリポジトリがフラグメントインターフェースを使用し、異なるリポジトリでカスタマイズを再利用できる場合があります。
次の例は、リポジトリフラグメントとその実装を示しています。
save(…) をオーバーライドするフラグメント interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
public <S extends T> S save(S entity) {
// Your custom implementation
}
}
次の例は、前述のリポジトリフラグメントを使用するリポジトリを示しています。
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
構成
リポジトリインフラストラクチャは、リポジトリが見つかったパッケージのクラスをスキャンして、カスタム実装フラグメントを自動検出しようとします。これらのクラスは、デフォルトで Impl になる接尾辞を追加するという命名規則に従う必要があります。
次の例は、デフォルトの接尾辞を使用するリポジトリと、接尾辞のカスタム値を設定するリポジトリを示しています。
@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
前の例の最初の構成は、カスタムリポジトリ実装として機能する com.acme.repository.CustomizedUserRepositoryImpl というクラスを検索しようとします。2 番目の例では、com.acme.repository.CustomizedUserRepositoryMyPostfix を検索しようとします。
あいまいさの解決
一致するクラス名を持つ複数の実装が異なるパッケージで見つかった場合、Spring Data は Bean 名を使用して、使用する実装を識別します。
前に示した CustomizedUserRepository の次の 2 つのカスタム実装を考えると、最初の実装が使用されます。その Bean 名は customizedUserRepositoryImpl であり、これはフラグメントインターフェース(CustomizedUserRepository)の名前と接尾辞 Impl に一致します。
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
UserRepository インターフェースに @Component("specialCustom") でアノテーションを付けると、Bean 名に Impl を加えたものが、com.acme.impl.two のリポジトリ実装用に定義されたものと一致し、最初のものの代わりに使用されます。
手動接続
カスタム実装でアノテーションベースの構成とオートワイヤーのみを使用する場合、上記のアプローチは他の Spring Bean と同様に処理されるため、上手く機能します。実装フラグメント Bean に特別な接続が必要な場合、Bean を宣言し、前のセクションで説明した規則に従って名前を付けることができます。インフラストラクチャは、Bean 定義を手動で作成する代わりに、名前で手動で定義したものを参照します。次の例は、カスタム実装を手動で接続する方法を示しています。
class MyClass {
MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {
…
}
}
8.6.2. ベースリポジトリをカスタマイズする
前のセクションで説明したアプローチでは、ベースリポジトリの動作をカスタマイズしてすべてのリポジトリが影響を受けるようにする場合、各リポジトリインターフェースをカスタマイズする必要があります。代わりに、すべてのリポジトリの動作を変更するために、永続化テクノロジ固有のリポジトリベースクラスを継承する実装を作成できます。このクラスは、次の例に示すように、リポジトリプロキシのカスタムベースクラスとして機能します。
class MyRepositoryImpl<T, ID>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
@Transactional
public <S extends T> S save(S entity) {
// implementation goes here
}
}
このクラスには、ストア固有のリポジトリファクトリ実装が使用するスーパークラスのコンストラクターが必要です。リポジトリの基本クラスに複数のコンストラクターがある場合は、EntityInformation とストア固有のインフラストラクチャオブジェクト(EntityManager またはテンプレートクラスなど)を取得するコンストラクターをオーバーライドします。 |
最後のステップは、カスタマイズされたリポジトリ基本クラスを Spring Data インフラストラクチャに認識させることです。構成では、次の例に示すように、repositoryBaseClass を使用してこれを行うことができます。
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
8.7. 集約ルートからのイベントの公開
リポジトリによって管理されるエンティティは、集約ルートです。ドメイン駆動設計アプリケーションでは、これらの集約ルートは通常、ドメインイベントを発行します。Spring Data は、@DomainEvents と呼ばれるアノテーションを提供します。これは、次の例に示すように、集約パブリケーションのメソッドで使用して、その公開をできるだけ簡単にすることができます。
class AnAggregateRoot {
@DomainEvents (1)
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication (2)
void callbackMethod() {
// … potentially clean up domain events list
}
}
| 1 | @DomainEvents を使用するメソッドは、単一のイベントインスタンスまたはイベントのコレクションのいずれかを返すことができます。引数を取ってはいけません。 |
| 2 | すべてのイベントが公開された後、@AfterDomainEventPublication でアノテーションが付けられたメソッドがあります。これを使用して、公開するイベントのリストを(他の用途の中でも)潜在的にクリーンアップできます。 |
メソッドは、Spring Data リポジトリの save(…)、saveAll(…)、delete(…) または deleteAll(…) メソッドのいずれかが呼び出されるたびに呼び出されます。
8.8. Spring Data 拡張
このセクションでは、さまざまなコンテキストで Spring Data を使用できるようにする一連の Spring Data 拡張について説明します。現在、ほとんどの統合は Spring MVC を対象としています。
8.8.1. Querydsl 拡張
Querydsl (英語) は、流れるような API を使用して、静的に型指定された SQL のようなクエリの構築を可能にするフレームワークです。
次の例に示すように、いくつかの Spring Data モジュールは、QuerydslPredicateExecutor を介して Querydsl との統合を提供します。
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … more functionality omitted.
}
| 1 | Predicate に一致する単一のエンティティを検索して返します。 |
| 2 | Predicate に一致するすべてのエンティティを検索して返します。 |
| 3 | Predicate に一致するエンティティの数を返します。 |
| 4 | Predicate に一致するエンティティが存在するかどうかを返します。 |
Querydsl サポートを使用するには、次の例に示すように、リポジトリインターフェースで QuerydslPredicateExecutor を継承します。
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
前の例では、次の例に示すように、Querydsl Predicate インスタンスを使用して型安全なクエリを記述できます。
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
8.8.2. Web サポート
リポジトリプログラミングモデルをサポートする Spring Data モジュールには、さまざまな Web サポートが付属しています。Web 関連のコンポーネントでは、Spring MVC JAR がクラスパス上にある必要があります。それらのいくつかは、Spring HATEOAS [GitHub] (英語) との統合さえ提供します。一般に、統合サポートは、次の例に示すように、JavaConfig 構成クラスで @EnableSpringDataWebSupport アノテーションを使用することで有効になります。
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />@EnableSpringDataWebSupport アノテーションは、いくつかのコンポーネントを登録します。これらについては、このセクションの後半で説明します。また、クラスパスで Spring HATEOAS を検出し、統合コンポーネント(存在する場合)も登録します。
基本的な Web サポート
前のセクションで示した構成は、いくつかの基本的なコンポーネントを登録します。
Spring MVC がリクエストパラメーターまたはパス変数からリポジトリ管理ドメインクラスのインスタンスを解決できるようにする
DomainClassConverterクラスの使用。Spring MVC がリクエストパラメーターから
PageableおよびSortインスタンスを解決できるようにするHandlerMethodArgumentResolver実装。Jackson モジュールは、使用する Spring Data モジュールに応じて、
PointやDistanceなどの型を逆 / 直列化するか、特定の型を格納します。
DomainClassConverter クラスの使用
DomainClassConverter クラスを使用すると、Spring MVC コントローラーメソッドシグネチャーでドメイン型を直接使用できるため、次の例に示すように、リポジトリからインスタンスを手動で検索する必要がありません。
@Controller
@RequestMapping("/users")
class UserController {
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
このメソッドは User インスタンスを直接受け取り、それ以上のルックアップは必要ありません。インスタンスは、Spring MVC が最初にパス変数をドメインクラスの id 型に変換し、最終的にドメイン型に登録されたリポジトリインスタンスで findById(…) を呼び出してインスタンスにアクセスすることで解決できます。
現在、リポジトリは変換のために発見される資格があるために CrudRepository を実装しなければなりません。 |
ページング可能およびソート用の HandlerMethodArgumentResolvers
前のセクションで示した構成スニペットは、PageableHandlerMethodArgumentResolver と SortHandlerMethodArgumentResolver のインスタンスも登録します。次の例に示すように、登録により、Pageable および Sort が有効なコントローラーメソッド引数として有効になります。
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
上記のメソッドシグネチャーにより、Spring MVC は、次のデフォルト構成を使用して、リクエストパラメーターから Pageable インスタンスを派生させようとします。
| 取得するページ。0 からインデックス付けされ、デフォルトは 0 です。 |
| 取得するページのサイズ。デフォルトは 20 です。 |
|
|
この動作をカスタマイズするには、PageableHandlerMethodArgumentResolverCustomizer インターフェースまたは SortHandlerMethodArgumentResolverCustomizer インターフェースをそれぞれ実装する Bean を登録します。次の例に示すように、customize() メソッドが呼び出され、設定を変更できます。
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
既存の MethodArgumentResolver のプロパティを設定するだけでは目的に合わない場合は、SpringDataWebConfiguration または HATEOAS 対応の拡張機能を継承し、pageableResolver() または sortResolver() メソッドをオーバーライドし、@Enable アノテーションを使用する代わりにカスタマイズした構成ファイルをインポートします。
リクエストから複数の Pageable または Sort インスタンスを解決する必要がある場合(たとえば、複数のテーブルの場合)、Spring の @Qualifier アノテーションを使用して互いに区別できます。次に、リクエストパラメーターの前に ${qualifier}_ を付ける必要があります。次の例は、結果のメソッドシグネチャーを示しています。
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
thing1_page、thing2_page などを設定する必要があります。
メソッドに渡されるデフォルトの Pageable は PageRequest.of(0, 20) と同等ですが、Pageable パラメーターの @PageableDefault アノテーションを使用してカスタマイズできます。
Pageable のハイパーメディアサポート
Spring HATEOAS には、表現モデルクラス(PagedResources)が付属しています。これにより、Page インスタンスのコンテンツを、必要な Page メタデータと、クライアントがページを簡単にナビゲートできるようにするリンクで強化できます。Page から PagedResources への変換は、PagedResourcesAssembler と呼ばれる Spring HATEOAS ResourceAssembler インターフェースの実装によって行われます。次の例は、PagedResourcesAssembler をコントローラーメソッドの引数として使用する方法を示しています。
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
前の例に示すように、構成を有効にすると、PagedResourcesAssembler をコントローラーメソッドの引数として使用できます。その上で toResources(…) を呼び出すと、次の効果があります。
Pageのコンテンツは、PagedResourcesインスタンスのコンテンツになります。PagedResourcesオブジェクトはPageMetadataインスタンスをアタッチし、Pageおよび基礎となるPageRequestからの情報が取り込まれます。PagedResourcesには、ページの状態に応じて、prevおよびnextリンクが添付される場合があります。リンクは、メソッドがマップする URI を指します。メソッドに追加されたページネーションパラメーターは、PageableHandlerMethodArgumentResolverの設定と一致して、リンクを後で解決できるようにします。
データベースに 30 個の Person インスタンスがあると仮定します。これで、リクエスト(GET http://localhost:8080/persons)をトリガーして、次のような出力を確認できます。
{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
} アセンブラーは正しい URI を生成し、デフォルト構成を選択して、パラメーターを次のリクエストの Pageable に解決しました。つまり、その構成を変更すると、リンクは自動的に変更に準拠します。デフォルトでは、アセンブラーはそれが呼び出されたコントローラーメソッドを指しますが、ページネーションリンクを構築するためのベースとして使用されるカスタム Link を渡すことにより、それをカスタマイズできます。これにより、PagedResourcesAssembler.toResource(…) メソッドがオーバーロードされます。
Spring Data Jackson モジュール
コアモジュール、および一部のストア固有のモジュールには、Spring Data ドメインで使用される org.springframework.data.geo.Distance や org.springframework.data.geo.Point などの型の Jackson モジュールのセットが付属しています。
これらのモジュールは、Web サポートが有効になり、com.fasterxml.jackson.databind.ObjectMapper が使用可能になるとインポートされます。
初期化中に、SpringDataJacksonConfiguration と同様に SpringDataJacksonModules がインフラストラクチャによって取得されるため、宣言された com.fasterxml.jackson.databind.Module が Jackson ObjectMapper で使用できるようになります。
次のドメイン型のデータバインディングミックスインは、共通のインフラストラクチャによって登録されます。
org.springframework.data.geo.Distance org.springframework.data.geo.Point org.springframework.data.geo.Box org.springframework.data.geo.Circle org.springframework.data.geo.Polygon
個々のモジュールは、追加の |
Web データバインディングのサポート
次の例に示すように、Spring Data 射影(射影で説明)を使用して、JSONPath (英語) 式(Jayway JsonPath [GitHub] (英語) が必要)または XPath [W3C] (英語) 式(XmlBeam (英語) が必要)のいずれかを使用して、受信リクエストペイロードをバインドできます。
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
前の例に示されている型は、Spring MVC ハンドラーメソッドの引数として使用するか、RestTemplate のいずれかのメソッドで ParameterizedTypeReference を使用することで使用できます。上記のメソッド宣言は、指定されたドキュメント内の任意の場所で firstname を見つけようとします。lastname XML ルックアップは、受信ドキュメントのトップレベルで実行されます。の JSON バリアントは、トップレベルの lastname を最初に試行しますが、前者が値を返さない場合は、user サブドキュメントにネストされた lastname も試行します。こうすることで、クライアントが公開メソッドを呼び出さなくても、ソースドキュメントの構造の変更を簡単に軽減できます (通常、クラスベースのペイロードバインディングの欠点です)。
ネストされた射影は、射影に従ってサポートされます。メソッドがインターフェース以外の複雑な型を返す場合、Jackson ObjectMapper が最終値のマッピングに使用されます。
Spring MVC の場合、@EnableSpringDataWebSupport がアクティブになり、必要な依存関係がクラスパスで使用可能になるとすぐに、必要なコンバーターが自動的に登録されます。RestTemplate で使用する場合は、手動で ProjectingJackson2HttpMessageConverter (JSON) または XmlBeamHttpMessageConverter を登録します。
詳細については、標準の Spring Data サンプルリポジトリ [GitHub] (英語) の Web 射影の例 [GitHub] (英語) を参照してください。
Querydsl Web サポート
QueryDSL (英語) が統合されているストアの場合、Request クエリ文字列に含まれている属性からクエリを派生させることができます。
次のクエリ文字列を検討してください。
?firstname=Dave&lastname=Matthews 前の例の User オブジェクトが与えられた場合、次のように QuerydslPredicateArgumentResolver を使用して、クエリ文字列を次の値に解決できます。
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))Querydsl がクラスパスで見つかると、この機能は @EnableSpringDataWebSupport とともに自動的に有効になります。 |
メソッドシグネチャーに @QuerydslPredicate を追加すると、すぐに使用できる Predicate が提供されます。これは、QuerydslPredicateExecutor を使用して実行できます。
型情報は通常、メソッドの戻り値型から解決されます。その情報は必ずしもドメイン型と一致しないため、QuerydslPredicate の root 属性を使用することをお勧めします。 |
次の例は、メソッドシグネチャーで @QuerydslPredicate を使用する方法を示しています。
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
| 1 | User の一致する Predicate にクエリ文字列引数を解決します。 |
デフォルトのバインディングは次のとおりです。
eqとしての単純なプロパティのObject。containsのようなプロパティのようなコレクションのObject。inとしての単純なプロパティのCollection。
これらのバインディングは、@QuerydslPredicate の bindings 属性を使用するか、Java 8 default methods を使用して、次のようにリポジトリインターフェースに QuerydslBinderCustomizer メソッドを追加することによってカスタマイズできます。
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
| 1 | QuerydslPredicateExecutor は、Predicate の特定のファインダーメソッドへのアクセスを提供します。 |
| 2 | リポジトリインターフェースで定義された QuerydslBinderCustomizer が自動的に選択され、ショートカット @QuerydslPredicate(bindings=…) が選択されます。 |
| 3 | username プロパティのバインディングを単純な contains バインディングとして定義します。 |
| 4 | String プロパティのデフォルトのバインディングを、大文字と小文字を区別しない contains 一致になるように定義します。 |
| 5 | password プロパティを Predicate 解決から除外します。 |
リポジトリまたは @QuerydslPredicate から特定のバインディングを適用する前に、デフォルトの Querydsl バインディングを保持する QuerydslBinderCustomizerDefaults Bean を登録できます。 |
リファレンスドキュメント
9. JDBC リポジトリ
この章では、JDBC のリポジトリサポートの専門分野について説明します。これは、Spring Data リポジトリの操作で説明されているコアリポジトリサポートに基づいています。そこで説明されている基本的な概念をしっかりと理解している必要があります。
9.1. なぜ Spring Data JDBC なのでしょうか?
Java の世界におけるリレーショナルデータベースの主な永続化 API は、確かに JPA であり、独自の Spring Data モジュールを持っています。なぜ別のものがあるのでしょうか?
JPA は、開発者を支援するために多くのことを行います。とりわけ、エンティティへの変更を追跡します。遅延読み込みを行います。さまざまなオブジェクト構造を、同等の幅広いデータベース設計にマッピングできます。
これはすばらしいことで、多くのことが非常に簡単になります。基本的な JPA チュートリアルを参照してください。しかし、JPA が特定のことを行う理由については、しばしば混乱を招きます。また、JPA では概念的に非常に単純なことがかなり難しくなります。
Spring Data JDBC は、以下の設計上の決定を受け入れることにより、概念的にはるかにシンプルになることを目指しています。
エンティティをロードすると、SQL ステートメントが実行されます。これが完了すると、完全に読み込まれたエンティティができます。遅延読み込みやキャッシュは行われません。
エンティティを保存すると保存されます。保存しない場合は保存されません。ダーティトラッキングもセッションもありません。
エンティティをテーブルにマッピングする方法の簡単なモデルがあります。おそらく、かなり単純な場合にのみ機能します。気に入らない場合は、独自の戦略をコーディングする必要があります。Spring Data JDBC は、アノテーションを使用して戦略をカスタマイズするための非常に限られたサポートのみを提供します。
9.2. ドメイン駆動設計およびリレーショナルデータベース。
すべての Spring Data モジュールは、ドメイン駆動設計の「リポジトリ」、「集約」、「集約ルート」の概念に触発されています。これらは、Spring Data JDBC にとってさらに重要な可能性があります。これは、リレーショナルデータベースを操作する際の通常の慣行にある程度反するからです。
集約は、アトミックな変更間で一貫性が保証されるエンティティのグループです。典型的な例は、Order と OrderItems です。Order のプロパティ(たとえば、numberOfItems は OrderItems の実際の数と一貫性があります)は、変更が加えられても一貫性を保ちます。
集合体全体の参照は常に一貫しているとは限りません。最終的に一貫性が保証されます。
各集約には、集約のエンティティの 1 つである集約ルートが 1 つだけあります。集約は、その集約ルートのメソッドを介してのみ操作されます。これらは、前述のアトミックな変更です。
リポジトリは、特定の型のすべての集約のコレクションのように見える永続ストアの抽象化です。一般的に、Spring Data の場合、これは集約ルートごとに 1 つの Repository が必要であることを意味します。さらに、Spring Data JDBC の場合、これは、集約ルートから到達可能なすべてのエンティティがその集約ルートの一部と見なされることを意味します。Spring Data JDBC は、集約のみが集約の非ルートエンティティを格納するテーブルへの外部キーを持ち、他のエンティティが非ルートエンティティを指すことはないと想定しています。
| 現在の実装では、集約ルートから参照されるエンティティは削除され、Spring Data JDBC によって再作成されます。 |
データベースの作業および設計のスタイルに一致する実装で、リポジトリメソッドを上書きできます。
9.3. 入門
作業環境をブートストラップ設定する簡単な方法は、Pleiades All in One (JDK, STS, Lombok 付属) 、Spring Tools (英語) または Spring Initializr から Spring ベースのプロジェクトを作成することです。
まず、実行中のデータベースサーバーをセットアップする必要があります。JDBC アクセス用にデータベースを構成する方法については、ベンダーのドキュメントを参照してください。
STS で Spring プロジェクトを作成するには:
ファイル→新規→ Spring テンプレートプロジェクト→シンプル Spring ユーティリティプロジェクトに移動し、プロンプトが表示されたらはいを押します。次に、プロジェクトとパッケージ名(
org.spring.jdbc.exampleなど)を入力します。以下を
pom.xmlファイルのdependencies要素に追加します。<dependencies> <!-- other dependency elements omitted --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jdbc</artifactId> <version>3.0.6</version> </dependency> </dependencies>pom.xml の Spring のバージョンを次のように変更します
<spring.framework.version>6.0.9</spring.framework.version>次の Maven の Spring マイルストーンリポジトリの場所を
pom.xmlに追加して、<dependencies/>要素と同じレベルになるようにします。<repositories> <repository> <id>spring-milestone</id> <name>Spring Maven MILESTONE Repository</name> <url>https://repo.spring.io/libs-milestone</url> </repository> </repositories>
リポジトリも参照できます (英語) 。
9.4. サンプルリポジトリ
GitHub リポジトリといくつかの例 (英語) をダウンロードして試して、ライブラリの動作を確認してください。
9.5. アノテーションベースの構成
次の例に示すように、Spring Data JDBC リポジトリのサポートは、Java 構成を介したアノテーションによってアクティブにできます。
@Configuration
@EnableJdbcRepositories (1)
class ApplicationConfig extends AbstractJdbcConfiguration { (2)
@Bean
DataSource dataSource() { (3)
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource) { (4)
return new NamedParameterJdbcTemplate(dataSource);
}
@Bean
TransactionManager transactionManager(DataSource dataSource) { (5)
return new DataSourceTransactionManager(dataSource);
}
}
| 1 | @EnableJdbcRepositories は、Repository から派生したインターフェースの実装を作成します |
| 2 | AbstractJdbcConfiguration は、Spring Data JDBC に必要なさまざまなデフォルト Bean を提供します |
| 3 | データベースに接続する DataSource を作成します。これは、次の 2 つの Bean メソッドで必要です。 |
| 4 | Spring Data JDBC がデータベースにアクセスするために使用する NamedParameterJdbcOperations を作成します。 |
| 5 | Spring Data JDBC は、Spring JDBC によって提供されるトランザクション管理を利用します。 |
前の例の構成クラスは、spring-jdbc の EmbeddedDatabaseBuilder API を使用して、組み込み HSQL データベースをセットアップします。次に、DataSource を使用して NamedParameterJdbcOperations と TransactionManager をセットアップします。@EnableJdbcRepositories を使用して、最終的に Spring Data JDBC リポジトリをアクティブ化します。基本パッケージが構成されていない場合は、構成クラスが存在するパッケージを使用します。AbstractJdbcConfiguration を拡張すると、さまざまな Bean が確実に登録されます。メソッドを上書きすると、セットアップをカスタマイズできます(以下を参照)。
この構成は、Spring Boot を使用してさらに簡略化できます。Spring Boot では、スターター spring-boot-starter-data-jdbc が依存関係に含まれていれば、DataSource で十分です。他のすべては Spring Boot によって行われます。
このセットアップでカスタマイズしたいことがいくつかあります。
9.5.1. ダイアレクト
Spring Data JDBC は、インターフェース Dialect の実装を使用して、データベースまたはその JDBC ドライバーに固有の動作をカプセル化します。デフォルトでは、AbstractJdbcConfiguration は使用中のデータベースを判別し、正しい Dialect を登録しようとします。この動作は、jdbcDialect(NamedParameterJdbcOperations) を上書きすることで変更できます。
ダイアレクトがないデータベースを使用している場合、アプリケーションは起動しません。その場合、Dialect 実装を提供するようベンダーに依頼する必要があります。または、次のこともできます。
独自の
Dialectを実装します。Dialectを返すJdbcDialectProviderを実装します。META-INFにspring.factoriesリソースを作成してプロバイダーを登録し、行を追加して登録を実行します
org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=<fully qualified name of your JdbcDialectProvider>
9.6. 永続化エンティティ
CrudRepository.save(…) メソッドを使用して、集約を保存できます。集約が新しい場合、集約ルートの挿入が発生し、その後に直接または間接的に参照されるすべてのエンティティの挿入ステートメントが続きます。
集約ルートが新規ではない場合、すべての参照エンティティが削除され、集約ルートが更新され、すべての参照エンティティが再度挿入されます。インスタンスが新しいかどうかは、インスタンスの状態の一部であることに注意してください。
| このアプローチには、明らかな欠点がいくつかあります。参照されたエンティティのうち実際に変更されたものがわずかしかない場合、削除と挿入は無駄です。このプロセスは改善される可能性があり、おそらく改善される予定ですが、Spring Data JDBC が提供できるものには特定の制限があります。集約の以前の状態はわかりません。そのため、更新プロセスは常にデータベースで見つかったものをすべて取得し、save メソッドに渡されたエンティティの状態に変換する必要があります。 |
9.6.1. オブジェクトマッピングの基礎
このセクションでは、Spring Data オブジェクトマッピング、オブジェクト作成、フィールドとプロパティへのアクセス、可変性と不変性の基礎について説明します。このセクションは、基になるデータストア(JPA など)のオブジェクトマッピングを使用しない Spring Data モジュールにのみ適用されることに注意してください。また、インデックス、列名やフィールド名のカスタマイズなど、ストア固有のオブジェクトマッピングについては、ストア固有のセクションを参照してください。
Spring Data オブジェクトマッピングの中心的なロールは、ドメインオブジェクトのインスタンスを作成し、ストアネイティブデータ構造をそれらにマッピングすることです。つまり、2 つの基本的な手順が必要です。
公開されたコンストラクターの 1 つを使用したインスタンスの作成。
すべての公開されたプロパティを具体化するインスタンスの設定。
オブジェクト作成
Spring Data は、その型のオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。
@PersistenceCreatorでアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。コンストラクターが 1 つしかない場合は、それが使用されます。
複数のコンストラクターがあり、そのうちの 1 つだけに
@PersistenceCreatorアノテーションが付けられている場合は、それが使用されます。型が Java
Recordの場合、標準コンストラクターが使用されます。引数のないコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。
値の解決では、コンストラクター / ファクトリメソッドの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含め、プロパティが入力されたかのように解決が実行されます。これには、クラスファイルで利用可能なパラメーター名情報、またはコンストラクターに存在する @ConstructorProperties アノテーションも必要です。
値の解決は、ストア固有の SpEL 式を使用した Spring Framework の @Value 値アノテーションを使用してカスタマイズできます。詳細については、ストア固有のマッピングに関するセクションを参照してください。
プロパティ設定
エンティティのインスタンスが作成されると、Spring Data はそのクラスの残りのすべての永続プロパティを設定します。エンティティのコンストラクターによってすでに入力されていない場合(つまり、コンストラクターの引数リストを介して使用される場合)、ID プロパティが最初に入力され、循環オブジェクト参照の解決が可能になります。その後、コンストラクターによってまだ設定されていないすべての非一時的なプロパティがエンティティインスタンスに設定されます。そのために、次のアルゴリズムを使用します。
プロパティが不変であるが
with …メソッドを公開している場合(以下を参照)、with …メソッドを使用して、新しいプロパティ値を持つ新しいエンティティインスタンスを作成します。プロパティアクセス(つまり、getter および setter を介したアクセス)が定義されている場合、setter メソッドを呼び出しています。
プロパティが変更可能な場合、フィールドを直接設定します。
プロパティが不変の場合、永続化操作(オブジェクト作成を参照)で使用されるコンストラクターを使用して、インスタンスのコピーを作成します。
デフォルトでは、フィールド値を直接設定します。
次のエンティティを見てみましょう。
class Person {
private final @Id Long id; (1)
private final String firstname, lastname; (2)
private final LocalDate birthday;
private final int age; (3)
private String comment; (4)
private @AccessType(Type.PROPERTY) String remarks; (5)
static Person of(String firstname, String lastname, LocalDate birthday) { (6)
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) { (1)
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}
void setRemarks(String remarks) { (5)
this.remarks = remarks;
}
}
| 1 | identifier プロパティは final ですが、コンストラクターで null に設定されます。クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。元の Person インスタンスは、新しいインスタンスが作成されるときに変更されません。通常、ストア管理される他のプロパティにも同じパターンが適用されますが、永続化操作のために変更する必要がある場合があります。永続化コンストラクター(6 を参照)は事実上コピーコンストラクターであり、プロパティの設定は新しい識別子値が適用された新しいインスタンスの作成に変換されるため、wither メソッドはオプションです。 |
| 2 | firstname および lastname プロパティは、getter を介して潜在的に公開される通常の不変のプロパティです。 |
| 3 | age プロパティは不変ですが、birthday プロパティから派生しています。示されている設計では、Spring Data は宣言された唯一のコンストラクターを使用するため、データベース値はデフォルト設定よりも優先されます。計算が優先されることを意図している場合でも、このコンストラクターがパラメーターとして age を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは age フィールドを設定しようとし、不変で no with … メソッドが存在します。 |
| 4 | comment プロパティは変更可能で、そのフィールドを直接設定することによって入力されます。 |
| 5 | remarks プロパティは変更可能で、setter メソッドを呼び出すことによって設定されます。 |
| 6 | このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの中心的な考え方は、@PersistenceCreator によるコンストラクターの曖昧性解消の必要性を回避するために、追加のコンストラクターの代わりにファクトリメソッドを使用することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。Spring Data でオブジェクトのインスタンス化にファクトリメソッドを使用する場合は、@PersistenceCreator でアノテーションを付けます。 |
一般的な推奨事項
不変オブジェクトにこだわる — 不変オブジェクトは、オブジェクトを具体化するのはコンストラクターのみを呼び出すだけなので、簡単に作成できます。また、これにより、クライアントオブジェクトがオブジェクトの状態を操作できるようにする setter メソッドがドメインオブジェクトに散らばるのを防ぎます。それらが必要な場合は、同じ場所に配置された限られた型でのみ呼び出せるように、パッケージを保護することをお勧めします。コンストラクターのみの実体化は、プロパティの設定よりも最大 30% 高速です。
all-args コンストラクターを提供する — エンティティを不変の値としてモデル化できない、またはしたくない場合でも、オブジェクトのマッピングがプロパティの設定をスキップできるため、エンティティのすべてのプロパティを引数として取るコンストラクターを提供することには価値があります。最適なパフォーマンスのため。
@PersistenceCreatorを回避するために、オーバーロードされたコンストラクターの代わりにファクトリメソッドを使用します — 最適なパフォーマンスに必要なすべての引数コンストラクターでは、通常、自動生成識別子などを省略したアプリケーションユースケース固有のコンストラクターを公開します。これらの all-args コンストラクターのバリアントを公開する静的ファクトリメソッド。生成されたインスタンス生成クラスとプロパティアクセッサクラスを使用できるようにする制約を必ず守ってください。
生成される識別子については、すべての引数の永続化コンストラクター(推奨)または
with …メソッドと組み合わせて final フィールドを使用しますLombok を使用してボイラープレートコードを回避します — 永続化操作は通常、すべての引数を取るコンストラクターを必要とするため、その宣言はフィールド割り当てに対するボイラープレートパラメーターの退屈な繰り返しとなりますが、Lombok の
@AllArgsConstructorを使用することで回避することができます。
プロパティのオーバーライド
Java を使用すると、ドメインクラスを柔軟に設計できます。この場合、サブクラスは、スーパークラスで同じ名前ですでに宣言されているプロパティを定義できます。次の例を考えてみましょう。
public class SuperType {
private CharSequence field;
public SuperType(CharSequence field) {
this.field = field;
}
public CharSequence getField() {
return this.field;
}
public void setField(CharSequence field) {
this.field = field;
}
}
public class SubType extends SuperType {
private String field;
public SubType(String field) {
super(field);
this.field = field;
}
@Override
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
// optional
super.setField(field);
}
}
どちらのクラスも、割り当て可能な型を使用して field を定義します。ただし、SubType は SuperType.field をシャドウします。クラスの設計によっては、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチである可能性があります。または、setter で super.setField(…) を呼び出すと、SuperType で field を設定できます。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、これらすべてのメカニズムはある程度の競合を引き起こします。Spring Data は、型が割り当て可能でない場合、スーパー型のプロパティをスキップします。つまり、オーバーライドされたプロパティの型は、オーバーライドとして登録されるスーパー型のプロパティ型に割り当て可能である必要があります。そうでない場合、スーパー型のプロパティは一時的なものと見なされます。通常、個別のプロパティ名を使用することをお勧めします。
Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。
どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに
@Transientアノテーションを付けることで、プロパティを除外できます。データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。
@AccessType(PROPERTY)を使用することは、通常、setter 実装のさらなる仮定を行わずにスーパープロパティを設定することができないため、使用できません。
Kotlin サポート
Spring Data は、Kotlin の仕様を適合させて、オブジェクトの作成と変更を可能にします。
Kotlin オブジェクトの作成
Kotlin クラスはインスタンス化がサポートされています。すべてのクラスはデフォルトで不変であり、変更可能なプロパティを定義するには明示的なプロパティ宣言が必要です。
Spring Data は、その型のオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。
@PersistenceCreatorでアノテーションが付けられたコンストラクターがある場合は、それが使用されます。型が Kotlin データクラスの場合、プライマリコンストラクターが使用されます。
@PersistenceCreatorでアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。コンストラクターが 1 つしかない場合は、それが使用されます。
複数のコンストラクターがあり、そのうちの 1 つだけに
@PersistenceCreatorアノテーションが付けられている場合は、それが使用されます。型が Java
Recordの場合、標準コンストラクターが使用されます。引数のないコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。
次の data クラス Person を検討してください。
data class Person(val id: String, val name: String)
上記のクラスは、明示的なコンストラクターを持つ典型的なクラスにコンパイルされます。別のコンストラクターを追加してこのクラスをカスタマイズし、@PersistenceCreator でアノテーションを付けてコンストラクターの設定を示します。
data class Person(var id: String, val name: String) {
@PersistenceCreator
constructor(id: String) : this(id, "unknown")
}
Kotlin は、パラメーターが提供されない場合にデフォルト値を使用できるようにすることで、パラメーターのオプションをサポートしています。Spring Data がパラメーターのデフォルト設定を持つコンストラクターを検出した場合、データストアが値を提供しない(または単に null を返す)場合、Kotlin はパラメーターのデフォルト設定を適用できるため、これらのパラメーターは存在しません。name のパラメーターのデフォルト設定を適用する次のクラスを検討してください。
data class Person(var id: String, val name: String = "unknown")
name パラメーターが結果の一部ではないか、その値が null であるたびに、name は unknown にデフォルト設定されます。
Kotlin データクラスのプロパティ設定
Kotlin では、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の data クラス Person を検討してください。
data class Person(val id: String, val name: String)
このクラスは事実上不変です。Kotlin が既存のオブジェクトからすべてのプロパティ値をコピーしてメソッドに引数として提供されたプロパティ値を適用する新しいオブジェクトインスタンスを作成する copy(…) メソッドを生成するときに、新しいインスタンスを作成できます。
Kotlin オーバーライドプロパティ
Kotlin では、プロパティのオーバーライド (英語) を宣言して、サブクラスのプロパティを変更できます。
open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
このような配置では、field という名前の 2 つのプロパティがレンダリングされます。Kotlin は、各クラスの各プロパティのプロパティアクセサー(getter および setter)を生成します。事実上、コードは次のようになります。
public class SuperType {
private int field;
public SuperType(int field) {
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
public final class SubType extends SuperType {
private int field;
public SubType(int field) {
super(field);
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
SubType の Getter および setter は、SubType.field のみを設定し、SuperType.field は設定しません。このような配置では、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチです。SubType にメソッドを追加して this.SuperType.field = … を介して SuperType.field を設定することは可能ですが、サポートされている規則の範囲外です。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、プロパティのオーバーライドによってある程度の競合が発生します。通常、個別のプロパティ名を使用することをお勧めします。
Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。
どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに
@Transientアノテーションを付けることで、プロパティを除外できます。データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。
@AccessType(PROPERTY)を使用すると、スーパープロパティが設定できないため使用できません。
9.6.2. エンティティでサポートされている型
現在、次の型のプロパティがサポートされています。
すべてのプリミティブ型とそれらのボックス化された型 (
int、float、Integer、Floatなど)列挙型は名前にマップされます。
Stringjava.util.Date、java.time.LocalDate、java.time.LocalDateTime、java.time.LocalTimeデータベースでサポートされている場合、上記の型の配列とコレクションは、配列型の列にマップできます。
データベースドライバーが受け入れるもの。
他のエンティティへの参照。これらは 1 対 1 の関連、または埋め込み型と見なされます。1 対 1 の関連エンティティが
id属性を持つことはオプションです。参照エンティティのテーブルには、参照エンティティに基づく名前を持つ追加の列が必要です。バックリファレンスを参照してください。埋め込みエンティティにはidは必要ありません。存在する場合は無視されます。Set<some entity>は、1 対多の関連と見なされます。参照エンティティのテーブルには、参照エンティティに基づく名前を持つ追加の列が必要です。バックリファレンスを参照してください。Map<simple type, some entity>は、修飾された 1 対多の関連と見なされます。参照されるエンティティのテーブルには、2 つの追加の列があると予想されます。1 つは外部キーの参照エンティティに基づいて名前が付けられたもの ( バックリファレンスを参照)、もう 1 つは同じ名前で、マップキーの追加の_keyサフィックスです。この動作は、それぞれNamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path)とNamingStrategy.getKeyColumn(RelationalPersistentProperty property)を実装することで変更できます。または、@MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name")で属性にアノテーションを付けることもできますList<some entity>はMap<Integer, some entity>としてマップされます。
参照エンティティ
参照エンティティの処理は制限されています。これは、上記の集約ルートの考え方に基づいています。別のエンティティを参照する場合、そのエンティティは定義上、集約の一部です。そのため、参照を削除すると、以前に参照されたエンティティが削除されます。これは、参照が 1-1 または 1-n であるが、n-1 または n-m ではないことも意味します。
n-1 または n-m の参照がある場合、定義上、2 つの別個の集約を扱っています。それらの間の参照は、Spring Data JDBC で適切にマップされる単純な id 値としてエンコードされる場合があります。これらをエンコードするより良い方法は、AggregateReference のインスタンスにすることです。AggregateReference は、id 値を別の集約への参照としてマークするラッパーです。また、その集約の型は、型パラメーターでエンコードされます。
バックリファレンス
集約内のすべての参照は、データベース内で逆方向の外部キー関連になります。デフォルトでは、外部キー列の名前は参照エンティティのテーブル名です。
または、@Table アノテーションを無視して、参照エンティティのエンティティ名で名前を付けるように選択することもできます。RelationalMappingContext で setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING) を呼び出すことにより、この動作を有効にします。
List および Map 参照の場合、リストインデックスまたはマップキーを保持するために追加の列が必要です。これは、追加の _KEY サフィックスを持つ外部キー列に基づいています。
これらの逆参照の名前をまったく別の方法で付けたい場合は、必要に応じて NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path) を実装できます。
AggregateReference の宣言と設定 class Person {
@Id long id;
AggregateReference<Person, Long> bestFriend;
}
// ...
Person p1, p2 = // some initialization
p1.bestFriend = AggregateReference.to(p2.id);
9.6.3. NamingStrategy
Spring Data JDBC が提供する CrudRepository の標準実装を使用する場合、特定のテーブル構造が期待されます。アプリケーションコンテキストで NamingStrategy (Javadoc) を提供することで、これを調整できます。
9.6.4. Custom table names
NamingStrategy がデータベーステーブル名と一致しない場合、@Table (Javadoc) アノテーションを使用して名前をカスタマイズできます。このアノテーションの要素 value は、カスタムテーブル名を提供します。次の例では、MyEntity クラスをデータベースの CUSTOM_TABLE_NAME テーブルにマップします。
@Table("CUSTOM_TABLE_NAME")
class MyEntity {
@Id
Integer id;
String name;
}
9.6.5. Custom column names
NamingStrategy がデータベースの列名と一致しない場合、@Column (Javadoc) アノテーションを使用して名前をカスタマイズできます。このアノテーションの要素 value は、カスタム列名を提供します。次の例は、MyEntity クラスの name プロパティをデータベースの CUSTOM_COLUMN_NAME 列にマップします。
class MyEntity {
@Id
Integer id;
@Column("CUSTOM_COLUMN_NAME")
String name;
}
@MappedCollection (Javadoc) アノテーションは、参照型(1 対 1 の関連)またはセット、リスト、マップ(1 対多の関連)で使用できます。アノテーションの idColumn 要素は、他のテーブルの id 列を参照する外部キー列のカスタム名を提供します。次の例では、MySubEntity クラスに対応するテーブルに NAME 列と、MyEntity ID の CUSTOM_MY_ENTITY_ID_COLUMN_NAME 列が関連の理由で含まれています。
class MyEntity {
@Id
Integer id;
@MappedCollection(idColumn = "CUSTOM_MY_ENTITY_ID_COLUMN_NAME")
Set<MySubEntity> subEntities;
}
class MySubEntity {
String name;
}
List および Map を使用する場合、List のデータセットの位置または Map のエンティティのキー値の追加列が必要です。この追加の列名は、@MappedCollection (Javadoc) アノテーションの keyColumn 要素を使用してカスタマイズできます。
class MyEntity {
@Id
Integer id;
@MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
List<MySubEntity> name;
}
class MySubEntity {
String name;
}
9.6.6. 埋め込みエンティティ
埋め込みエンティティは、データベースにテーブルが 1 つしかない場合でも、java データモデルに値オブジェクトを保持するために使用されます。次の例では、MyEntity が @Embedded アノテーションでマップされています。この結果、データベースには、id および name (EmbeddedEntity クラスの)の 2 つの列を持つテーブル my_entity が期待されます。
ただし、結果セット内で name 列が実際に null である場合、@Embedded の onEmpty に従って、プロパティ embeddedEntity 全体が null に設定されます。ネストされたすべてのプロパティが null である場合、null はオブジェクトになります。
この動作とは反対に、USE_EMPTY は、デフォルトコンストラクターまたは結果セットから NULL 可能パラメーター値を受け入れるコンストラクターを使用して、新しいインスタンスを作成しようとします。
class MyEntity {
@Id
Integer id;
@Embedded(onEmpty = USE_NULL) (1)
EmbeddedEntity embeddedEntity;
}
class EmbeddedEntity {
String name;
}
| 1 | null の name の場合、Nulls embeddedEntity。USE_EMPTY を使用して、name プロパティの潜在的な null 値で embeddedEntity をインスタンス化します。 |
エンティティで複数回値オブジェクトが必要な場合は、@Embedded アノテーションのオプションの prefix 要素を使用してこれを実現できます。この要素はプレフィックスを表し、埋め込みオブジェクトの各列名の先頭に追加されます。
|
Collection または Map を含む埋め込みエンティティは、少なくとも空のコレクションまたはマップを含むため、常に空ではないとみなされます。このようなエンティティは、@Embedded(onEmpty = USE_NULL) を使用する場合でも、null になることはありません。
9.6.7. エンティティ状態検出戦略
次の表は、エンティティが新しいかどうかを検出するために Spring Data が提供する戦略を説明しています。
|
By default, Spring Data inspects the identifier property of the given entity.
If the identifier property is |
|
If a property annotated with |
Implementing | エンティティが 注: |
カスタム | モジュール固有のリポジトリファクトリのサブクラスを作成し、 |
9.6.8. ID 生成
Spring Data JDBC は、ID を使用してエンティティを識別します。エンティティの ID には、Spring Data の @Id (Javadoc) アノテーションを付ける必要があります。
データベースに ID 列の自動インクリメント列がある場合、生成された値は、データベースに挿入された後にエンティティに設定されます。
重要な制約の 1 つは、エンティティを保存した後、そのエンティティが新しいものであってはならないことです。エンティティが新しいかどうかは、エンティティの状態の一部であることに注意してください。自動インクリメント列では、ID 列の値を使用して Spring Data によって ID が設定されるため、これは自動的に行われます。自動インクリメント列を使用していない場合は、BeforeConvert リスナーを使用して、エンティティの ID を設定できます(このドキュメントで後述)。
9.6.9. 読み取り専用プロパティ
@ReadOnlyProperty アノテーションが付けられた属性は、Spring Data JDBC によってデータベースに書き込まれませんが、エンティティがロードされるときに読み取られます。
Spring Data JDBC は、エンティティの書き込み後にエンティティを自動的にリロードしません。そのような列のデータベースで生成されたデータを表示する場合は、明示的にリロードする必要があります。
アノテーション付き属性がエンティティまたはエンティティのコレクションである場合、個別のテーブル内の 1 つ以上の個別の行で表されます。Spring Data JDBC は、これらの行の挿入、削除、更新を実行しません。
9.6.10. 挿入専用プロパティ
@InsertOnlyProperty でアノテーションが付けられた属性は、挿入操作中に Spring Data JDBC によってのみデータベースに書き込まれます。更新の場合、これらのプロパティは無視されます。
@InsertOnlyProperty は、集約ルートに対してのみサポートされています。
9.6.11. 楽観的ロック
Spring Data JDBC は、集約ルートで @Version (Javadoc) のアノテーションが付けられた数値属性による楽観的ロックをサポートします。Spring Data JDBC がそのようなバージョン属性を持つ集約を保存するたびに、次の 2 つのことが起こります。集約ルートの更新ステートメントには、データベースに格納されているバージョンが実際に変更されていないことをチェックする where 句が含まれます。そうでない場合は、OptimisticLockingFailureException がスローされます。また、バージョン属性はエンティティとデータベースの両方で増加するため、同時アクションは変更を認識し、上記のように該当する場合は OptimisticLockingFailureException をスローします。
このプロセスは、新しい集合体の挿入にも適用されます。null または 0 バージョンは新しいインスタンスを示し、その後、増加したインスタンスはインスタンスを新規ではないものとしてマークします。UUID が使用されます。
削除中にバージョンチェックも適用されますが、バージョンは増加しません。
9.7. クエリメソッド
このセクションでは、Spring Data JDBC の実装と使用に関する特定の情報を提供します。
通常、リポジトリでトリガーするデータアクセス操作のほとんどは、データベースに対して実行されるクエリになります。このようなクエリの定義は、次の例に示すように、リポジトリインターフェースでメソッドを宣言することです。
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(); (6)
}
| 1 | このメソッドは、指定された firstname を持つすべての人々のクエリを示します。クエリは、And および Or と連結できる制約のメソッド名を解析することによって導出されます。メソッド名は SELECT … FROM person WHERE firstname = :firstname のクエリ式になります。 |
| 2 | Pageable を使用して、オフセットと並べ替えのパラメーターをデータベースに渡します。 |
| 3 | Slice<Person> を返します。LIMIT+1 行を選択して、消費するデータがまだあるかどうかを判断します。ResultSetExtractor のカスタマイズはサポートされていません。 |
| 4 | Page<Person> を返すページ付けされたクエリを実行します。指定されたページ境界内のデータのみを選択し、場合によってはカウントクエリを選択して合計カウントを決定します。ResultSetExtractor のカスタマイズはサポートされていません。 |
| 5 | 指定された条件で単一のエンティティを検索します。一意でない結果の場合は、IncorrectResultSizeDataAccessException で完了します。 |
| 6 | <3> とは対照的に、クエリがより多くの結果ドキュメントを生成した場合でも、最初のエンティティは常に出力されます。 |
| 7 | findByLastname メソッドは、指定された lastname を持つすべての人のクエリを表示します。 |
| 8 | streamByLastname メソッドは Stream を返します。これにより、データベースから値が返されるとすぐに値が可能になります。 |
| 9 | Spring 式言語を使用して、パラメーターを動的に解決できます。サンプルでは、現在のユーザーのユーザー名を解決するために Spring Security が使用されています。 |
次の表は、クエリメソッドでサポートされるキーワードを示しています。
| キーワード | サンプル | 論理的な結果 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
クエリの派生は、結合を使用せずに WHERE 句で使用できるプロパティに制限されています。 |
9.7.1. クエリ検索戦略
JDBC モジュールは、@Query アノテーションの文字列またはプロパティファイルの名前付きクエリとしてクエリを手動で定義することをサポートします。
メソッドの名前からクエリを取得することは、現在、単純なプロパティに制限されています。つまり、プロパティは集約ルートに直接存在します。また、このアプローチでは、select クエリのみがサポートされています。
9.7.2. @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 の関連を設定するために使用されます。エンティティのマップ、リスト、セット、配列に対して個別のクエリが生成されます。
Spring は、-parameters コンパイラーフラグに基づく Java 8 のパラメーター名の検出を完全にサポートしています。デバッグ情報の代替としてビルドでこのフラグを使用することにより、名前付きパラメーターの @Param アノテーションを省略できます。 |
| Spring Data JDBC は、名前付きパラメーターのみをサポートします。 |
9.7.3. 名前付きクエリ
前のセクションで説明したように、アノテーションにクエリが指定されていない場合、Spring Data JDBC は名前付きクエリを見つけようとします。クエリの名前を決定する方法は 2 つあります。デフォルトでは、クエリのドメインクラス、つまりリポジトリの集約ルートを取得し、その単純名を取得して、. で区切られたメソッドの名前を追加します。あるいは、@Query アノテーションには、検索するクエリの名前を指定するために使用できる name 属性があります。
名前付きクエリは、クラスパスのプロパティファイル META-INF/jdbc-named-queries.properties で提供されることが期待されています。
そのファイルの場所は、値を @EnableJdbcRepositories.namedQueriesLocation に設定することにより変更できます。
ストリーミング結果
クエリメソッドの戻り値の型として Stream を指定すると、Spring Data JDBC は要素が使用可能になるとすぐに要素を返します。大量のデータを処理する場合、これは遅延とメモリ要件を削減するのに適しています。
ストリームには、データベースへのオープン接続が含まれています。メモリリークを回避するには、ストリームを閉じて、最終的にその接続を閉じる必要があります。そのための推奨される方法は try-with-resource clause です。また、データベースへの接続が閉じられると、ストリームはそれ以上の要素を取得できず、例外をスローする可能性があることも意味します。
カスタム RowMapper
@Query(rowMapperClass = ….) を使用するか、メソッド戻り値の型ごとに RowMapperMap Bean を登録して RowMapper を登録することにより、使用する RowMapper を構成できます。次の例は、DefaultQueryMappingConfiguration を登録する方法を示しています。
@Bean
QueryMappingConfiguration rowMappers() {
return new DefaultQueryMappingConfiguration()
.register(Person.class, new PersonRowMapper())
.register(Address.class, new AddressRowMapper());
}
メソッドに使用する RowMapper を決定するときは、メソッドの戻り値の型に基づいて、次の手順に従います。
型が単純型の場合、
RowMapperは使用されません。代わりに、クエリは単一の列を持つ単一の行を返すことが期待され、戻り値の型への変換がその値に適用されます。
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);
以下の戻り値の型を指定できます。
voidint(更新されたレコード数)boolean(レコードが更新されたかどうか)
変更クエリは、データベースに対して直接実行されます。イベントやコールバックは呼び出されません。アノテーション付きクエリで更新されない場合、監査アノテーションを含むフィールドも更新されません。
9.8. 例示による問い合わせ
9.8.1. 導入
この章では、Query by Example の概要とその使用方法について説明します。
Query by Example(QBE)は、シンプルなインターフェースを備えた使いやすいクエリ手法です。動的なクエリの作成が可能になり、フィールド名を含むクエリを作成する必要がなくなります。実際、Query by Example では、ストア固有のクエリ言語を使用してクエリを記述する必要はまったくありません。
9.8.2. 使用方法
例示による問い合わせ API は、次の 4 つの部分で構成されています。
プローブ: フィールドが設定されたドメインオブジェクトの実際の例。
ExampleMatcher:ExampleMatcherには、特定のフィールドの照合方法に関する詳細が記載されています。複数の例で再利用できます。Example:Exampleは、プローブとExampleMatcherで構成されています。クエリの作成に使用されます。FetchableFluentQuery:FetchableFluentQueryは流れるような API を提供し、Exampleから派生したクエリをさらにカスタマイズできるようにします。Fluent API を使用すると、クエリの順序付けの射影と結果の処理を指定できます。
例示による問い合わせは、いくつかのユースケースに適しています。
静的または動的な制約のセットを使用してデータストアをクエリします。
既存のクエリを壊すことを心配せずにドメインオブジェクトを頻繁にリファクタリングします。
基礎となるデータストア API から独立して動作します。
例示による問い合わせには、いくつかの制限もあります。
firstname = ?0 or (firstname = ?1 and lastname = ?2)など、ネストまたはグループ化されたプロパティ制約はサポートされていません。文字列の starts/contains/ends/regex マッチングと他のプロパティ型の完全一致のみをサポートします。
Query by Example を開始する前に、ドメインオブジェクトが必要です。開始するには、次の例に示すように、リポジトリのインターフェースを作成します。
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
前の例は、単純なドメインオブジェクトを示しています。これを使用して Example を作成できます。デフォルトでは、null 値を持つフィールドは無視され、文字列はストア固有のデフォルトを使用して照合されます。
例示による問い合わせ条件へのプロパティの包含は、null 可能性に基づいています。プリミティブ型(int、double、…)を使用するプロパティは、ExampleMatcher はプロパティパスを無視しますでない限り、常に含まれます。 |
例は、of ファクトリメソッドを使用するか、ExampleMatcher を使用して作成できます。Example は不変です。次のリストは、簡単な例を示しています。
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
Example<Person> example = Example.of(person); (3)
| 1 | ドメインオブジェクトの新しいインスタンスを作成します。 |
| 2 | クエリにプロパティを設定します。 |
| 3 | Example を作成します。 |
リポジトリを使用して、サンプルクエリを実行できます。これを行うには、リポジトリインターフェースに QueryByExampleExecutor<T> を継承させます。次のリストは、QueryByExampleExecutor インターフェースからの抜粋を示しています。
QueryByExampleExecutorpublic interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
9.8.3. マッチャーの例
例はデフォルト設定に限定されません。次の例に示すように、ExampleMatcher を使用して、文字列照合、null 処理、プロパティ固有の設定に独自のデフォルトを指定できます。
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
ExampleMatcher matcher = ExampleMatcher.matching() (3)
.withIgnorePaths("lastname") (4)
.withIncludeNullValues() (5)
.withStringMatcher(StringMatcher.ENDING); (6)
Example<Person> example = Example.of(person, matcher); (7)
| 1 | ドメインオブジェクトの新しいインスタンスを作成します。 |
| 2 | セットのプロパティ。 |
| 3 | ExampleMatcher を作成して、すべての値が一致することを期待します。この段階では、さらに構成しなくても使用できます。 |
| 4 | lastname プロパティパスを無視する新しい ExampleMatcher を構築します。 |
| 5 | 新しい ExampleMatcher を作成して、lastname プロパティパスを無視し、null 値を含めます。 |
| 6 | 新しい ExampleMatcher を作成して、lastname プロパティパスを無視し、null 値を含め、サフィックス文字列の照合を実行します。 |
| 7 | ドメインオブジェクトと設定された ExampleMatcher に基づいて新しい Example を作成します。 |
デフォルトでは、ExampleMatcher はプローブに設定されたすべての値が一致することを期待しています。暗黙的に定義された述語のいずれかに一致する結果を取得する場合は、ExampleMatcher.matchingAny() を使用します。
個々のプロパティ(「名」や「姓」、ネストされたプロパティの場合は "address.city" など)の動作を指定できます。次の例に示すように、一致するオプションと大文字と小文字の区別を使用して調整できます。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
マッチャーオプションを構成する別の方法は、ラムダ(Java 8 で導入)を使用することです。このアプローチは、実装者にマッチャーの変更を要求するコールバックを作成します。設定オプションはマッチャーインスタンス内に保持されているため、マッチャーを返す必要はありません。次の例は、ラムダを使用するマッチャーを示しています。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
Example によって作成されたクエリは、構成の統合ビューを使用します。デフォルトのマッチング設定は ExampleMatcher レベルで設定できますが、個々の設定は特定のプロパティパスに適用できます。ExampleMatcher で設定された設定は、明示的に定義されていない限り、プロパティパス設定に継承されます。プロパティパッチの設定は、デフォルト設定よりも優先されます。次の表は、さまざまな ExampleMatcher 設定の範囲を説明しています。
| 設定 | スコープ |
|---|---|
null ハンドリング |
|
文字列マッチング |
|
Ignoring properties |
Property path |
Case sensitivity |
|
Value transformation |
Property path |
9.8.4. Fluent API
QueryByExampleExecutor offers one more method, which we did not mention so far: <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction).
As with other methods, it executes a query derived from an Example.
However, with the second argument, you can control aspects of that execution that you cannot dynamically control otherwise.
You do so by invoking the various methods of the FetchableFluentQuery in the second argument.
sortBy lets you specify an ordering for your result.
as lets you specify the type to which you want the result to be transformed.
project limits the queried attributes.
first, firstValue, one, oneValue, all, page, stream, count, and exists define what kind of result you get and how the query behaves when more than the expected number of results are available.
Optional<Person> match = repository.findBy(example,
q -> q
.sortBy(Sort.by("lastname").descending())
.first()
);
9.8.5. Running an Example
In Spring Data JDBC, you can use Query by Example with Repositories, as shown in the following example:
public interface PersonRepository
extends CrudRepository<Person, String>,
QueryByExampleExecutor<Person> { … }
public class PersonService {
@Autowired PersonRepository personRepository;
public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
Currently, only SingularAttribute properties can be used for property matching.
|
The property specifier accepts property names (such as firstname and lastname). You can navigate by chaining properties together with dots (address.city). You can also tune it with matching options and case sensitivity.
The following table shows the various StringMatcher options that you can use and the result of using them on a field named firstname:
| Matching | Logical result |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9.8.6. 射影
Spring Data クエリメソッドは通常、リポジトリによって管理される集約ルートの 1 つまたは複数のインスタンスを返します。ただし、これらの型の特定の属性に基づいて射影を作成することが望ましい場合があります。Spring Data では、専用の戻り値型をモデル化して、管理対象集合体の部分ビューをより選択的に取得できます。
次の例のようなリポジトリおよび集約ルート型を想像してください。
class Person {
@Id UUID id;
String firstname, lastname;
Address address;
static class Address {
String zipCode, city, street;
}
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<Person> findByLastname(String lastname);
}
ここで、人の名前属性のみを取得することを想像してください。Spring Data はこれを達成するためにどのような意味を持っていますか? この章の残りはその質問に回答します。
インターフェースベースの射影
クエリの結果を名前属性のみに制限する最も簡単な方法は、次の例に示すように、読み取るプロパティのアクセサーメソッドを公開するインターフェースを宣言することです。
interface NamesOnly {
String getFirstname();
String getLastname();
}
ここで重要なことは、ここで定義されたプロパティが集約ルートのプロパティと正確に一致することです。これにより、クエリメソッドを次のように追加できます。
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
クエリ実行エンジンは、返された各要素に対して実行時にそのインターフェースのプロキシインスタンスを作成し、公開されたメソッドへの呼び出しをターゲットオブジェクトに転送します。
基本メソッド(たとえば、CrudRepository、ストア固有のリポジトリインターフェース、Simple … Repository で宣言されている)をオーバーライドするメソッドを Repository で宣言すると、宣言された戻り値の型に関係なく、基本メソッドが呼び出されます。基本メソッドは射影に使用できないため、互換性のある戻り値の型を使用してください。一部のストアモジュールは、@Query アノテーションをサポートして、オーバーライドされたベースメソッドをクエリメソッドに変換します。このクエリメソッドを使用して、射影を返すことができます。 |
射影は再帰的に使用できます。Address 情報の一部も含めたい場合は、次の例に示すように、そのための射影インターフェースを作成し、getAddress() の宣言からそのインターフェースを返します。
interface PersonSummary {
String getFirstname();
String getLastname();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
}
}
メソッドの呼び出し時に、ターゲットインスタンスの address プロパティが取得され、順番に投影プロキシにラップされます。
閉じた射影
アクセサーメソッドがすべてターゲット集合体のプロパティに一致する射影インターフェースは、閉じた射影と見なされます。次の例(この章の前半でも使用しました)は、閉じた射影です。
interface NamesOnly {
String getFirstname();
String getLastname();
}
閉じた射影を使用する場合、Spring Data はクエリの実行を最適化できます。これは、射影プロキシのバックアップに必要なすべての属性がわかっているためです。詳細については、リファレンスドキュメントのモジュール固有の部分を参照してください。
開いた射影
次の例に示すように、@Value アノテーションを使用して、射影インターフェースのアクセサーメソッドを使用して新しい値を計算することもできます。
interface NamesOnly {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
…
}
射影を支える集約ルートは、target 変数で利用可能です。@Value を使用した射影インターフェースは、オープン射影です。この場合、Spring Data はクエリ実行最適化を適用できません。これは、SpEL 式が集約ルートの任意の属性を使用できるためです。
@Value で使用される式は複雑すぎてはいけません — String 変数でのプログラミングは避けたいです。非常に単純な式の場合、次の例に示すように、1 つのオプションはデフォルトのメソッド(Java 8 で導入)に頼ることです。
interface NamesOnly {
String getFirstname();
String getLastname();
default String getFullName() {
return getFirstname().concat(" ").concat(getLastname());
}
}
このアプローチでは、射影インターフェースで公開される他のアクセサーメソッドに純粋に基づいてロジックを実装できる必要があります。次の例に示すように、2 番目のより柔軟なオプションは、Spring Bean にカスタムロジックを実装し、SpEL 式からそれを呼び出すことです。
@Component
class MyBean {
String getFullName(Person person) {
…
}
}
interface NamesOnly {
@Value("#{@myBean.getFullName(target)}")
String getFullName();
…
}
SpEL 式が myBean を参照し、getFullName(…) メソッドを呼び出し、射影ターゲットをメソッドパラメーターとして転送する方法に注目してください。SpEL 式の評価に裏付けられたメソッドは、メソッドパラメーターを使用することもできます。このパラメーターは、式から参照できます。メソッドのパラメーターは、args という名前の Object 配列を介して使用できます。次の例は、args 配列からメソッドパラメーターを取得する方法を示しています。
interface NamesOnly {
@Value("#{args[0] + ' ' + target.firstname + '!'}")
String getSalutation(String prefix);
}
繰り返しますが、より複雑な式の場合は、前に説明したように、Spring Bean を使用し、式でメソッドを呼び出す必要があります。
null 可能ラッパー
射影インターフェースの Getter は、null 許容ラッパーを使用して null の安全性を向上させることができます。現在サポートされているラッパー型は次のとおりです。
java.util.Optionalcom.google.common.base.Optionalscala.Optionio.vavr.control.Option
interface NamesOnly {
Optional<String> getFirstname();
}
基になる射影値が null でない場合、値はラッパー型の現在の表現を使用して返されます。バッキング値が null の場合、getter メソッドは使用されたラッパー型の空の表現を返します。
クラスベースの射影 (DTO)
射影を定義するもう 1 つの方法は、取得することになっているフィールドのプロパティを保持する値型 DTO(データ転送オブジェクト)を使用することです。これらの DTO 型は、プロキシが発生せず、ネストされた射影を適用できないことを除いて、射影インターフェースとまったく同じ方法で使用できます。
ストアがロードするフィールドを制限することでクエリの実行を最適化する場合、ロードされるフィールドは公開されているコンストラクターのパラメーター名から決定されます。
次の例は、投影 DTO を示しています。
record NamesOnly(String firstname, String lastname) {
}
Java レコードは、値のセマンティクスに準拠しているため、DTO 型を定義するのに理想的です。すべてのフィールドは private final であり、equals(…)/hashCode()/toString() メソッドは自動的に作成されます。または、投影するプロパティを定義する任意のクラスを使用できます。
動的射影
これまで、コレクションの戻り値型または要素型として射影型を使用しました。ただし、呼び出し時に使用する型を選択することもできます(これにより、動的になります)。動的射影を適用するには、次の例に示すようなクエリメソッドを使用します。
interface PersonRepository extends Repository<Person, UUID> {
<T> Collection<T> findByLastname(String lastname, Class<T> type);
}
この方法では、次の例に示すように、メソッドを使用して、そのままで、または射影を適用して集約を取得できます。
void someMethod(PersonRepository people) {
Collection<Person> aggregates =
people.findByLastname("Matthews", Person.class);
Collection<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}
型 Class のクエリパラメーターは、動的射影パラメーターとして適格かどうかがインスペクションされます。クエリの実際の戻り値の型が Class パラメーターのジェネリクスパラメーター型と等しい場合、一致する Class パラメーターはクエリまたは SpEL 式内で使用できません。Class パラメーターをクエリ引数として使用する場合は、必ず別のジェネリクスパラメーター(Class<?> など)を使用してください。 |
9.9. MyBatis 統合
CRUD 操作とクエリメソッドは MyBatis に委譲できます。このセクションでは、MyBatis と統合するように Spring Data JDBC を構成する方法、およびクエリの実行とライブラリへのマッピングを引き継ぐために従うべき規則について説明します。
9.9.1. 構成
MyBatis を Spring Data JDBC に適切にプラグインする最も簡単な方法は、MyBatisJdbcConfiguration をアプリケーション構成にインポートすることです。
@Configuration
@EnableJdbcRepositories
@Import(MyBatisJdbcConfiguration.class)
class Application {
@Bean
SqlSessionFactoryBean sqlSessionFactoryBean() {
// Configure MyBatis here
}
}
ご覧のとおり、MyBatisJdbcConfiguration は ApplicationContext で最終的に利用可能になるために SqlSession Bean に依存しているため、宣言する必要があるのは SqlSessionFactoryBean だけです。
9.9.2. 使用規則
CrudRepository の各操作に対して、Spring Data JDBC は複数のステートメントを実行します。アプリケーションコンテキストに SqlSessionFactory [GitHub] (英語) がある場合、Spring Data は、各ステップで、SessionFactory がステートメントを提供するかどうかをチェックします。見つかった場合は、そのステートメント(構成されたエンティティへのマッピングを含む)が使用されます。
ステートメントの名前は、エンティティ型の完全修飾名と Mapper. およびステートメントの種類を決定する String を連結することにより構築されます。例: org.example.User のインスタンスが挿入される場合、Spring Data JDBC は org.example.UserMapper.insert という名前のステートメントを探します。
ステートメントが実行されると、[MyBatisContext] のインスタンスが引数として渡され、さまざまな引数がステートメントで使用可能になります。
次の表は、使用可能な MyBatis ステートメントについて説明しています。
| 名前 | 目的 | このステートメントをトリガーする可能性のある CrudRepository メソッド | MyBatisContext で利用可能な属性 |
|---|---|---|---|
| 単一のエンティティを挿入します。これは、集約ルートによって参照されるエンティティにも適用されます。 |
|
|
| 単一のエンティティを更新します。これは、集約ルートによって参照されるエンティティにも適用されます。 |
|
|
| 単一のエンティティを削除します。 |
|
|
| 指定されたプロパティパスのプレフィックスとして使用される型の集約ルートによって参照されるすべてのエンティティを削除します。ステートメント名のプレフィックスに使用される型は、削除されるエンティティの名前ではなく、集約ルートの名前であることに注意してください。 |
|
|
| プレフィックスとして使用される型のすべての集約ルートを削除します |
|
|
| 指定された propertyPath を持つ集約ルートによって参照されるすべてのエンティティを削除します |
|
|
| ID で集約ルートを選択します |
|
|
| すべての集約ルートを選択します |
|
|
| ID 値によって集約ルートのセットを選択します |
|
|
| 別のエンティティによって参照されるエンティティのセットを選択します。参照エンティティの型がプレフィックスに使用されます。参照されるエンティティ型は、接尾辞として使用されます。このメソッドは非推奨です。代わりに | すべての |
|
| プロパティパスを介して別のエンティティによって参照されるエンティティのセットを選択します。 | すべての |
|
| ソートされたすべての集約ルートを選択します |
|
|
| オプションでソートされた集約ルートのページを選択します |
|
|
| プレフィックスとして使用される型の集約ルートの数を数えます |
|
|
9.10. Lifecycle Events
Spring Data JDBC triggers events that get published to any matching ApplicationListener beans in the application context.
Events and callbacks get only triggered for aggregate roots.
If you want to process non-root entities, you need to do that through a listener for the containing aggregate root.
Entity lifecycle events can be costly, and you may notice a change in the performance profile when loading large result sets. You can disable lifecycle events on the Template API.
For example, the following listener gets invoked before an aggregate gets saved:
@Bean
ApplicationListener<BeforeSaveEvent<Object>> loggingSaves() {
return event -> {
Object entity = event.getEntity();
LOG.info("{} is getting saved.", entity);
};
}
If you want to handle events only for a specific domain type you may derive your listener from AbstractRelationalEventListener and overwrite one or more of the onXXX methods, where XXX stands for an event type.
Callback methods will only get invoked for events related to the domain type and their subtypes, therefore you don’t require further casting.
class PersonLoadListener extends AbstractRelationalEventListener<Person> {
@Override
protected void onAfterLoad(AfterLoadEvent<Person> personLoad) {
LOG.info(personLoad.getEntity());
}
}
The following table describes the available events. For more details about the exact relation between process steps see the description of available callbacks which map 1:1 to events.
| Event | When It Is Published |
|---|---|
Before an aggregate root gets deleted. |
|
After an aggregate root gets deleted. |
|
Before an aggregate root gets converted into a plan for executing SQL statements, but after the decision was made if the aggregate is new or not, i.e. if an update or an insert is in order. This is the correct event if you want to set an id programmatically. |
|
Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets inserted or updated was made). Do not use this for creating Ids for new aggregates. Use |
|
After an aggregate root gets saved (that is, inserted or updated). |
|
After an aggregate root gets created from a database |
Lifecycle events depend on an ApplicationEventMulticaster, which in case of the SimpleApplicationEventMulticaster can be configured with a TaskExecutor, and therefore gives no guarantees when an Event is processed.
|
9.10.1. Store-specific EntityCallbacks
Spring Data JDBC uses the EntityCallback API for its auditing support and reacts on the callbacks listed in the following table.
| Process | EntityCallback / Process Step |
Comment |
|---|---|---|
Delete |
Before the actual deletion. |
|
The aggregate root and all the entities of that aggregate get removed from the database. |
||
After an aggregate gets deleted. |
||
Save |
Determine if an insert or an update of the aggregate is to be performed dependen on if it is new or not. |
|
This is the correct callback if you want to set an id programmatically. In the previous step new aggregates got detected as such and a Id generated in this step would be used in the following step. |
||
Convert the aggregate to a aggregate change, it is a sequence of SQL statements to be executed against the database. In this step the decision is made if an Id is provided by the aggregate or if the Id is still empty and is expected to be generated by the database. |
||
Changes made to the aggregate root may get considered, but the decision if an id value will be sent to the database is already made in the previous step. |
||
The SQL statements determined above get executed against the database. |
||
After an aggregate root gets saved (that is, inserted or updated). |
||
Load |
Load the aggregate using 1 or more SQL queries. Construct the aggregate from the resultset. |
|
We encourage the use of callbacks over events since they support the use of immutable classes and therefore are more powerful and versatile than events.
9.11. Entity Callbacks
The Spring Data infrastructure provides hooks for modifying an entity before and after certain methods are invoked.
Those so called EntityCallback instances provide a convenient way to check and potentially modify an entity in a callback fashioned style.
An EntityCallback looks pretty much like a specialized ApplicationListener.
Some Spring Data modules publish store specific events (such as BeforeSaveEvent) that allow modifying the given entity. In some cases, such as when working with immutable types, these events can cause trouble.
Also, event publishing relies on ApplicationEventMulticaster. If configuring that with an asynchronous TaskExecutor it can lead to unpredictable outcomes, as event processing can be forked onto a Thread.
Entity callbacks provide integration points with both synchronous and reactive APIs to guarantee in-order execution at well-defined checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
Entity callbacks are typically separated by API type. This separation means that a synchronous API considers only synchronous entity callbacks and a reactive implementation considers only reactive entity callbacks.
|
The Entity Callback API has been introduced with Spring Data Commons 2.2. It is the recommended way of applying entity modifications.
Existing store specific |
9.11.1. Implementing Entity Callbacks
An EntityCallback is directly associated with its domain type through its generic type argument.
Each Spring Data module typically ships with a set of predefined EntityCallback interfaces covering the entity lifecycle.
EntityCallback@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before a domain object is saved.
* Can return either the same or a modified instance.
*
* @return the domain object to be persisted.
*/
T onBeforeSave(T entity <2>, String collection <3>); (1)
}
| 1 | BeforeSaveCallback specific method to be called before an entity is saved. Returns a potentially modifed instance. |
| 2 | The entity right before persisting. |
| 3 | A number of store specific arguments like the collection the entity is persisted to. |
EntityCallback@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked on subscription, before a domain object is saved.
* The returned Publisher can emit either the same or a modified instance.
*
* @return Publisher emitting the domain object to be persisted.
*/
Publisher<T> onBeforeSave(T entity <2>, String collection <3>); (1)
}
| 1 | BeforeSaveCallback specific method to be called on subscription, before an entity is saved. Emits a potentially modifed instance. |
| 2 | The entity right before persisting. |
| 3 | A number of store specific arguments like the collection the entity is persisted to. |
Optional entity callback parameters are defined by the implementing Spring Data module and inferred from call site of EntityCallback.callback().
|
Implement the interface suiting your application needs like shown in the example below:
BeforeSaveCallbackclass DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered { (2)
@Override
public Object onBeforeSave(Person entity, String collection) { (1)
if(collection == "user") {
return // ...
}
return // ...
}
@Override
public int getOrder() {
return 100; (2)
}
}
| 1 | Callback implementation according to your requirements. |
| 2 | Potentially order the entity callback if multiple ones for the same domain type exist. Ordering follows lowest precedence. |
9.11.2. Registering Entity Callbacks
EntityCallback beans are picked up by the store specific implementations in case they are registered in the ApplicationContext.
Most template APIs already implement ApplicationContextAware and therefore have access to the ApplicationContext
次の例は、有効なエンティティコールバック登録のコレクションを説明しています。
EntityCallback Bean 登録の例 @Order(1) (1)
@Component
class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}
@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
Ordered { (2)
@Override
public Object onBeforeSave(Person entity, String collection) {
// ...
}
@Override
public int getOrder() {
return 100; (2)
}
}
@Configuration
public class EntityCallbackConfiguration {
@Bean
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() { (3)
return (BeforeSaveCallback<Person>) it -> // ...
}
}
@Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> { (4)
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}
| 1 | BeforeSaveCallback は @Order アノテーションからオーダーを受け取ります。 |
| 2 | BeforeSaveCallback は、Ordered インターフェース実装を介してオーダーを受け取ります。 |
| 3 | ラムダ式を使用した BeforeSaveCallback。デフォルトでは順不同で、最後に呼び出されます。ラムダ式によって実装されたコールバックはタイピング情報を公開しないため、割り当て不可能なエンティティでこれらを呼び出すと、コールバックのスループットに影響することに注意してください。class または enum を使用して、コールバック Bean の型フィルタリングを有効にします。 |
| 4 | 単一の実装クラスに複数のエンティティコールバックインターフェースを組み合わせます。 |
9.12. カスタム変換
Spring Data JDBC を使用すると、カスタムコンバーターを登録して、データベースでの値のマッピング方法に影響を与えることができます。現在、コンバーターはプロパティレベルでのみ適用されます。
9.12.1. 登録済みの Spring コンバーターを使用したプロパティの記述
次の例は、Boolean オブジェクトから String 値に変換する Converter の実装を示しています。
@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {
@Override
public String convert(Boolean source) {
return source != null && source ? "T" : "F";
}
}
ここで注意すべき点がいくつかあります。Boolean と String はどちらも単純な型であるため、Spring Data はこのコンバーターを適用する方向(読み取りまたは書き込み)のヒントを必要とします。このコンバーターに @WritingConverter のアノテーションを付けることにより、すべての Boolean プロパティを String としてデータベースに書き込むように Spring Data に指示します。
9.12.2. Spring コンバーターを使用した読み取り
次の例は、String から Boolean 値に変換する Converter の実装を示しています。
@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {
@Override
public Boolean convert(String source) {
return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
}
}
ここで注意すべき点がいくつかあります。String と Boolean はどちらも単純な型であるため、Spring Data はこのコンバーターを適用する方向(読み取りまたは書き込み)のヒントを必要とします。このコンバーターに @ReadingConverter のアノテーションを付けることにより、Spring Data に、Boolean プロパティに割り当てる必要があるデータベースからのすべての String 値を変換するように指示します。
9.12.3. JdbcConverter を使用した Spring コンバーターの登録
class MyJdbcConfiguration extends AbstractJdbcConfiguration {
// …
@Override
protected List<?> userConverters() {
return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
}
}
以前のバージョンの Spring Data JDBC では、AbstractJdbcConfiguration.jdbcCustomConversions() を直接上書きすることが推奨されていました。この方法では、すべてのデータベースを対象とした変換、使用された Dialect によって登録された変換、ユーザーによって登録された変換がアセンブルされるため、これは不要になり、推奨もされなくなりました。古いバージョンの Spring Data JDBC から移行していて、AbstractJdbcConfiguration.jdbcCustomConversions() が上書きされた場合、Dialect からの変換は登録されません。 |
9.12.4. JdbcValue
値変換では、JdbcValue を使用して、java.sql.Types 型の JDBC 操作に伝搬される値を強化します。型の派生を使用する代わりに JDBC 固有の型を指定する必要がある場合は、カスタム書き込みコンバーターを登録します。このコンバーターは、値を実際の JDBCType のフィールドを持つ JdbcValue に変換する必要があります。
次の Spring Converter 実装の例は、String からカスタム Email 値オブジェクトに変換します。
@ReadingConverter
public class EmailReadConverter implements Converter<String, Email> {
public Email convert(String source) {
return Email.valueOf(source);
}
}
ソースとターゲットの型がネイティブ型である Converter を記述した場合、それを読み取りコンバーターと見なすか、書き込みコンバーターと見なすべきかを判断できません。コンバーターインスタンスを両方として登録すると、望ましくない結果が生じる可能性があります。例: Converter<String, Long> があいまいですが、書き込み時にすべての String インスタンスを Long インスタンスに変換しようとしても意味がありません。インフラストラクチャーにコンバーターを一方向にのみ登録させるために、コンバーターの実装で使用される @ReadingConverter および @WritingConverter アノテーションを提供します。
インスタンスはクラスパスまたはコンテナースキャンから取得されないため、コンバーターは明示的な登録の対象となります。変換サービスへの不要な登録と、そのような登録に起因する副作用を回避するためです。コンバーターは、ソースおよびターゲットの型に基づいて、登録されたコンバーターの登録および照会を可能にする中央機能として CustomConversions に登録されます。
CustomConversions には、事前に定義された一連のコンバーター登録が付属しています。
java.time、java.util.Date、String型間の変換用の JSR-310 コンバーター。
ローカルテンポラル型(LocalDateTime から java.util.Date など)のデフォルトコンバーターは、システムデフォルトのタイムゾーン設定に依存して、これらの型間で変換します。独自のコンバーターを登録することにより、デフォルトのコンバーターをオーバーライドできます。 |
コンバーターの明確化
一般に、Converter の実装は、変換元と変換先のソース型とターゲット型をインスペクションします。これらの 1 つが、基になるデータアクセス API がネイティブに処理できる型かどうかに応じて、コンバーターインスタンスを読み取りまたは書き込みコンバーターとして登録します。次の例は、書き込みコンバーターと読み取りコンバーターを示しています(違いは Converter の修飾子の順序にあることに注意してください)。
// Write converter as only the target type is one that can be handled natively
class MyConverter implements Converter<Person, String> { … }
// Read converter as only the source type is one that can be handled natively
class MyConverter implements Converter<String, Person> { … }
9.13. ログ
Spring Data JDBC は、それ自体ではほとんどまたはまったくロギングを行いません。代わりに、SQL ステートメントを発行する JdbcTemplate のメカニズムにより、ログが提供されます。実行されている SQL ステートメントをインスペクションする場合は、Spring の NamedParameterJdbcTemplate または MyBatis のロギングをアクティブにします。
9.14. トランザクション性
CrudRepository インスタンスのメソッドは、デフォルトでトランザクションです。読み取り操作の場合、トランザクション構成 readOnly フラグは true に設定されます。他のすべては、デフォルトのトランザクション構成が適用されるように、プレーンな @Transactional アノテーションで構成されています。詳細については、SimpleJdbcRepository (Javadoc) の Javadoc を参照してください。リポジトリで宣言されたメソッドの 1 つのトランザクション構成を微調整する必要がある場合は、次のように、リポジトリインターフェースでメソッドを再宣言します。
interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
List<User> findAll();
// Further query method declarations
}
上記により、findAll() メソッドはタイムアウトが 10 秒で、readOnly フラグなしで実行されます。
トランザクションの動作を変更する別の方法は、通常複数のリポジトリをカバーするファサードまたはサービス実装を使用することです。その目的は、非 CRUD 操作のトランザクション境界を定義することです。次の例は、このようなファサードを作成する方法を示しています。
@Service
public class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
上記の例では、addRoleToAllUsers(…) の呼び出しがトランザクション内で実行されます(既存のトランザクションに参加するか、まだ実行されていない場合は新しいトランザクションを作成します)。リポジトリのトランザクション設定は無視されます。これは、外部のトランザクション設定が実際に使用されるリポジトリを決定するためです。<tx:annotation-driven /> を明示的にアクティブにするか、@EnableTransactionManagement を使用して、ファサードが機能するようにアノテーションベースの構成を取得する必要があることに注意してください。上記の例では、コンポーネントスキャンを使用することを前提としています。
9.14.1. トランザクションクエリメソッド
クエリメソッドをトランザクション対応にするには、次の例に示すように、定義するリポジトリインターフェースで @Transactional を使用します。
@Transactional(readOnly = true)
interface UserRepository extends CrudRepository<User, Long> {
List<User> findByLastname(String lastname);
@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
通常、ほとんどのクエリメソッドはデータの読み取りのみを行うため、readOnly フラグを true に設定する必要があります。それとは対照的に、deleteInactiveUsers() は @Modifying アノテーションを使用し、トランザクション構成をオーバーライドします。このメソッドは readOnly フラグが false に設定されています。
| クエリメソッドをトランザクションにすることを強くお勧めします。これらのメソッドは、エンティティにデータを入力するために複数のクエリを実行する場合があります。共通のトランザクションがない場合、Spring Data JDBC はさまざまな接続でクエリを実行します。これにより、接続プールに過度の負担がかかる可能性があり、複数のメソッドが 1 つを保持しているときに新しい接続をリクエストすると、デッドロックが発生する可能性があります。 |
readOnly フラグを設定することにより、読み取り専用クエリをそのようにマークすることは間違いなく合理的です。ただし、これは、操作クエリをトリガーしないことのチェックとしては機能しません(ただし、一部のデータベースは、読み取り専用トランザクション内の INSERT および UPDATE ステートメントを拒否します)。代わりに、readOnly フラグは、パフォーマンスを最適化するためのヒントとして、基盤となる JDBC ドライバーに伝達されます。 |
9.15. 監査
9.15.1. 基本
Spring Data は、エンティティを作成または変更したユーザーと変更がいつ発生したかを透過的に追跡するための高度なサポートを提供します。この機能を利用するには、アノテーションを使用するか、インターフェースを実装することで定義できる監査メタデータをエンティティクラスに装備する必要があります。さらに、必要なインフラストラクチャコンポーネントを登録するには、アノテーション構成または XML 構成のいずれかを介して監査を有効にする必要があります。構成サンプルについては、ストア固有のセクションを参照してください。
作成日と変更日のみを追跡するアプリケーションでは、エンティティに |
アノテーションベースの監査メタデータ
エンティティを作成または変更したユーザーをキャプチャーする @CreatedBy と @LastModifiedBy、および変更が発生したときにキャプチャーする @CreatedDate と @LastModifiedDate を提供します。
class Customer {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
// … further properties omitted
}
ご覧のとおり、キャプチャーする情報に応じて、アノテーションを選択的に適用できます。変更が行われたときにキャプチャーすることを示すアノテーションは、JDK8 の日付と時刻の型、long、Long、レガシー Java Date と Calendar のプロパティで使用できます。
以下のスニペットに示すように、監査メタデータは必ずしもルートレベルのエンティティに存在する必要はありませんが、埋め込まれたエンティティに追加できます(実際に使用されているストアによって異なります)。
class Customer {
private AuditMetadata auditingMetadata;
// … further properties omitted
}
class AuditMetadata {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
}
インターフェースベースの監査メタデータ
アノテーションを使用して監査メタデータを定義したくない場合は、ドメインクラスに Auditable インターフェースを実装させることができます。すべての監査プロパティの setter メソッドを公開します。
AuditorAware
@CreatedBy または @LastModifiedBy を使用する場合、監査インフラストラクチャは何らかの形で現在のプリンシパルを認識する必要があります。そのために、現在のユーザーまたはアプリケーションと対話するシステムが誰であるかをインフラストラクチャに伝えるために実装する必要がある AuditorAware<T> SPI インターフェースを提供します。ジェネリクス型 T は、@CreatedBy または @LastModifiedBy アノテーションが付けられたプロパティの型を定義します。
次の例は、Spring Security の Authentication オブジェクトを使用するインターフェースの実装を示しています。
AuditorAware の実装 class SpringSecurityAuditorAware implements AuditorAware<User> {
@Override
public Optional<User> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
実装は、Spring Security が提供する Authentication オブジェクトにアクセスし、UserDetailsService 実装で作成したカスタム UserDetails インスタンスを検索します。ここでは、UserDetails 実装を介してドメインユーザーを公開しているが、見つかった Authentication に基づいて、どこからでも検索できると想定しています。
ReactiveAuditorAware
リアクティブインフラストラクチャを使用する場合、コンテキスト情報を利用して @CreatedBy または @LastModifiedBy 情報を提供することができます。アプリケーションと対話している現在のユーザーまたはシステムが誰であるかをインフラストラクチャに通知するために実装する必要がある ReactiveAuditorAware<T> SPI インターフェースを提供します。ジェネリクス型 T は、@CreatedBy または @LastModifiedBy でアノテーションが付けられたプロパティがどの型でなければならないかを定義します。
次の例は、リアクティブ Spring Security の Authentication オブジェクトを使用するインターフェースの実装を示しています。
ReactiveAuditorAware の実装 class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {
@Override
public Mono<User> getCurrentAuditor() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
実装は、Spring Security が提供する Authentication オブジェクトにアクセスし、UserDetailsService 実装で作成したカスタム UserDetails インスタンスを検索します。ここでは、UserDetails 実装を介してドメインユーザーを公開しているが、見つかった Authentication に基づいて、どこからでも検索できると想定しています。
9.16. JDBC 監査
監査をアクティブにするには、次の例に示すように、@EnableJdbcAuditing を構成に追加します。
@Configuration
@EnableJdbcAuditing
class Config {
@Bean
AuditorAware<AuditableUser> auditorProvider() {
return new AuditorAwareImpl();
}
}
型 AuditorAware の Bean を ApplicationContext に公開すると、監査インフラストラクチャはそれを自動的に選択し、それを使用してドメイン型に設定する現在のユーザーを決定します。ApplicationContext に複数の実装が登録されている場合、@EnableJdbcAuditing の auditorAwareRef 属性を明示的に設定することにより、使用する実装を選択できます。
9.17. JDBC ロック
Spring Data JDBC は、派生クエリメソッドのロックをサポートします。リポジトリ内の特定の派生クエリメソッドのロックを有効にするには、@Lock でアノテーションを付けます。型 LockMode の必須値は、2 つの値を提供します。読み取り中のデータが変更されないことを保証する PESSIMISTIC_READ と、データを変更するためのロックを取得する PESSIMISTIC_WRITE です。一部のデータベースはこの区別をしていません。その場合、両方のモードは PESSIMISTIC_WRITE と同等です。
interface UserRepository extends CrudRepository<User, Long> {
@Lock(LockMode.PESSIMISTIC_READ)
List<User> findByLastname(String lastname);
}
上記のように、メソッド findByLastname(String lastname) は悲観的な読み取りロックを使用して実行されます。MySQL ダイアレクトでデータベースを使用している場合、これは、たとえば次のクエリになります。
Select * from user u where u.lastname = lastname LOCK IN SHARE MODELockMode.PESSIMISTIC_READ の代わりに、LockMode.PESSIMISTIC_WRITE を使用できます。
付録
付録 A: 用語集
- AOP
アスペクト指向プログラミング
- CRUD
作成、読み取り、更新、削除 - 基本的な永続化操作
- 依存性注入
外部からコンポーネントへのコンポーネントの依存関係を渡すパターン。コンポーネントを解放して、依存関係自体をルックアップします。詳細については、https://en.wikipedia.org/wiki/Dependency_Injection を参照してください。
- JPA
Java Persistence API
- Spring
Java アプリケーションフレームワーク — /projects/spring-framework
付録 B: Populators 名前空間リファレンス
<populator/> 要素
<populator /> 要素を使用すると、Spring Data リポジトリインフラストラクチャを介してデータストアにデータを入力できます。[ 1 ]
| 名前 | 説明 |
|---|---|
| リポジトリからオブジェクトを読み取るためのファイルの場所には、データが入力されます。 |
付録 C: リポジトリクエリキーワード
サポートされているクエリメソッドの件名キーワード
次の表に、述語を表現するために Spring Data リポジトリのクエリ導出メカニズムで一般的にサポートされているサブジェクトキーワードを示します。ここにリストされている一部のキーワードは特定のストアでサポートされていない可能性があるため、サポートされているキーワードの正確なリストについては、ストア固有のドキュメントを参照してください。
| キーワード | 説明 |
|---|---|
| 通常、リポジトリ型、 |
| 射影が存在し、通常は |
| 数値結果を返す射影をカウントします。 |
| 結果なし( |
| クエリ結果を結果の最初の |
| 一意のクエリを使用して、一意の結果のみを返します。その機能がサポートされているかどうかは、ストア固有のドキュメントを参照してください。このキーワードは、 |
サポートされているクエリメソッドの述語キーワードと修飾子
次の表に、Spring Data リポジトリクエリ派生メカニズムで一般的にサポートされている述語キーワードを示します。ただし、ここにリストされている一部のキーワードは特定のストアでサポートされていない可能性があるため、サポートされているキーワードの正確なリストについては、ストア固有のドキュメントを参照してください。
| 論理キーワード | キーワード表現 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
フィルター述語に加えて、次の修飾子のリストがサポートされています。
| キーワード | 説明 |
|---|---|
| 大文字と小文字を区別しない比較のために、述語キーワードとともに使用されます。 |
| すべての適切なプロパティの大文字と小文字を区別しません。クエリメソッド述語のどこかで使用されます。 |
| 静的な並べ替え順序を指定し、その後にプロパティのパスと方向を指定します(例: |
付録 D: リポジトリクエリの戻り値の型
サポートされているクエリの戻り値の型
次の表に、Spring Data リポジトリで一般的にサポートされる戻り値の型を示します。ただし、ここにリストされている一部の型は特定のストアでサポートされていない可能性があるため、サポートされる戻り値の型の正確なリストについてはストア固有のドキュメントを参照してください。
地理空間型(GeoResult、GeoResults、GeoPage など)は、地理空間クエリをサポートするデータストアでのみ使用できます。一部のストアモジュールは、独自の結果ラッパー型を定義する場合があります。 |
| 戻りの型 | 説明 |
|---|---|
| 戻り値がないことを示します。 |
プリミティブ | Java プリミティブ。 |
ラッパーの種類 | Java ラッパー型。 |
| 一意のエンティティ。クエリメソッドが最大で 1 つの結果を返すことを期待します。結果が見つからない場合、 |
|
|
|
|
|
|
| Java 8 または Guava |
| Scala または Vavr |
| Java 8 |
|
|
|
|
Vavr | Vavr コレクション型。詳細については、Vavr コレクションのサポートを参照してください。 |
|
|
| Java 8 |
| 使用可能なデータがさらにあるかどうかを示すサイズのデータチャンク。 |
| 結果の総数などの追加情報を含む |
| 参照場所までの距離などの追加情報を含む結果エントリ。 |
| 参照場所までの平均距離などの追加情報を含む |
| 参照位置までの平均距離など、 |
| リアクティブリポジトリを使用して 0 個または 1 個の要素を放出するプロジェクト Reactor |
| プロジェクト Reactor |
| リアクティブリポジトリを使用して単一の要素を放出する RxJava |
| リアクティブリポジトリを使用して 0 個または 1 個の要素を放出する RxJava |
| リアクティブリポジトリを使用してゼロ、1 つ、多くの要素を放出する RxJava |