© 2018-2020 The original authors.

このドキュメントのコピーは、あなた自身の使用および他者への配布のために作成することができますが、そのようなコピーに料金を請求しないこと、さらに、印刷物または電子的に配布されるかどうかにかかわらず、各コピーにこの著作権表示が含まれていることを条件とします。

序文

Spring Data R2DBC プロジェクトは、コア Spring の概念を、リレーショナルデータベースに R2DBC (英語) ドライバーを使用するソリューションの開発に適用します。行を格納および照会するための高レベルの抽象化として DatabaseClient を提供します。

このドキュメントは、Spring Data-R2DBC サポートのリファレンスガイドです。R2DBC モジュールの概念とセマンティクスを説明します。

このセクションでは、Spring とデータベースの基本的な概要を説明します。

1. Spring の学習

Spring Data は、以下を含む Spring フレームワークのコア機能を使用します。

Spring API を知る必要はありませんが、その背後にある概念を理解することは重要です。最低限、Inversion of Control (IoC) の背後にある概念は知っておくべきであり、使用する IoC コンテナーにも精通している必要があります。

Spring コンテナーの IoC サービスを呼び出す必要なしに、R2DBC サポートのコア機能を直接使用できます。これは JdbcTemplate によく似ており、Spring コンテナーの他のサービスなしで「スタンドアロン」で使用できます。リポジトリのサポートなど、Spring Data R2DBC のすべての機能を使用するには、Spring を使用するようにライブラリの一部を構成する必要があります。

Spring の詳細については、Spring Framework の詳細を説明する包括的なドキュメントを参照してください。このテーマに関する記事、ブログエントリ、書籍が多数あります。詳細については、Spring フレームワークのホームページを参照してください。

2. R2DBC とは何ですか?

R2DBC (英語) は、Reactive Relational Database Connectivity の頭字語です。R2DBC は、ドライバーベンダーがリレーショナルデータベースにアクセスするために実装するリアクティブ API を宣言する API 仕様イニシアチブです。

R2DBC が作成された理由に関する理由の 1 つは、少ないスレッドで並行処理し、より少ないハードウェアリソースでスケーリングする (英語) ためのノンブロッキングアプリケーションスタックの必要性です。JDBC は完全なブロッキング API であるため、標準化されたリレーショナルデータベースアクセス API、つまり JDBC を再利用しても、このニーズを満たすことはできません。ThreadPool を使用したブロッキング動作を補正する試みは、限られた用途しかありません。

もう 1 つの理由は、ほとんどのアプリケーションがリレーショナルデータベースを使用してデータを保存することです。いくつかの NoSQL データベースベンダーは、データベースにリアクティブデータベースクライアントを提供していますが、NoSQL への移行はほとんどのプロジェクトのオプションではありません。これが、ノンブロッキングデータベースドライバーの基盤として機能する新しい共通 API の動機でした。オープンソースエコシステムはさまざまなノンブロッキングリレーショナルデータベースドライバーの実装をホストしますが、各クライアントにはベンダー固有の API が付属しているため、これらのライブラリの上に汎用レイヤーを配置することはできません。

3. リアクティブとは何ですか?

「リアクティブ」という用語は、変更、可用性、および処理可能性への反応に基づいて構築されたプログラミングモデルを指します。I-O イベントに反応するネットワークコンポーネント、マウスイベントに反応する UI コントローラー、利用可能になるリソースなどです。その意味で、ノンブロッキングはリアクティブです。なぜなら、ブロックされるのではなく、操作が完了するかデータが利用可能になったときに通知に反応するモードになっているからです。

Spring チームがリアクティブと関連付ける別の重要なメカニズムもあります。それは、ノンブロッキングバックプレッシャーです。同期命令型コードでは、呼び出しをブロックすることは、呼び出し側を待機させるバックプレッシャーの自然な形として機能します。ノンブロッキングコードでは、高速プロデューサーがその宛先を圧倒しないように、イベントのレートを制御することが不可欠になります。

バックプレッシャーを伴う非同期コンポーネント間の相互作用を定義する Reactive Streams は小さなスペックです (GitHub) (Java 9 でも採用 )。例:データリポジトリ(Publisher (英語) として機能)は、HTTP サーバー(Subscriber (英語) として機能)がレスポンスに書き込むことができるデータを生成できます。Reactive Streams の主な目的は、パブリッシャーがデータを生成する速度をサブスクライバーに制御させることです。

4. リアクティブ API

Reactive Streams は相互運用性に重要なロールを果たします。ライブラリやインフラストラクチャコンポーネントには関心がありますが、低レベルすぎるため、アプリケーション API としてはあまり有用ではありません。アプリケーションは、非同期ロジックを構成するために、より高レベルでより関数 API を必要とします。これは、Java 8 ストリーム API に似ていますが、テーブルだけではありません。これは、リアクティブライブラリが果たすロールです。

プロジェクト Reactor (GitHub) は、Spring Data R2DBC に最適なリアクティブライブラリです。Mono (英語) および Flux (英語) API タイプを提供し、演算子の ReactiveX ボキャブラリに合わせた豊富な演算子セットを介して 0..1 (Mono)および 0..N (Flux)のデータシーケンスを処理します。Reactor は Reactive Streams ライブラリであるため、そのすべてのオペレーターはノンブロッキングバックプレッシャーをサポートしています。Reactor は、サーバー側の Java に重点を置いています。Spring との緊密な協力により開発されます。

Spring Data R2DBC は、コア依存関係としてプロジェクト Reactor を必要としますが、Reactive Streams 仕様を通じて他のリアクティブライブラリと相互運用可能です。原則として、Spring Data R2DBC リポジトリは、プレーンな Publisher を入力として受け入れ、それを内部で Reactor タイプに適合させ、それを使用して、Mono または Flux のいずれかを出力として返します。任意の Publisher を入力として渡し、出力に操作を適用できますが、別のリアクティブライブラリで使用できるように出力を調整する必要があります。可能な場合はいつでも、Spring Data は RxJava または別のリアクティブライブラリの使用に透過的に適応します。

5. 要件

Spring Data R2DBC 1.x バイナリには以下が必要です。

6. 追加のヘルプリソース

新しいフレームワークを学ぶことは必ずしも簡単ではありません。このセクションでは、Spring Data R2DBC モジュールを開始するためのわかりやすいガイドと思われるものを提供しようとしています。ただし、問題が発生した場合やアドバイスが必要な場合は、次のいずれかのリンクを使用してください。

コミュニティフォーラム

スタックオーバーフロー (英語) 上の Spring Data は、すべての Spring Data(R2DBC だけでなく)ユーザーが情報を共有し、互いに助け合うためのタグです。登録は投稿にのみ必要であることに注意してください。

専門サポート

Spring Data および Spring を開発している Pivotal Sofware, Inc では、レスポンス時間が保証されたプロフェッショナルなソースからのサポートを提供しています。

7. 開発のフォロー

  • Spring Data R2DBC ソースコードリポジトリ、ナイトリービルド、およびスナップショットアーティファクトの詳細については、Spring Data R2DBC ホームページを参照してください

  • スタックオーバーフロー (英語) のコミュニティを通じて開発者と対話することにより、Spring Data が Spring コミュニティのニーズに最適に対応できるようにすることができます。

  • バグが発生した場合、または改善を提案したい場合は、Spring Data R2DBC 課題追跡 (GitHub) システムでチケットを作成してください。

  • Spring エコシステムの最新ニュースや最新情報を入手するには、Spring コミュニティポータルに登録してください。

  • Twitter(SpringData (英語) )で Spring ブログ (英語) または Spring Data プロジェクトチームをフォローすることもできます。

8. プロジェクトメタデータ

9. 注目の新機能

9.1. Spring Data R2DBC 1.2.0 の新機能

  • Spring Data R2DBC DatabaseClient を非推奨にし、非推奨の API を廃止して Spring R2DBC を採用します。詳細については、移行ガイドを参照してください。

  • エンティティコールバックのサポート。

  • 監査から @EnableR2dbcAuditing

  • 永続性コンストラクターでの @Value のサポート。

9.2. Spring Data R2DBC 1.1.0 の新機能

9.3. Spring Data R2DBC 1.0.0 の新機能

  • R2DBC 0.8.0.RELEASE にアップグレードします。

  • 影響を受ける行カウントを消費するためのクエリメソッドの @Modifying アノテーション。

  • 行がデータベースに存在しない場合、関連付けられた ID を持つリポジトリ save(…) は TransientDataAccessException で完了します。

  • 接続シングルトンを使用したテスト用に SingleConnectionConnectionFactory を追加しました。

  • @Query での SpEL 式のサポート。

  • AbstractRoutingConnectionFactory を介した ConnectionFactory ルーティング。

  • ResourceDatabasePopulator および ScriptUtils によるスキーマの初期化のためのユーティリティ。

  • TransactionDefinition を介した Auto-Commit および分離レベルコントロールの伝播とリセット。

  • エンティティレベルのコンバーターのサポート。

  • 具象ジェネリックおよびコルーチンの Kotlin 拡張。

  • ダイアレクトを登録するためのプラグ可能なメカニズムを追加します。

  • 名前付きパラメーターのサポート。

  • DatabaseClient による初期 R2DBC サポート。

  • TransactionalDatabaseClient による初期トランザクションサポート。

  • R2dbcRepository による初期 R2DBC リポジトリサポート。

  • Postgres および Microsoft SQL Server の最初のダイアレクトサポート。

10. 依存関係

個々の Spring Data モジュールの開始日が異なるため、それらのほとんどには異なるメジャーバージョン番号とマイナーバージョン番号が付いています。互換性のあるものを見つける最も簡単な方法は、定義された互換性のあるバージョンとともに提供される Spring Data Release Train BOM に依存することです。Maven プロジェクトでは、次のように、POM の <dependencyManagement /> セクションでこの依存関係を宣言します。

例 1: Spring Data リリーストレイン BOM の使用
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2020.0.0-RC2</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

現在のリリーストレインバージョンは 2020.0.0-RC2 です。トレインバージョンでは、パターン YYYY.MINOR.MICROcalver (英語) を使用しています。バージョン名は、GA リリースとサービスリリースでは ${calver} に従い、他のすべてのバージョンでは次のパターンに従います。${calver}-${modifier}modifier は次のいずれかになります。

  • SNAPSHOT: 現在のスナップショット

  • M1M2 など:マイルストーン

  • RC1RC2 など:リリース候補

BOM の使用例は、Spring Data サンプルリポジトリ (GitHub) にあります。これを配置すると、次のように、<dependencies /> ブロックにバージョンなしで使用する Spring Data モジュールを宣言できます。

例 2: Spring Data モジュールへの依存関係の宣言
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

10.1. Spring Boot を使用した依存関係管理

Spring Boot は、Spring Data モジュールの最新バージョンを選択します。それでも新しいバージョンにアップグレードする場合は、プロパティ spring-data-releasetrain.version を、使用するトレインバージョンとイテレーションに構成します。

10.2. Spring Framework

Spring Data モジュールの現在のバージョンには、バージョン 5.3.0-RC2 以上の Spring Framework が必要です。モジュールは、そのマイナーバージョンの古いバグ修正バージョンでも動作する場合があります。ただし、その世代内の最新バージョンを使用することを強くお勧めします。

11. Spring Data リポジトリの操作

Spring Data リポジトリの抽象化のゴールは、さまざまな永続ストアのデータアクセスレイヤーを実装するために必要な定型コードの量を大幅に削減することです。

Spring Data リポジトリのドキュメントとモジュール

この章では、Spring Data リポジトリのコア概念とインターフェースについて説明します。この章の情報は、Spring Data Commons モジュールから引用されています。Java Persistence API(JPA)モジュールの構成とコードサンプルを使用します。XML 名前空間宣言と型を、使用する特定のモジュールに相当するものに拡張する必要があります。「[repositories.namespace-reference]」は、リポジトリ API をサポートするすべての Spring Data モジュールでサポートされる XML 構成をカバーしています。「リポジトリクエリキーワード」は、一般的なリポジトリ抽象化でサポートされるクエリメソッドキーワードをカバーしています。モジュールの特定の機能の詳細については、このドキュメントのそのモジュールに関する章を参照してください。

11.1. コアコンセプト

Spring Data リポジトリ抽象化の中心的なインターフェースは Repository です。ドメインクラスと、ドメインクラスの ID タイプを型引数として管理します。このインターフェースは、主にマーカーインターフェースとして機能し、使用するタイプをキャプチャーし、このインターフェースを継承するインターフェースを発見できます。CrudRepository は、管理されているエンティティクラスに洗練された CRUD 機能を提供します。

例 3: 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 のエンティティが存在するかどうかを示します。
また、JpaRepository や MongoRepository などの永続化技術固有の抽象化も提供します。これらのインターフェースは CrudRepository を継承し、CrudRepository などのかなり汎用的な永続化テクノロジーにとらわれないインターフェースに加えて、基礎となる永続化テクノロジーの機能を公開します。

CrudRepository の上部には、エンティティへのページ分割されたアクセスを容易にするための追加のメソッドを追加する PagingAndSortingRepository 抽象化があります。

例 4: PagingAndSortingRepository インターフェース
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<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));

クエリメソッドに加えて、カウントクエリと削除クエリの両方のクエリ派生を使用できます。次のリストは、派生カウントクエリのインターフェース定義を示しています。

例 5: 派生カウントクエリ
interface UserRepository extends CrudRepository<User, Long> {

  long countByLastname(String lastname);
}

次のリストは、派生削除クエリのインターフェース定義を示しています。

例 6: 派生削除クエリ
interface UserRepository extends CrudRepository<User, Long> {

  long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);
}

11.2. クエリメソッド

通常、標準の CRUD 機能リポジトリには、基になるデータストアに対するクエリがあります。Spring Data では、これらのクエリを宣言することは 4 ステップのプロセスになります。

  1. 次の例に示すように、リポジトリまたはそのサブインターフェースの 1 つを継承するインターフェースを宣言し、処理するドメインクラスと ID タイプに入力します。

    interface PersonRepository extends Repository<Person, Long> {  …  }
  2. インターフェースでクエリメソッドを宣言します。

    interface PersonRepository extends Repository<Person, Long> {
      List<Person> findByLastname(String lastname);
    }
  3. JavaConfig または XML 構成を使用して、Spring をセットアップして、これらのインターフェースのプロキシインスタンスを作成します。

    1. Java 構成を使用するには、次のようなクラスを作成します。

      import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
      
      @EnableJpaRepositories
      class Config {  …  }
    2. XML 構成を使用するには、次のような Bean を定義します。

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:jpa="http://www.springframework.org/schema/data/jpa"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/data/jpa
           https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
      
         <jpa:repositories base-package="com.acme.repositories"/>
      
      </beans>

    この例では、JPA 名前空間が使用されています。リポジトリの抽象化を他のストアに使用する場合、これをストアモジュールの適切なネームスペース宣言に変更する必要があります。つまり、たとえば mongodb を優先して jpa を交換する必要があります。

    + また、アノテーション付きクラスのパッケージがデフォルトで使用されるため、JavaConfig バリアントはパッケージを明示的に構成しないことに注意してください。スキャンするパッケージをカスタマイズするには、データストア固有のリポジトリの @Enable${store}Repositories -annotation の basePackage …  属性のいずれかを使用します。

  4. 次の例に示すように、リポジトリインスタンスを挿入して使用します。

    class SomeClient {
    
      private final PersonRepository repository;
    
      SomeClient(PersonRepository repository) {
        this.repository = repository;
      }
    
      void doSomething() {
        List<Person> persons = repository.findByLastname("Matthews");
      }
    }

以下のセクションでは、各ステップについて詳しく説明します。

11.3. リポジトリインターフェースの定義

最初に、ドメインクラス固有のリポジトリインターフェースを定義します。インターフェースは Repository を継承し、ドメインクラスと ID の型を指定する必要があります。そのドメインタイプの CRUD メソッドを公開する場合は、Repository ではなく CrudRepository を継承します。

11.3.1. リポジトリ定義の微調整

通常、リポジトリインターフェースは RepositoryCrudRepository または PagingAndSortingRepository を継承します。あるいは、Spring Data インターフェースを継承したくない場合は、@RepositoryDefinition を使用してリポジトリインターフェースにアノテーションを付けることもできます。CrudRepository を継承すると、エンティティを操作するためのメソッドの完全なセットが公開されます。公開するメソッドを選択する場合、CrudRepository から公開するメソッドをドメインリポジトリにコピーします。

そうすることで、提供された Spring Data リポジトリ機能の上に独自の抽象化を定義できます。

次の例は、CRUD メソッド(この場合は findById および save)を選択的に公開する方法を示しています。

例 7: CRUD メソッドを選択的に公開する
@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 が実行時にインスタンスを作成してはならないすべてのリポジトリインターフェースに、そのアノテーションを必ず追加してください。

11.3.2. 複数の Spring Data モジュールでリポジトリを使用する

定義済みスコープ内のすべてのリポジトリインターフェースが Spring Data モジュールにバインドされているため、アプリケーションで一意の Spring Data モジュールを使用すると、物事が簡単になります。アプリケーションによっては、複数の Spring Data モジュールを使用する必要がある場合があります。そのような場合、リポジトリ定義は永続化テクノロジーを区別する必要があります。クラスパスで複数のリポジトリファクトリを検出すると、Spring Data は厳密なリポジトリ構成モードに入ります。厳密な構成では、リポジトリまたはドメインクラスの詳細を使用して、リポジトリ定義の Spring Data モジュールバインディングについて決定します。

  1. リポジトリ定義がモジュール固有のリポジトリを継承する場合、特定の Spring Data モジュールの有効な候補です。

  2. ドメインクラスにモジュール固有のタイプアノテーションが付けられている場合、特定の Spring Data モジュールの有効な候補です。Spring Data モジュールは、サードパーティのアノテーション(JPA の @Entity など)を受け入れるか、独自のアノテーション(Spring Data MongoDB および Spring Data Elasticsearch の @Document など)を提供します。

次の例は、モジュール固有のインターフェース(この場合は JPA)を使用するリポジトリを示しています。

例 8: モジュール固有のインターフェースを使用したリポジトリ定義
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 モジュールの有効な候補です。

次の例は、汎用インターフェースを使用するリポジトリを示しています。

例 9: ジェネリックインターフェースを使用したリポジトリ定義
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 を区別できません。

次の例は、アノテーション付きのドメインクラスを使用するリポジトリを示しています。

例 10: アノテーション付きのドメインクラスを使用したリポジトリ定義
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 を参照します。

次の悪い例は、アノテーションが混在するドメインクラスを使用するリポジトリを示しています。

例 11: アノテーションが混在するドメインクラスを使用したリポジトリ定義
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 ベースの構成の基本パッケージは必須です。

次の例は、基本パッケージのアノテーション駆動型の構成を示しています。

例 12: 基本パッケージのアノテーション駆動型構成
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration {  …  }

11.4. クエリメソッドの定義

リポジトリプロキシには、メソッド名からストア固有のクエリを派生させる 2 つの方法があります。

  • メソッド名から直接クエリを導出します。

  • 手動で定義されたクエリを使用します。

利用可能なオプションは、実際のストアによって異なります。ただし、作成する実際のクエリを決定する戦略が必要です。次のセクションでは、使用可能なオプションについて説明します。

11.4.1. クエリ検索戦略

クエリを解決するリポジトリインフラストラクチャでは、次の戦略を使用できます。XML 構成では、query-lookup-strategy 属性を使用して名前空間で戦略を構成できます。Java 構成の場合、Enable${store}Repositories アノテーションの queryLookupStrategy 属性を使用できます。特定のデータストアでは一部の戦略がサポートされていない場合があります。

  • CREATE は、クエリメソッド名からストア固有のクエリを作成しようとします。一般的なアプローチは、メソッド名から既知のプレフィックスの特定のセットを削除し、メソッドの残りを解析することです。クエリ構築の詳細については、「クエリ作成」を参照してください。

  • USE_DECLARED_QUERY は宣言されたクエリを見つけようとし、見つからない場合は例外をスローします。クエリは、アノテーションによってどこかで定義するか、他の手段で宣言できます。特定のストアのドキュメントを参照して、そのストアで利用可能なオプションを見つけてください。リポジトリインフラストラクチャが、ブートストラップ時にメソッドの宣言されたクエリを見つけられない場合、失敗します。

  • CREATE_IF_NOT_FOUND (デフォルト)は、CREATE と USE_DECLARED_QUERY を組み合わせたものです。最初に宣言されたクエリを検索し、宣言されたクエリが見つからない場合は、カスタムメソッド名ベースのクエリを作成します。これはデフォルトのルックアップ戦略であるため、明示的に何も設定しない場合に使用されます。メソッド名による迅速なクエリ定義だけでなく、必要に応じて宣言されたクエリを導入することにより、これらのクエリのカスタムチューニングも可能です。

11.4.2. クエリ作成

Spring Data リポジトリインフラストラクチャに組み込まれたクエリビルダメカニズムは、リポジトリのエンティティに対して制約クエリを構築できます。このメカニズムは、メソッドからプレフィックス find … Byread … Byquery … Bycount … By および get … By を取り除き、残りの解析を開始します。導入句には、作成するクエリに個別のフラグを設定する Distinct などの追加の式を含めることができます。ただし、最初の By は区切り文字として機能し、実際の条件の開始を示します。非常に基本的なレベルでは、エンティティプロパティの条件を定義し、And および Or で連結できます。次の例は、いくつかのクエリを作成する方法を示しています。

例 13: メソッド名からのクエリ作成
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);
}

メソッドの解析の実際の結果は、クエリを作成する永続ストアによって異なります。ただし、注意すべき一般的な事項がいくつかあります。

  • 式は通常、連結可能な演算子と組み合わせたプロパティトラバーサルです。プロパティ式を AND および OR と組み合わせることができます。また、プロパティ式の BetweenLessThanGreaterThan や Like などの演算子のサポートも取得できます。サポートされる演算子はデータストアによって異なる場合があるため、リファレンスドキュメントの適切な部分を参照してください。

  • メソッドパーサーは、個々のプロパティ(たとえば findByLastnameIgnoreCase(…))または大文字と小文字の区別をサポートするタイプのすべてのプロパティ(通常は String インスタンス - findByLastnameAndFirstnameAllIgnoreCase(…) など)の IgnoreCase フラグの設定をサポートします。ケースの無視がサポートされているかどうかはストアによって異なるため、ストア固有のクエリメソッドについては、リファレンスドキュメントの関連セクションを参照してください。

  • プロパティを参照するクエリメソッドに OrderBy 句を追加し、並べ替え方向(Asc または Desc)を提供することにより、静的な順序を適用できます。動的な並べ替えをサポートするクエリメソッドを作成するには、「特別なパラメーター処理」を参照してください。

11.4.3. プロパティ式

前の例に示すように、プロパティ式は管理対象エンティティの直接プロパティのみを参照できます。クエリの作成時に、解析されたプロパティが管理対象ドメインクラスのプロパティであることを既に確認しています。ただし、ネストされたプロパティを走査して制約を定義することもできます。次のメソッドシグネチャーを検討してください。

List<Person> findByAddressZipCode(ZipCode zipCode);

Person に Address があり、ZipCode があるとします。その場合、メソッドはプロパティトラバーサル x.address.zipCode を作成します。解決アルゴリズムは、パーツ全体(AddressZipCode)をプロパティとして解釈することから始まり、その名前(大文字ではない)のプロパティのドメインクラスをチェックします。アルゴリズムが成功した場合、そのプロパティを使用します。そうでない場合、アルゴリズムはキャメルケース部分のソースを右側から頭と尾に分割し、対応するプロパティ(この例では AddressZip と Code)を見つけようとします。アルゴリズムがそのヘッドを持つプロパティを見つけると、テールを取得し、そこからツリーを構築し続け、先ほど説明したメソッドでテールを分割します。最初の分割が一致しない場合、アルゴリズムは分割ポイントを左(AddressZipCode)に移動して続行します。

これはほとんどの場合に機能するはずですが、アルゴリズムが間違ったプロパティを選択する可能性があります。Person クラスにも addressZip プロパティがあるとします。アルゴリズムは最初の分割ラウンドですでに一致し、間違ったプロパティを選択して失敗します(addressZip のタイプにはおそらく code プロパティがないため)。

このあいまいさを解決するには、メソッド名内で _ を使用して、トラバーサルポイントを手動で定義します。メソッド名は次のようになります。

List<Person> findByAddress_ZipCode(ZipCode zipCode);

アンダースコア文字を予約文字として扱うため、標準の Java 命名規則に従うことを強くお勧めします(つまり、プロパティ名にアンダースコアを使用せず、代わりにキャメルケースを使用します)。

11.4.4. 特別なパラメーター処理

クエリでパラメーターを処理するには、前の例で既に見たようにメソッドパラメーターを定義します。それに加えて、インフラストラクチャは Pageable や Sort などの特定の特定のタイプを認識し、ページネーションとソートをクエリに動的に適用します。次の例は、これらの機能を示しています。

例 14: 照会メソッドで PageableSlice および 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 つの式にまとめることができます。

例 15: ソート式の定義
Sort sort = Sort.by("firstname").ascending()
  .and(Sort.by("lastname").descending());

タイプセーフなソート式の定義方法については、タイプからソート式を定義し、メソッド参照を使用してソートするプロパティを定義します。

例 16: タイプセーフ API を使用してソート式を定義する
TypedSort<Person> person = Sort.sort(Person.class);

Sort sort = person.by(Person::getFirstname).ascending()
  .and(person.by(Person::getLastname).descending());
TypedSort.by(…) はランタイムプロキシを利用します。通常は CGlib を使用します。これにより、Gral VM Native などのツールを使用すると、ネイティブイメージのコンパイルが妨げられる可能性があります。

ストアの実装が Querydsl をサポートしている場合、生成されたメタモデルタイプを使用してソート式を定義することもできます。

例 17: Querydsl API を使用してソート式を定義する
QSort sort = QSort.by(QPerson.firstname.asc())
  .and(QSort.by(QPerson.lastname.desc()));

11.4.5. クエリ結果の制限

first または top キーワードを使用することで、クエリメソッドの結果を制限できます。これらのキーワードは互換的に使用できます。オプションの数値を top または first に追加して、返される結果の最大サイズを指定できます。数を省略すると、結果サイズ 1 が想定されます。次の例は、クエリサイズを制限する方法を示しています。

例 18: 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」最大エレメントの照会メソッドを表現できます。

11.4.6. コレクションまたはイテラブルを返すリポジトリメソッド

複数の結果を返すクエリメソッドは、標準の Java IterableListSet を使用できます。さらに、Spring Data の StreamableIterable のカスタム拡張、および Vavr (英語) が提供するコレクションタイプの返送をサポートしています。

Streamable をクエリメソッドの戻り値の型として使用する

Streamable は、Iterable または任意のコレクションタイプの代替として使用できます。非並列 Stream (Iterable にない)にアクセスするための便利なメソッド、直接  … .filter(…) および  … .map(…) を要素に渡して Streamable を他の連結する機能を提供します。

例 19: 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 では、これらのラッパータイプが以下の条件を満たす場合、クエリメソッドの戻り値のタイプとしてこれらのラッパータイプを使用できるため、この追加ステップを回避できます。

  1. タイプは Streamable を実装します。

  2. この型は、Streamable を引数として取得する、of(…) または valueOf(…) という名前のコンストラクターまたは静的ファクトリメソッドを公開します。

サンプルの使用例は次のようになります。

class Product { (1)
  MonetaryAmount getPrice() {  …  }
}

@RequiredArgConstructor(staticName = "of")
class Products implements Streamable<Product> { (2)

  private Streamable<Product> streamable;

  public MonetaryAmount getTotal() { (3)
    return streamable.stream() //
      .map(Priced::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }
}

interface ProductRepository implements Repository<Product, Long> {
  Products findAllByDescriptionContaining(String text); (4)
}
1 製品の価格にアクセスするための API を公開する Product エンティティ。
2Products.of(…) (Lombok アノテーションを介して作成されたファクトリメソッド)を介して構築できる Streamable<Product> のラッパータイプ。
3 ラッパータイプは、Streamable<Product> の新しい値を計算する追加の API を公開します。
4 そのラッパー型は、クエリメソッドの戻り型として直接使用できます。Stremable<Product> を返し、リポジトリクライアントで手動でラップする必要はありません。
Vavr コレクションのサポート

Vavr (英語) は、Java で関数型プログラミングの概念を取り入れるライブラリです。クエリメソッドの戻り値の型として使用できるコレクション型のカスタムセットが付属しています。

Vavr コレクションタイプ 使用される Vavr 実装タイプ 有効な Java ソースタイプ

io.vavr.collection.Seq

io.vavr.collection.List

java.util.Iterable

io.vavr.collection.Set

io.vavr.collection.LinkedHashSet

java.util.Iterable

io.vavr.collection.Map

io.vavr.collection.LinkedHashMap

java.util.Map

最初の列の型(またはそのサブタイプ)は quer メソッドの戻り型として使用でき、実際のクエリ結果(3 番目の列)の Java 型に応じて実装型として使用される 2 番目の列の型を取得します。または、Traversable (Vavr Iterable 相当)を宣言して、実際の戻り値から実装クラスを導出できます。つまり、java.util.List は Vavr List/Seq に、java.util.Set は Vavr LinkedHashSet/Set などになります。

11.4.7. リポジトリメソッドの null 処理

Spring Data 2.0, リポジトリの時点で、個々の集約インスタンスを返す CRUD メソッドは、Java 8 の Optional を使用して、値が存在しない可能性があることを示します。さらに、Spring Data はクエリメソッドで次のラッパータイプを返すことをサポートしています。

  • com.google.common.base.Optional

  • scala.Option

  • io.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 メタアノテーションにより、IDEAEclipse (英語) 、および Kotlin (英語) などのツールベンダーは、Spring アノテーションのサポートをハードコードすることなく、一般的な方法で null 安全サポートを提供できます。クエリメソッドの nullability 制約の実行時チェックを有効にするには、次の例に示すように、package-info.java で Spring の @NonNullApi を使用して、パッケージレベルで null 禁止を有効にする必要があります。

例 20: package-info.java で非 null 可能性を宣言する
@org.springframework.lang.NonNullApi
package com.acme;

null 以外のデフォルトが設定されると、リポジトリクエリメソッドの呼び出しは、null 許容性の制約について実行時に検証されます。クエリの実行結果が定義された制約に違反すると、例外がスローされます。これは、メソッドが null を返すが、null 不可として宣言されている場合に発生します(リポジトリが存在するパッケージで定義されたアノテーションのデフォルト)。null 可能結果に再度オプトインする場合は、個々のメソッドで @Nullable を選択的に使用します。このセクションの冒頭で説明した結果ラッパータイプの使用は、引き続き期待どおりに機能します。空の結果は、不在を表す値に変換されます。

次の例は、今説明したいくつかの手法を示しています。

例 21: さまざまな null 値制約を使用する
package com.acme;                                                       (1)

import org.springframework.lang.Nullable;

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 リポジトリは、言語メカニズムを使用してこれらの制約を定義し、次のように同じランタイムチェックを適用します。

例 22: Kotlin リポジトリでの null 可能性制約の使用
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 を返します。

11.4.8. クエリ結果のストリーミング

Java 8 Stream<T> を戻り型として使用することにより、クエリメソッドの結果をインクリメンタルに処理できます。次の例に示すように、クエリ結果を Stream データストアにラップする代わりに、ストリーミングを実行するために特定のメソッドが使用されます。

例 23: Java 8 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 を手動で閉じることができます。
例 24: Stream<T> を使用すると、try-with-resources ブロックが生成されます
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}
現在、すべての Spring Data モジュールが戻り型として Stream<T> をサポートしているわけではありません。

11.4.9. 非同期クエリの結果

Spring の非同期メソッド実行機能を使用して、リポジトリクエリを非同期に実行できます。これは、Spring TaskExecutor に送信されたタスクで実際のクエリ実行が行われている間に、メソッドが呼び出し時にすぐに戻ることを意味します。非同期クエリの実行は、リアクティブクエリの実行とは異なるため、混在させないでください。リアクティブサポートの詳細については、ストア固有のドキュメントを参照してください。次の例は、いくつかの非同期クエリを示しています。

@Async
Future<User> findByFirstname(String firstname);               (1)

@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)

@Async
ListenableFuture<User> findOneByLastname(String lastname);    (3)
1 戻り型として java.util.concurrent.Future を使用します。
2 戻り型として Java 8 java.util.concurrent.CompletableFuture を使用します。
3 戻り型として org.springframework.util.concurrent.ListenableFuture を使用します。

11.5. リポジトリインスタンスの作成

このセクションでは、定義されたリポジトリインターフェースのインスタンスと Bean 定義を作成します。そのための 1 つの方法は、リポジトリメカニズムをサポートする各 Spring Data モジュールに同梱されている Spring 名前空間を使用することです。ただし、通常は Java 構成を使用することをお勧めします。

11.5.1. XML 構成

次の例に示すように、各 Spring Data モジュールには、Spring がスキャンする基本パッケージを定義できる repositories 要素が含まれています。

例 25: XML を介した Spring Data リポジトリの有効化
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.springframework.org/schema/data/jpa"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

  <repositories base-package="com.acme.repositories" />

</beans:beans>

上記の例では、Spring は com.acme.repositories とそのすべてのサブパッケージをスキャンして、Repository を継承するインターフェースまたはそのサブインターフェースの 1 つを探すように指示されています。見つかったインターフェースごとに、インフラストラクチャは永続化テクノロジー固有の FactoryBean を登録して、クエリメソッドの呼び出しを処理する適切なプロキシを作成します。各 Bean は、インターフェース名から派生した Bean 名で登録されるため、UserRepository のインターフェースは userRepository で登録されます。ネストされたリポジトリインターフェースの Bean 名には、囲む型名がプレフィックスとして付加されます。base-package 属性ではワイルドカードを使用できるため、スキャンしたパッケージのパターンを定義できます。

フィルターを使う

デフォルトでは、インフラストラクチャは、設定されたベースパッケージにある永続化技術固有の Repository サブインターフェースを継承するすべてのインターフェースをピックアップし、Bean インスタンスを作成します。ただし、Bean インスタンスを作成するインターフェースをより詳細に制御したい場合があります。そのためには、<repositories /> 要素内で <include-filter /> および <exclude-filter /> 要素を使用します。セマンティクスは、Spring のコンテキスト名前空間の要素とまったく同じです。詳細については、これらの要素の Spring リファレンスドキュメントを参照してください。

例:特定のインターフェースをリポジトリ Bean としてインスタンス化から除外するには、次の構成を使用できます。

例 26: exclude-filter 要素を使用する
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

上記の例は、SomeRepository で終わるすべてのインターフェースをインスタンス化から除外します。

11.5.2. JavaConfig

リポジトリインフラストラクチャは、JavaConfig クラスでストア固有の @Enable${store}Repositories アノテーションを使用してトリガーすることもできます。Spring コンテナーの Java ベースの構成の概要については、Spring リファレンスドキュメントの JavaConfig を参照してください。

Spring Data リポジトリを有効にするサンプル構成は次のようになります。

例 27: アノテーションベースのリポジトリ構成のサンプル
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    //  …
  }
}
上記の例では、JPA 固有のアノテーションを使用しています。これは、実際に使用するストアモジュールに応じて変更します。同じことが EntityManagerFactory Bean の定義にも当てはまります。ストア固有の構成について説明しているセクションを参照してください。

11.5.3. スタンドアロン使用

Spring コンテナーの外部(たとえば、CDI 環境)でリポジトリインフラストラクチャを使用することもできます。クラスパスにはまだ Spring ライブラリがいくつか必要ですが、通常、プログラムでリポジトリを設定することもできます。リポジトリサポートを提供する Spring Data モジュールには、次のように使用できる永続化技術固有の RepositoryFactory が付属しています。

例 28: リポジトリファクトリのスタンドアロン使用
RepositoryFactorySupport factory =  …  // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

11.6. Spring Data リポジトリのカスタム実装

このセクションでは、リポジトリのカスタマイズと、フラグメントが複合リポジトリを形成する方法について説明します。

クエリメソッドが別の動作を必要とする場合、またはクエリ派生によって実装できない場合は、カスタム実装を提供する必要があります。Spring Data リポジトリを使用すると、カスタムリポジトリコードを提供し、一般的な CRUD 抽象化およびクエリメソッド機能と統合できます。

11.6.1. 個々のリポジトリのカスタマイズ

リポジトリにカスタム機能を追加するには、次の例に示すように、最初にフラグメントインターフェースとカスタム機能の実装を定義する必要があります。

例 29: カスタムリポジトリ機能のインターフェース
interface CustomizedUserRepository {
  void someCustomMethod(User user);
}
例 30: カスタムリポジトリ機能の実装
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}
フラグメントインターフェースに対応するクラス名の最も重要な部分は、Impl 後置です。

実装自体は Spring Data に依存せず、通常の Spring Bean にすることができます。そのため、標準の依存性注入動作を使用して、他の Bean(JdbcTemplate など)への参照を注入したり、アスペクトに参加したりすることができます。

次に、次の例に示すように、リポジトリインターフェースでフラグメントインターフェースを継承できます。

例 31: リポジトリインターフェースの変更
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

リポジトリインターフェースでフラグメントインターフェースを拡張すると、CRUD とカスタム機能が組み合わされ、クライアントで使用できるようになります。

Spring Data リポジトリは、リポジトリ構成を形成するフラグメントを使用して実装されます。フラグメントは、ベースリポジトリ、関数側面(QueryDsl など)、およびカスタムインターフェースとその実装です。リポジトリインターフェースにインターフェースを追加するたびに、フラグメントを追加して構成を強化します。ベースリポジトリおよびリポジトリアスペクトの実装は、各 Spring Data モジュールによって提供されます。

次の例は、カスタムインターフェースとその実装を示しています。

例 32: 実装のフラグメント
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 を継承するカスタムリポジトリのインターフェースを示しています。

例 33: リポジトリインターフェースの変更
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

  // Declare query methods here
}

リポジトリは、宣言の順序でインポートされる複数のカスタム実装で構成されます。カスタム実装は、基本実装およびリポジトリの側面よりも優先度が高くなります。この順序付けにより、ベースリポジトリおよびアスペクトメソッドをオーバーライドし、2 つのフラグメントが同じメソッドシグネチャーを提供する場合のあいまいさを解決できます。リポジトリフラグメントは、単一のリポジトリインターフェースでの使用に限定されません。複数のリポジトリがフラグメントインターフェースを使用し、異なるリポジトリでカスタマイズを再利用できる場合があります。

次の例は、リポジトリフラグメントとその実装を示しています。

例 34: 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
  }
}

次の例は、前述のリポジトリフラグメントを使用するリポジトリを示しています。

例 35: カスタマイズされたリポジトリインターフェース
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
構成

名前空間構成を使用する場合、リポジトリインフラストラクチャは、リポジトリが見つかったパッケージのクラスをスキャンすることにより、カスタム実装フラグメントを自動検出しようとします。これらのクラスは、名前空間要素の repository-impl-postfix 属性をフラグメントインターフェース名に追加する命名規則に従う必要があります。この接尾辞のデフォルトは Impl です。次の例は、デフォルトの接尾辞を使用するリポジトリと、接尾辞のカスタム値を設定するリポジトリを示しています。

例 36: 構成例
<repositories base-package="com.acme.repository" />

<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />

前の例の最初の構成は、com.acme.repository.CustomizedUserRepositoryImpl と呼ばれるクラスを検索して、カスタムリポジトリ実装として機能しようとします。2 番目の例では、com.acme.repository.CustomizedUserRepositoryMyPostfix を検索しようとします。

あいまいさの解決

一致するクラス名を持つ複数の実装が異なるパッケージで見つかった場合、Spring Data は Bean 名を使用して、使用する実装を識別します。

前に示した CustomizedUserRepository の次の 2 つのカスタム実装を考えると、最初の実装が使用されます。その Bean 名は customizedUserRepositoryImpl であり、これはフラグメントインターフェース(CustomizedUserRepository)の名前と接尾辞 Impl に一致します。

例 37: あいまいな実装の解決
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 定義を手動で作成する代わりに、名前で手動で定義したものを参照します。次の例は、カスタム実装を手動で接続する方法を示しています。

例 38: カスタム実装の手動接続
<repositories base-package="com.acme.repository" />

<beans:bean id="userRepositoryImpl" class=" … ">
  <!-- further configuration -->
</beans:bean>

11.6.2. ベースリポジトリをカスタマイズする

前のセクションで説明したアプローチでは、ベースリポジトリの動作をカスタマイズしてすべてのリポジトリが影響を受けるようにする場合、各リポジトリインターフェースをカスタマイズする必要があります。代わりに、すべてのリポジトリの動作を変更するために、永続化テクノロジ固有のリポジトリベースクラスを継承する実装を作成できます。このクラスは、次の例に示すように、リポジトリプロキシのカスタムベースクラスとして機能します。

例 39: カスタムリポジトリベースクラス
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 インフラストラクチャーにカスタマイズされたリポジトリ基本クラスを認識させることです。Java 構成では、次の例に示すように、@Enable${store}Repositories アノテーションの repositoryBaseClass 属性を使用してこれを行うことができます。

例 40: JavaConfig を使用したカスタムリポジトリベースクラスの構成
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration {  …  }

次の例に示すように、XML 名前空間で対応する属性を使用できます。

例 41: XML を使用したカスタムリポジトリベースクラスの構成
<repositories base-package="com.acme.repository"
     base-class=" … .MyRepositoryImpl" />

11.7. 集約ルートからのイベントの公開

リポジトリによって管理されるエンティティは、集約ルートです。ドメイン駆動設計アプリケーションでは、これらの集約ルートは通常、ドメインイベントを発行します。Spring Data は、@DomainEvents と呼ばれるアノテーションを提供します。これは、次の例に示すように、集約パブリケーションのメソッドで使用して、その公開をできるだけ簡単にすることができます。

例 42: 集約ルートからのドメインイベントの公開
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(…) メソッドのいずれかが呼び出されるたびに呼び出されます。

11.8. Spring Data 拡張

このセクションでは、さまざまなコンテキストで Spring Data を使用できるようにする一連の Spring Data 拡張について説明します。現在、統合のほとんどは Spring MVC を対象としています。

11.8.1. Querydsl 拡張

Querydsl (英語) は、流れるような API を使用して、静的に型指定された SQL のようなクエリの構築を可能にするフレームワークです。

次の例に示すように、いくつかの Spring Data モジュールは QuerydslPredicateExecutor を介して Querydsl との統合を提供します。

例 43: QuerydslPredicateExecutor インターフェース
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.
}
1Predicate に一致する単一のエンティティを検索して返します。
2Predicate に一致するすべてのエンティティを検索して返します。
3Predicate に一致するエンティティの数を返します。
4Predicate に一致するエンティティが存在するかどうかを返します。

Querydsl サポートを利用するには、次の例に示すように、リポジトリインターフェースで QuerydslPredicateExecutor を継承します。

例 44: リポジトリでの Querydsl 統合
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

上記の例では、次の例に示すように、Querydsl Predicate インスタンスを使用してタイプセーフクエリを記述できます。

Predicate predicate = user.firstname.equalsIgnoreCase("dave")
  .and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

11.8.2. Web サポート

このセクションには、Spring Data Commons の現在(およびそれ以降)のバージョンに実装されている Spring Data Web サポートのドキュメントが含まれています。新しく導入されたサポートは多くの点を変更するため、[web.legacy] で以前の動作のドキュメントを保持しました。

リポジトリプログラミングモデルをサポートする Spring Data モジュールには、さまざまな Web サポートが付属しています。Web 関連のコンポーネントでは、Spring MVC JAR がクラスパス上にある必要があります。それらのいくつかは Spring HATEOAS (GitHub) との統合さえ提供します。一般に、次の例に示すように、JavaConfig 構成クラスで @EnableSpringDataWebSupport アノテーションを使用することにより、統合サポートが有効になります。

例 45: Spring Data Web サポートの有効化
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

@EnableSpringDataWebSupport アノテーションは、少し説明するいくつかのコンポーネントを登録します。また、クラスパスで Spring HATEOAS を検出し、存在する場合は同様に統合コンポーネントを登録します。

あるいは、XML 構成を使用する場合、次の例(SpringDataWebConfiguration の場合)に示すように、SpringDataWebConfiguration または HateoasAwareSpringDataWebConfiguration を Spring Bean として登録します。

例 46: XML で Spring Data Web サポートを有効にする
<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" />
基本的な Web サポート

前のセクションで示した構成は、いくつかの基本的なコンポーネントを登録します。

  • Spring MVC がリクエストパラメーターまたはパス変数からリポジトリ管理ドメインクラスのインスタンスを解決できるようにする DomainClassConverter

  • Spring MVC がリクエストパラメーターから Pageable および Sort インスタンスを解決できるようにする HandlerMethodArgumentResolver 実装。

DomainClassConverter

DomainClassConverter では、Spring MVC コントローラーメソッドシグネチャーでドメインタイプを直接使用できるため、次の例に示すように、リポジトリを介してインスタンスを手動で検索する必要はありません。

例 47: メソッドシグネチャーでドメインタイプを使用する 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 が有効なコントローラーメソッド引数として有効になります。

例 48: コントローラーメソッドの引数として Pageable を使用する
@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 インスタンスを取得しようとします。

表 1: Pageable インスタンスについて評価されたリクエストパラメーター

page

取得するページ。0 からインデックス付けされ、デフォルトは 0 です。

size

取得するページのサイズ。デフォルトは 20 です。

sort

property,property(,ASC|DESC)(,IgnoreCase) 形式でソートする必要があるプロパティ。デフォルトのソート方向は、大文字と小文字が区別される昇順です。?sort=firstname&sort=lastname,asc&sort=city,ignorecase など、方向や大文字と小文字を区別する場合は、複数の sort パラメーターを使用します。

この動作をカスタマイズするには、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 には、クライアントがページを簡単にナビゲートできるようにするためのリンクだけでなく、必要な Page メタデータと Page インスタンスのコンテンツを充実させる表現モデルクラス(PagedResources)が付属しています。Page から PagedResources への変換は、PagedResourcesAssembler と呼ばれる Spring HATEOAS ResourceAssembler インターフェースの実装によって行われます。次の例は、PagedResourcesAssembler をコントローラーメソッドの引数として使用する方法を示しています。

例 49: コントローラーメソッドの引数として 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 に解決することがわかります。つまり、その構成を変更すると、リンクは自動的にその変更を順守します。デフォルトでは、アセンブラはそれが呼び出されたコントローラーメソッドを指しますが、PagedResourcesAssembler.toResource(…) メソッドをオーバーロードするページネーションリンクを構築するためのベースとして使用されるカスタム Link を渡すことでカスタマイズできます。

Web データバインディングのサポート

Spring Data 射影(射影で説明)は、次の例に示すように、JSONPath (英語) 式(Jayway JsonPath (GitHub) または XPath (英語) 式が必要(XmlBeam (英語) が必要))を使用して、受信リクエストペイロードをバインドするために使用できます。

例 50: JSONPath または XPath 式を使用した HTTP ペイロードバインディング
@ProjectedPayload
public interface UserPayload {

  @XBRead("//firstname")
  @JsonPath("$..firstname")
  String getFirstname();

  @XBRead("/lastname")
  @JsonPath({ "$.lastname", "$.user.lastname" })
  String getLastname();
}

前の例に示した型は、Spring MVC ハンドラーメソッドの引数として、または RestTemplate のメソッドの 1 つで 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";
  }
}
1User の一致する 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)
  }
}
1QuerydslPredicateExecutor は、Predicate の特定のファインダーメソッドへのアクセスを提供します。
2 リポジトリインターフェースで定義された QuerydslBinderCustomizer が自動的に選択され、ショートカット @QuerydslPredicate(bindings=…​) が選択されます。
3username プロパティのバインディングを単純な contains バインディングとして定義します。
4String プロパティのデフォルトのバインディングを、大文字と小文字を区別しない contains 一致になるように定義します。
5password プロパティを Predicate 解決から除外します。

11.8.3. リポジトリポピュレーター

Spring JDBC モジュールを使用している場合は、DataSource に SQL スクリプトを取り込むサポートに精通していると思われます。リポジトリレベルでも同様の抽象化を使用できますが、ストアに依存しないため、SQL をデータ定義言語として使用しません。ポピュレーターは、XML(Spring の OXM 抽象化による)および JSON(Jackson による)をサポートして、リポジトリに取り込むデータを定義します。

次の内容のファイル data.json があると仮定します。

例 51: JSON で定義されたデータ
[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]

Spring Data Commons で提供されるリポジトリ名前空間の populator 要素を使用して、リポジトリにデータを追加できます。上記のデータを PersonRepository に取り込むには、次のようなポピュレーターを宣言します。

例 52: Jackson リポジトリポピュレーターの宣言
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:repository="http://www.springframework.org/schema/data/repository"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    https://www.springframework.org/schema/data/repository/spring-repository.xsd">

  <repository:jackson2-populator locations="classpath:data.json" />

</beans>

上記の宣言により、data.json ファイルは Jackson ObjectMapper によって読み取られ、逆直列化されます。

JSON オブジェクトが非整列化される型は、JSON ドキュメントの _class 属性を調べることで決定されます。インフラストラクチャは最終的に、適切なリポジトリを選択して、デシリアライズされたオブジェクトを処理します。

代わりに XML を使用してリポジトリにデータを取り込む必要があるデータを定義するには、unmarshaller-populator 要素を使用できます。Spring OXM で使用可能な XML マーシャラーオプションの 1 つを使用するように構成します。詳細については、Spring リファレンスドキュメントを参照してください。次の例は、JAXB でリポジトリポピュレーターを非整列化する方法を示しています。

例 53: 非整列化リポジトリポピュレーターの宣言 (JAXB を使用する)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:repository="http://www.springframework.org/schema/data/repository"
  xmlns:oxm="http://www.springframework.org/schema/oxm"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    https://www.springframework.org/schema/data/repository/spring-repository.xsd
    http://www.springframework.org/schema/oxm
    https://www.springframework.org/schema/oxm/spring-oxm.xsd">

  <repository:unmarshaller-populator locations="classpath:data.json"
    unmarshaller-ref="unmarshaller" />

  <oxm:jaxb2-marshaller contextPath="com.acme" />

</beans>

リファレンスドキュメント

12. 導入

12.1. ドキュメント構造

リファレンスドキュメントのこのパートでは、Spring Data R2DBC が提供するコア機能について説明しています。

R2DBC サポート」は、R2DBC モジュールの機能セットを紹介しています。

R2DBC リポジトリ」は、R2DBC のリポジトリサポートを紹介します。

13. R2DBC サポート

R2DBC には、さまざまな機能が含まれています。

  • R2DBC ドライバーインスタンスの Java ベースの @Configuration クラスによる Spring 構成のサポート。

  • 行と POJO 間の統合オブジェクトマッピングを使用して一般的な R2DBC 操作を実行する際の生産性を向上させる、エンティティバインド操作の主要クラスとしての R2dbcEntityTemplate

  • Spring の変換サービスと統合された機能豊富なオブジェクトマッピング。

  • 他のメタデータ形式をサポートするために拡張可能なアノテーションベースのマッピングメタデータ。

  • カスタムクエリメソッドのサポートを含む、リポジトリインターフェースの自動実装。

ほとんどのタスクでは、R2dbcEntityTemplate またはリポジトリサポートを使用する必要があります。どちらもリッチマッピング機能を使用します。R2dbcEntityTemplate は、アドホック CRUD 操作などのアクセス機能を探す場所です。

13.1. 入門

作業環境をセットアップする簡単な方法は、start.spring.io (英語) を介して Spring ベースのプロジェクトを作成することです。そうするには:

  1. 以下を pom.xml ファイルの dependencies 要素に追加します。

    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>io.r2dbc</groupId>
          <artifactId>r2dbc-bom</artifactId>
          <version>${r2dbc-releasetrain.version}</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <dependencies>
    
      <!-- other dependency elements omitted -->
    
      <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-r2dbc</artifactId>
        <version>1.2.0-RC2</version>
      </dependency>
    
      <!-- a R2DBC driver -->
      <dependency>
        <groupId>io.r2dbc</groupId>
        <artifactId>r2dbc-h2</artifactId>
        <version>Arabba-SR7</version>
      </dependency>
    
    </dependencies>
  2. pom.xml の Spring のバージョンを次のように変更する

    <spring-framework.version>5.3.0-RC2</spring-framework.version>
  3. Maven の Spring マイルストーンリポジトリの次の場所を、<dependencies/> 要素と同じレベルになるように pom.xml に追加します。

    <repositories>
      <repository>
        <id>spring-milestone</id>
        <name>Spring Maven MILESTONE Repository</name>
        <url>https://repo.spring.io/libs-milestone</url>
      </repository>
    </repositories>

リポジトリも参照できます (英語)

ロギングレベルを DEBUG に設定して、追加情報を表示することもできます。これを行うには、application.properties ファイルを編集して次のコンテンツを作成します。

logging.level.org.springframework.data.r2dbc=DEBUG

次に、たとえば、次のように Person クラスを作成して永続化できます。

public class Person {

  private final String id;
  private final String name;
  private final int age;

  public Person(String id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
  }

  public String getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
  }
}
// end::class[]}

次に、次のようにデータベースにテーブル構造を作成する必要があります。

CREATE TABLE person
  (id VARCHAR(255) PRIMARY KEY,
   name VARCHAR(255),
   age INT);

次のように、実行するメインアプリケーションも必要です。

import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.test.StepVerifier;

import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;

public class R2dbcApp {

  private static final Log log = LogFactory.getLog(R2dbcApp.class);

  public static void main(String[] args) {

    ConnectionFactory connectionFactory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");

    R2dbcEntityTemplate template = new R2dbcEntityTemplate(connectionFactory);

    template.getDatabaseClient().sql("CREATE TABLE person" +
        "(id VARCHAR(255) PRIMARY KEY," +
        "name VARCHAR(255)," +
        "age INT)")
      .fetch()
      .rowsUpdated()
      .as(StepVerifier::create)
      .expectNextCount(1)
      .verifyComplete();

    template.insert(Person.class)
      .using(new Person("joe", "Joe", 34))
      .as(StepVerifier::create)
      .expectNextCount(1)
      .verifyComplete();

    template.select(Person.class)
      .first()
      .doOnNext(it -> log.info(it))
      .as(StepVerifier::create)
      .expectNextCount(1)
      .verifyComplete();
  }
}

メインプログラムを実行すると、前述の例では次のような出力が生成されます。

2018-11-28 10:47:03,893 DEBUG amework.core.r2dbc.DefaultDatabaseClient: 310 - Executing SQL statement [CREATE TABLE person
  (id VARCHAR(255) PRIMARY KEY,
   name VARCHAR(255),
   age INT)]
2018-11-28 10:47:04,074 DEBUG amework.core.r2dbc.DefaultDatabaseClient: 908 - Executing SQL statement [INSERT INTO person (id, name, age) VALUES($1, $2, $3)]
2018-11-28 10:47:04,092 DEBUG amework.core.r2dbc.DefaultDatabaseClient: 575 - Executing SQL statement [SELECT id, name, age FROM person]
2018-11-28 10:47:04,436  INFO        org.spring.r2dbc.example.R2dbcApp:  43 - Person [id='joe', name='Joe', age=34]

この単純な例でも、注意すべき点はほとんどありません。

  • 標準の io.r2dbc.spi.ConnectionFactory オブジェクトを使用して、Spring Data R2DBC(R2dbcEntityTemplate)で中央ヘルパークラスのインスタンスを作成できます。

  • マッパーは、追加のメタデータを必要とせずに、標準の POJO オブジェクトに対して機能します(ただし、オプションでその情報を提供できます。こちらを参照してください)。

  • マッピング規則では、フィールドアクセスを使用できます。Person クラスには getter しかありません。

  • コンストラクターの引数名が保存された行の列名と一致する場合、それらはオブジェクトのインスタンス化に使用されます。

13.2. サンプルリポジトリ

GitHub リポジトリといくつかの例 (英語) をダウンロードして試して、ライブラリの動作を確認してください。

13.3. Spring を使用したリレーショナルデータベースへの接続

リレーショナルデータベースと Spring を使用する場合の最初のタスクの 1 つは、IoC コンテナーを使用して io.r2dbc.spi.ConnectionFactory オブジェクトを作成することです。サポートされているデータベースとドライバーを使用してください。

13.3.1. Java ベースのメタデータを使用して ConnectionFactory インスタンスを登録する

次の例は、Java ベースの Bean メタデータを使用して io.r2dbc.spi.ConnectionFactory のインスタンスを登録する例を示しています。

例 54: Java ベースの Bean メタデータを使用して io.r2dbc.spi.ConnectionFactory オブジェクトを登録する
@Configuration
public class ApplicationConfiguration extends AbstractR2dbcConfiguration {

  @Override
  @Bean
  public ConnectionFactory connectionFactory() {
    return  …
  }
}

このアプローチでは、Spring の AbstractR2dbcConfiguration を使用するコンテナーで、標準の io.r2dbc.spi.ConnectionFactory インスタンスを使用できます。ConnectionFactory インスタンスを直接登録するのに比べて、構成サポートには、@Repository アノテーションが付けられたデータアクセスクラスの Spring のポータブル DataAccessException 階層の例外に R2DBC 例外を変換する ExceptionTranslator 実装もコンテナーに提供するという追加の利点があります。この階層と @Repository の使用については、Spring の DAO サポート機能で説明されています。

AbstractR2dbcConfiguration は、DatabaseClient も登録します。これは、データベースの相互作用およびリポジトリの実装に必要です。

13.3.2. R2DBC ドライバー

Spring Data R2DBC は、R2DBC のプラグ可能な SPI メカニズムを介してドライバーをサポートします。Spring Data R2DBC で R2DBC 仕様を実装する任意のドライバーを使用できます。Spring Data R2DBC は各データベースの特定の機能に反応するため、Dialect の実装が必要です。そうしないと、アプリケーションが起動しません。Spring Data R2DBC には、次のドライバー用のダイアレクトの実装が付属しています。

Spring Data R2DBC は、ConnectionFactory をインスペクションすることによってデータベースの詳細に反応し、それに応じて適切なデータベースダイアレクトを選択します。使用するドライバーが Spring Data R2DBC にまだ認識されていない場合は、独自の R2dbcDialect(Javadoc) を構成する必要があります。

ダイアレクトは ConnectionFactory から DialectResolver(Javadoc) によって解決されます。通常 ConnectionFactoryMetadata を調べることによって。
org.springframework.data.r2dbc.dialect.DialectResolver$R2dbcDialectProvider から META-INF/spring.factories を実装するクラスを登録することにより、Spring に R2dbcDialect を自動検出させることができます。DialectResolver は、Spring の SpringFactoriesLoader を使用して、クラスパスからダイアレクトプロバイダー実装を検出します。

13.4. R2dbcEntityOperations データアクセス API

R2dbcEntityTemplate は、Spring Data R2DBC の中心的なエントリポイントです。データのクエリ、挿入、更新、削除など、一般的なアドホックユースケース向けに、直接的なエンティティ指向の方法と、より狭く流暢なインターフェースを提供します。

エントリポイント(insert()select()update() など)は、実行する操作に基づいた自然な命名スキーマに従います。エントリポイントから先に進むと、API は、SQL ステートメントを作成して実行する終了メソッドにつながるコンテキスト依存のメソッドのみを提供するように設計されています。Spring Data R2DBC は、R2dbcDialect 抽象化を使用して、バインドマーカー、ページ付けのサポート、および基になるドライバーによってネイティブにサポートされるデータ型を決定します。

すべてのターミナルメソッドは、常に目的の操作を表す Publisher タイプを返します。実際のステートメントは、サブスクリプション時にデータベースに送信されます。

13.4.1. エンティティを挿入および更新する方法

R2dbcEntityTemplate には、オブジェクトを保存および挿入するための便利な方法がいくつかあります。変換プロセスをよりきめ細かく制御するために、Spring コンバーターを R2dbcCustomConversions に登録できます(たとえば、Converter<Person, OutboundRow> や Converter<Row, Person>)。

保存操作を使用する単純なケースは、POJO を保存することです。この場合、テーブル名はクラスの名前(完全修飾ではない)によって決定されます。特定のコレクション名を使用して保存操作を呼び出すこともできます。マッピングメタデータを使用して、オブジェクトを格納するコレクションをオーバーライドできます。

挿入または保存するときに、Id プロパティが設定されていない場合、その値はデータベースによって自動生成されると想定されます。自動生成の場合、クラス内の Id プロパティまたはフィールドのタイプは、Long または Integer である必要があります。

次の例は、行を挿入してその内容を取得する方法を示しています。

例 55: R2dbcEntityTemplate を使用したエンティティの挿入と取得
Person person = new Person("John", "Doe");

Mono<Person> saved = template.insert(person);
Mono<Person> loaded = template.selectOne(query(where("firstname").is("John")),
    Person.class);

次の挿入および更新操作を使用できます。

同様の挿入操作のセットも利用できます。

  • Mono<T> insert(T objectToSave): オブジェクトをデフォルトのテーブルに挿入します。

  • Mono<T> update(T objectToSave): オブジェクトをデフォルトのテーブルに挿入します。

流暢な API を使用して、テーブル名をカスタマイズできます。

13.4.2. データの選択

R2dbcEntityTemplate の select(…) および selectOne(…) メソッドは、テーブルからデータを選択するために使用されます。どちらのメソッドも、フィールド射影、WHERE 句、ORDER BY 句、および制限 / オフセットページングを定義する Query オブジェクトを取ります。制限 / オフセット機能は、基盤となるデータベースに関係なく、アプリケーションに対して透過的です。この機能は、個々の SQL フレーバー間の違いに対応するために R2dbcDialect の抽象化によってサポートされています。

例 56: R2dbcEntityTemplate を使用したエンティティの選択
Flux<Person> loaded = template.select(query(where("firstname").is("John")),
    Person.class);

13.4.3. Fluent API

このセクションでは、流暢な API の使用箇所について説明します。次の簡単なクエリについて考えてみます。

Flux<Person> people = template.select(Person.class) (1)
    .all();
1from(…) メソッドで Person を使用すると、マッピングメタデータに基づいて FROM テーブルが設定されます。また、Person 結果オブジェクトに表形式の結果をマップします。
2all() 行をフェッチすると、結果を制限することなく Flux<Person> が返されます。

次の例では、名前、WHERE 条件、および ORDER BY 句でテーブル名を指定するより複雑なクエリを宣言しています。

Mono<Person> first = template.select(Person.class)  (1)
  .from("other_person")
  .matching(query(where("firstname").is("John")     (2)
    .and("lastname").in("Doe", "White"))
    .sort(by(desc("id"))))                          (3)
  .one();                                           (4)
1 テーブルから名前で選択すると、指定されたドメインタイプを使用した行の結果が返されます。
2 発行されたクエリは、結果をフィルタリングするために firstname および lastname 列で WHERE 条件を宣言します。
3 結果は個々の列名で並べ替えることができ、結果として ORDER BY 句が生成されます。
41 つの結果を選択すると、1 つの行のみがフェッチされます。行を消費するこの方法では、クエリが正確に単一の結果を返すことが期待されます。クエリの結果が複数の場合、Mono は IncorrectResultSizeDataAccessException を発行します。
select(Class<?>) を介してターゲットタイプを指定することにより、射影を結果に直接適用できます。

次の終了方法を使用して、単一のエンティティの取得と複数のエンティティの取得を切り替えることができます。

  • first(): 最初の行のみを使用して、Mono を返します。クエリが結果を返さない場合、返された Mono はオブジェクトを発行せずに完了します。

  • one()Mono を返す 1 行のみを使用します。クエリが結果を返さない場合、返された Mono はオブジェクトを発行せずに完了します。クエリが複数の行を返す場合、Mono は例外的に IncorrectResultSizeDataAccessException を出力します。

  • all()Flux を返すすべての返された行を使用します。

  • count()Mono<Long> を返すカウント射影を適用します。

  • exists()Mono<Boolean> を返すことにより、クエリが行を生成するかどうかを返します。

select() エントリポイントを使用して、SELECT クエリを表現できます。結果の SELECT クエリは、よく使用される句(WHERE および ORDER BY)をサポートし、ページネーションをサポートします。流れるような API スタイルにより、チェーンは複数のメソッドを一緒に理解しながら、コードを理解しやすくなります。読みやすくするために、Criteria インスタンスの作成に「新しい」キーワードを使用しないようにする静的インポートを使用できます。

Criteria クラスのメソッド

Criteria クラスは次のメソッドを提供します。これらのメソッドはすべて SQL 演算子に対応しています。

  • Criteria and(String column): 指定された property を持つ連鎖 Criteria を現在の Criteria に追加し、新しく作成された Criteria を返します。

  • Criteria or(String column): 指定された property を持つ連鎖 Criteria を現在の Criteria に追加し、新しく作成された Criteria を返します。

  • Criteria greaterThan(Object o)> 演算子を使用して基準を作成します。

  • Criteria greaterThanOrEquals(Object o)>= 演算子を使用して基準を作成します。

  • Criteria in(Object…​ o): varargs 引数に IN 演算子を使用して、基準を作成します。

  • Criteria in(Collection<?> collection): コレクションを使用して IN 演算子を使用して、基準を作成します。

  • Criteria is(Object o): 列マッチング(property = value)を使用して基準を作成します。

  • Criteria isNull()IS NULL 演算子を使用して基準を作成します。

  • Criteria isNotNull()IS NOT NULL 演算子を使用して基準を作成します。

  • Criteria lessThan(Object o)< 演算子を使用して基準を作成します。

  • Criteria lessThanOrEquals(Object o) 演算子を使用して基準を作成します。

  • Criteria like(Object o): エスケープ文字処理なしで LIKE 演算子を使用して基準を作成します。

  • Criteria not(Object o)!= 演算子を使用して基準を作成します。

  • Criteria notIn(Object…​ o): varargs 引数に NOT IN 演算子を使用して、基準を作成します。

  • Criteria notIn(Collection<?> collection): コレクションを使用して NOT IN 演算子を使用して、基準を作成します。

Criteria を SELECTUPDATE および DELETE クエリとともに使用できます。

13.4.4. データの挿入

insert() エントリポイントを使用してデータを挿入できます。

次の単純な型指定された挿入操作を検討してください。

Mono<Person> insert = template.insert(Person.class) (1)
    .using(new Person("John", "Doe")); (2)
1into(…) メソッドで Person を使用すると、マッピングメタデータに基づいて INTO テーブルが設定されます。また、挿入する Person オブジェクトを受け入れる insert ステートメントを準備します。
2 スカラー Person オブジェクトを提供します。または、Publisher を指定して、INSERT ステートメントのストリームを実行することもできます。このメソッドは、すべての非 null 値を抽出して挿入します。

13.4.5. データを更新する

update() エントリポイントを使用して行を更新できます。データの更新は、割り当てを指定する Update を受け入れて、更新するテーブルを指定することから始まります。また、Query を受け入れて WHERE 句を作成します。

次の単純な型付き更新操作を検討してください。

Person modified =  …

    Mono<Integer> update = template.update(Person.class)  (1)
        .inTable("other_table")                           (2)
        .matching(query(where("firstname").is("John")))   (3)
        .apply(update("age", 42));                        (4)
1Person オブジェクトを更新し、マッピングメタデータに基づいてマッピングを適用します。
2inTable(…) メソッドを呼び出して、別のテーブル名を設定します。
3WHERE 句に変換されるクエリを指定します。
4Update オブジェクトを適用します。この場合、age を 42 に設定し、影響を受ける行の数を返します。

13.4.6. データを削除する

delete() エントリポイントを使用して行を削除できます。データの削除は、削除するテーブルの指定から始まり、オプションで Criteria を受け入れて WHERE 句を作成します。

次の簡単な挿入操作を検討してください。

    Mono<Integer> delete = template.delete(Person.class)  (1)
        .from("other_table")                              (2)
        .matching(query(where("firstname").is("John")))   (3)
        .all();                                           (4)
1Person オブジェクトを削除し、マッピングメタデータに基づいてマッピングを適用します。
2from(…) メソッドを呼び出して、別のテーブル名を設定します。
3WHERE 句に変換されるクエリを指定します。
4 削除操作を適用して、影響を受ける行の数を返します。

14. R2DBC リポジトリ

この章では、R2DBC のリポジトリサポートの専門分野について説明します。この章は、Spring Data リポジトリの操作で説明されているコアリポジトリサポートに基づいています。この章を読む前に、そこで説明されている基本概念をしっかり理解しておく必要があります。

14.1. 使用方法

リレーショナルデータベースに保存されているドメインエンティティにアクセスするには、高度なリポジトリサポートを使用して、実装を大幅に容易にします。これを行うには、リポジトリのインターフェースを作成します。次の Person クラスを検討してください。

例 57: サンプル Person エンティティ
public class Person {

  @Id
  private Long id;
  private String firstname;
  private String lastname;

  //  …  getters and setters omitted
}

次の例は、前述の Person クラスのリポジトリインターフェースを示しています。

例 58: Person エンティティを永続化する基本的なリポジトリインターフェース
public interface PersonRepository extends ReactiveCrudRepository<Person, Long> {

  // additional custom query methods go here
}

R2DBC リポジトリを構成するには、@EnableR2dbcRepositories アノテーションを使用できます。基本パッケージが構成されていない場合、インフラストラクチャーはアノテーション付き構成クラスのパッケージをスキャンします。次の例は、リポジトリに Java 構成を使用する方法を示しています。

例 59: リポジトリの Java 構成
@Configuration
@EnableR2dbcRepositories
class ApplicationConfig extends AbstractR2dbcConfiguration {

  @Override
  public ConnectionFactory connectionFactory() {
    return  …
  }
}

ドメインリポジトリは ReactiveCrudRepository を継承しているため、エンティティにアクセスするためのリアクティブ CRUD 操作を提供します。ReactiveCrudRepository に加えて、PagingAndSortingRepository と同様のソート機能を追加する ReactiveSortingRepository もあります。リポジトリインスタンスの操作は、それをクライアントに挿入する依存関係の問題にすぎません。次のコードですべての Person オブジェクトを取得できます。

例 60: 個人エンティティへのページングアクセス
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class PersonRepositoryTests {

  @Autowired
  PersonRepository repository;

  @Test
  void readsAllEntitiesCorrectly() {

    repository.findAll()
      .as(StepVerifier::create)
      .expectNextCount(1)
      .verifyComplete();
  }

  @Test
  void readsEntitiesByNameCorrectly() {

    repository.findByFirstname("Hello World")
      .as(StepVerifier::create)
      .expectNextCount(1)
      .verifyComplete();
  }
}

前の例では、Spring の単体テストサポートを使用してアプリケーションコンテキストを作成します。これにより、テストケースへのアノテーションベースの依存性注入が実行されます。テストメソッド内では、リポジトリを使用してデータベースにクエリを実行します。結果に対する期待を検証するためのテスト支援として StepVerifier を使用します。

14.2. クエリメソッド

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

例 61: PersonRepository とクエリメソッド
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {

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

  Flux<Person> findByFirstname(Publisher<String> firstname);                        (2)

  Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); (3)

  Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);       (4)

  Mono<Person> findFirstByLastname(String lastname);                                (5)

  @Query("SELECT * FROM person WHERE lastname = :lastname")
  Flux<Person> findByLastname(String lastname);                                     (6)

  @Query("SELECT firstname, lastname FROM person WHERE lastname = $1")
  Mono<Person> findFirstByLastname(String lastname);                                (7)
}
1 このメソッドは、指定された lastname を持つすべての人々のクエリを示します。クエリは、And および Or と連結できる制約のメソッド名を解析することによって導出されます。メソッド名は SELECT … FROM person WHERE firstname = :firstname のクエリ式になります。
2 このメソッドは、指定された Publisher によって firstname が発行されると、指定された firstname を持つすべての人々のクエリを示します。
3Pageable を使用して、オフセットと並べ替えのパラメーターをデータベースに渡します。
4 指定された条件で単一のエンティティを検索します。一意でない結果の場合は、IncorrectResultSizeDataAccessException で完了します。
5<4> でない限り、クエリでより多くの結果行が生成された場合でも、最初のエンティティは常に発行されます。
6findByLastname メソッドは、指定された姓を持つすべての人々のクエリを表示します。
7firstname および lastname 列のみを射影する単一の Person エンティティの照会。アノテーション付きクエリは、この例では Postgres バインドマーカーであるネイティブバインドマーカーを使用します。

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

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

After

findByBirthdateAfter(Date date)

birthdate > date

GreaterThan

findByAgeGreaterThan(int age)

age > age

GreaterThanEqual

findByAgeGreaterThanEqual(int age)

age >= age

Before

findByBirthdateBefore(Date date)

birthdate < date

LessThan

findByAgeLessThan(int age)

age < age

LessThanEqual

findByAgeLessThanEqual(int age)

age <= age

Between

findByAgeBetween(int from, int to)

age BETWEEN from AND to

NotBetween

findByAgeNotBetween(int from, int to)

age NOT BETWEEN from AND to

In

findByAgeIn(Collection<Integer> ages)

age IN (age1, age2, ageN)

NotIn

findByAgeNotIn(Collection ages)

age NOT IN (age1, age2, ageN)

IsNotNullNotNull

findByFirstnameNotNull()

firstname IS NOT NULL

IsNullNull

findByFirstnameNull()

firstname IS NULL

LikeStartingWithEndingWith

findByFirstnameLike(String name)

firstname LIKE name

NotLikeIsNotLike

findByFirstnameNotLike(String name)

firstname NOT LIKE name

文字列の Containing 

findByFirstnameContaining(String name)

firstname LIKE '%' + name +'%'

文字列の NotContaining 

findByFirstnameNotContaining(String name)

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

(No keyword)

findByFirstname(String name)

firstname = name

Not

findByFirstnameNot(String name)

firstname != name

IsTrueTrue

findByActiveIsTrue()

active IS TRUE

IsFalseFalse

findByActiveIsFalse()

active IS FALSE

14.2.1. クエリの変更

前のセクションでは、特定のエンティティまたはエンティティのコレクションにアクセスするためのクエリを宣言する方法について説明しました。前の表のキーワードを使用すると、delete … By または remove … By と組み合わせて、一致する行を削除する派生クエリを作成できます。

例 62: Delete … By クエリ
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {

  Mono<Integer> deleteByLastname(String lastname);            (1)

  Mono<Void> deletePersonByLastname(String lastname);         (2)

  Mono<Boolean> deletePersonByLastname(String lastname);      (3)
}
1Mono<Integer> の戻り型を使用すると、影響を受ける行の数が返されます。
2Void を使用すると、結果値を出力せずに行が正常に削除されたかどうかがレポートされるだけです。
3Boolean を使用すると、少なくとも 1 つの行が削除されたかどうかが報告されます。

このアプローチは包括的なカスタム機能に適しているため、次の例に示すように、クエリメソッドに @Modifying アノテーションを付けることで、パラメーターバインディングのみが必要なクエリを変更できます。

@Modifying
@Query("UPDATE person SET firstname = :firstname where lastname = :lastname")
Mono<Integer> setFixedFirstnameFor(String firstname, String lastname);

変更クエリの結果は次のとおりです。

  • Void (または Kotlin Unit)は、更新カウントを破棄して完了を待ちます。

  • 影響を受ける行数を出力する Integer または別の数値タイプ。

  • 少なくとも 1 つの行が更新されたかどうかを出力する Boolean

@Modifying アノテーションは、@Query アノテーションと組み合わせた場合にのみ関連します。派生したカスタムメソッドでは、このアノテーションは不要です。

または、Spring Data リポジトリのカスタム実装で説明されている機能を使用して、カスタム変更動作を追加できます。

14.2.2. SpEL 式を使用したクエリ

クエリ文字列定義を SpEL 式と一緒に使用して、実行時に動的クエリを作成できます。SpEL 式は、クエリを実行する直前に評価される述語値を提供できます。

式は、すべての引数を含む配列を通じてメソッド引数を公開します。次のクエリは、[0] を使用して、lastname の述語値を宣言します(これは、:lastname パラメーターバインディングと同等です)。

@Query("SELECT * FROM person WHERE lastname = :#{[0]}")
Flux<Person> findByQueryWithExpression(String lastname);

クエリ文字列の SpEL は、クエリを強化する強力な方法です。しかし、彼らはまた、不要な引数の広い範囲を受け入れることができます。クエリに不要な変更が加えられないように、クエリに渡す前に文字列をサニタイズしてください。

式のサポートは、Query SPI: org.springframework.data.spel.spi.EvaluationContextExtension を介して拡張可能です。Query SPI は、プロパティと機能を提供し、ルートオブジェクトをカスタマイズできます。拡張機能は、クエリの作成時の SpEL 評価時にアプリケーションコンテキストから取得されます。

SpEL 式をプレーンパラメーターと組み合わせて使用する場合は、ネイティブバインドマーカーの代わりに名前付きパラメーター表記を使用して、適切なバインド順序を確保してください。

14.2.3. エンティティ状態検出戦略

次の表は、エンティティが新しいかどうかを検出するために Spring Data R2DBC が提供する戦略を説明しています。

表 3: Spring Data R2DBC でエンティティが新しいかどうかを検出するためのオプション

ID プロパティインスペクション (デフォルト)

デフォルトでは、save() メソッドは特定のエンティティの識別子プロパティをインスペクションします。識別子プロパティが null の場合、エンティティは新しいと見なされます。それ以外の場合は、データベースに存在すると見なされます。

Persistable の実装

エンティティが Persistable を実装している場合、Spring Data R2DBC はエンティティの isNew(…) メソッドに新しい検出を委譲します。詳細については、Javadoc を参照してください。

@Version による楽観的ロック

エンティティが OptimisticLocking by(@Version でアノテーションが付けられたバージョンプロパティ)を使用する場合、Spring Data R2DBC は、その値が Java のデフォルトの初期化値に対応するかどうかをバージョンプロパティを調べて、エンティティが新しいかどうかを確認します。これは、プリミティブ型の場合は 0、ラッパー型の場合は null です。

EntityInformation の実装

R2dbcRepositoryFactory のサブクラスを作成し、getEntityInformation(…) をオーバーライドすることにより、SimpleR2dbcRepository で使用される EntityInformation 抽象化をカスタマイズできます。次に、R2dbcRepositoryFactory のカスタム実装を Spring Bean として登録する必要があります。これはほとんど必要ないことに注意してください。詳細については、Javadoc を参照してください。

14.2.4. ID 生成

Spring Data R2DBC は、ID を使用してエンティティを識別します。エンティティの ID には、Spring Data の @Id(Javadoc) アノテーションを付ける必要があります。

データベースに ID 列の自動インクリメント列がある場合、生成された値は、データベースに挿入された後にエンティティに設定されます。

Spring Data R2DBC は、エンティティが新しく、識別子の値がデフォルトで初期値になっている場合、識別子の列の値を挿入しようとはしません。これは、プリミティブ型の場合は 0 であり、識別子プロパティが Long などの数値ラッパー型を使用している場合は null です。

重要な制約の 1 つは、エンティティを保存した後、そのエンティティが新しいものであってはならないことです。エンティティが新しいかどうかは、エンティティの状態の一部であることに注意してください。自動インクリメント列では、ID 列の値を使用して Spring Data によって ID が設定されるため、これは自動的に行われます。

14.2.5. 楽観的ロック

@Version アノテーションは、R2DBC のコンテキストで JPA と同様の構文を提供し、更新が一致するバージョンの行にのみ適用されるようにします。バージョンプロパティの実際の値は、別の操作がその間に行を変更した場合に更新が影響を与えないように、更新クエリに追加されます。その場合、OptimisticLockingFailureException がスローされます。次の例は、これらの機能を示しています。

@Table
class Person {

  @Id Long id;
  String firstname;
  String lastname;
  @Version Long version;
}

R2dbcEntityTemplate template =  … ;

Mono<Person> daenerys = template.insert(new Person("Daenerys"));                      (1)

Person other = template.select(Person.class)
                 .matching(query(where("id").is(daenerys.getId())))
                 .first().block();                                                    (2)

daenerys.setLastname("Targaryen");
template.update(daenerys);                                                            (3)

template.update(other).subscribe(); // emits OptimisticLockingFailureException        (4)
1 最初に行を挿入します。version は 0 に設定されます。
2 挿入したばかりの行をロードします。version は 0 のままです。
3 行を version = 0 で更新します。lastname を設定し、version を 1 にバンプします。
4version = 0 がまだ残っている以前にロードされた行を更新してみてください。現在の version は 1 であるため、操作は OptimisticLockingFailureException で失敗します。

14.2.6. 射影

Spring Data クエリメソッドは通常、リポジトリによって管理される集約ルートの 1 つまたは複数のインスタンスを返します。ただし、これらのタイプの特定の属性に基づいて射影を作成することが望ましい場合があります。Spring Data では、専用の戻り値型をモデル化して、管理対象集合体の部分ビューをより選択的に取得できます。

次の例のようなリポジトリおよび集約ルートタイプを想像してください。

例 63: サンプルの集約とリポジトリ
class Person {

  @Id UUID id;
  String firstname, lastname;
  Address address;

  static class Address {
    String zipCode, city, street;
  }
}

interface PersonRepository extends Repository<Person, UUID> {

  Flux<Person> findByLastname(String lastname);
}

ここで、人の名前属性のみを取得することを想像してください。Spring Data はこれを達成するためにどのような意味を持っていますか?この章の残りはその質問に回答します。

インターフェースベースの射影

クエリの結果を名前属性のみに制限する最も簡単な方法は、次の例に示すように、読み取るプロパティのアクセサーメソッドを公開するインターフェースを宣言することです。

例 64: 属性のサブセットを取得する射影インターフェース
interface NamesOnly {

  String getFirstname();
  String getLastname();
}

ここで重要なことは、ここで定義されたプロパティが集約ルートのプロパティと正確に一致することです。これにより、クエリメソッドを次のように追加できます。

例 65: クエリメソッドでインターフェースベースの射影を使用するリポジトリ
interface PersonRepository extends Repository<Person, UUID> {

  Flux<NamesOnly> findByLastname(String lastname);
}

クエリ実行エンジンは、返された各要素に対して実行時にそのインターフェースのプロキシインスタンスを作成し、公開されたメソッドへの呼び出しをターゲットオブジェクトに転送します。

射影は再帰的に使用できます。Address 情報の一部も含めたい場合は、次の例に示すように、そのための射影インターフェースを作成し、getAddress() の宣言からそのインターフェースを返します。

例 66: 属性のサブセットを取得する射影インターフェース
interface PersonSummary {

  String getFirstname();
  String getLastname();
  AddressSummary getAddress();

  interface AddressSummary {
    String getCity();
  }
}

メソッドの呼び出し時に、ターゲットインスタンスの address プロパティが取得され、順番に射影プロキシにラップされます。

閉じた射影

アクセサーメソッドがすべてターゲット集合体のプロパティに一致する射影インターフェースは、閉じた射影と見なされます。次の例(この章の前半でも使用しました)は、閉じた射影です。

例 67: 閉じた射影
interface NamesOnly {

  String getFirstname();
  String getLastname();
}

閉じた射影を使用する場合、Spring Data はクエリの実行を最適化できます。これは、射影プロキシのバックアップに必要なすべての属性がわかっているためです。詳細については、リファレンスドキュメントのモジュール固有の部分を参照してください。

開いた射影

次の例に示すように、@Value アノテーションを使用して、射影インターフェースのアクセサーメソッドを使用して新しい値を計算することもできます。

例 68: 開いた射影
interface NamesOnly {

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
   …
}

射影を支える集約ルートは、target 変数で利用可能です。@Value を使用した射影インターフェースは、オープン射影です。この場合、Spring Data はクエリ実行最適化を適用できません。これは、SpEL 式が集約ルートの任意の属性を使用できるためです。

@Value で使用される式は複雑すぎてはいけません — String 変数でのプログラミングは避けたいです。非常に単純な式の場合、次の例に示すように、1 つのオプションはデフォルトのメソッド(Java 8 で導入)に頼ることです。

例 69: カスタムロジックにデフォルトのメソッドを使用する射影インターフェース
interface NamesOnly {

  String getFirstname();
  String getLastname();

  default String getFullName() {
    return getFirstname().concat(" ").concat(getLastname());
  }
}

このアプローチでは、射影インターフェースで公開される他のアクセサーメソッドに純粋に基づいてロジックを実装できる必要があります。次の例に示すように、2 番目のより柔軟なオプションは、Spring Bean にカスタムロジックを実装し、SpEL 式からそれを呼び出すことです。

例 70: サンプル Person オブジェクト
@Component
class MyBean {

  String getFullName(Person person) {
     …
  }
}

interface NamesOnly {

  @Value("#{@myBean.getFullName(target)}")
  String getFullName();
   …
}

SpEL 式が myBean を参照し、getFullName(…) メソッドを呼び出し、射影ターゲットをメソッドパラメーターとして転送する方法に注目してください。SpEL 式の評価に裏付けられたメソッドは、メソッドパラメーターを使用することもできます。このパラメーターは、式から参照できます。メソッドのパラメーターは、args という名前の Object 配列を介して使用できます。次の例は、args 配列からメソッドパラメーターを取得する方法を示しています。

例 71: サンプル Person オブジェクト
interface NamesOnly {

  @Value("#{args[0] + ' ' + target.firstname + '!'}")
  String getSalutation(String prefix);
}

ここでも、より複雑な式のために、Spring Bean を使用する必要がありますし、説明するように、メソッド Invoke 表現を聞かせてそれ以前

null 可能ラッパー

射影インターフェースの Getter は、null 許容ラッパーを使用して null の安全性を向上させることができます。現在サポートされているラッパータイプは次のとおりです。

  • java.util.Optional

  • com.google.common.base.Optional

  • scala.Option

  • io.vavr.control.Option

例 72: null 許容ラッパーを使用した射影インターフェース
interface NamesOnly {

  Optional<String> getFirstname();
}

基になる射影値が null でない場合、値はラッパータイプの現在の表現を使用して返されます。バッキング値が null の場合、getter メソッドは使用されたラッパータイプの空の表現を返します。

クラスベースの射影 (DTO)

射影を定義するもう 1 つの方法は、取得することになっているフィールドのプロパティを保持する値型 DTO(データ転送オブジェクト)を使用することです。これらの DTO タイプは、プロキシが発生せず、ネストされた射影を適用できないことを除いて、射影インターフェースとまったく同じ方法で使用できます。

ストアがロードするフィールドを制限することでクエリの実行を最適化する場合、ロードされるフィールドは公開されているコンストラクターのパラメーター名から決定されます。

次の例は、射影 DTO を示しています。

例 73: 射影 DTO
class NamesOnly {

  private final String firstname, lastname;

  NamesOnly(String firstname, String lastname) {

    this.firstname = firstname;
    this.lastname = lastname;
  }

  String getFirstname() {
    return this.firstname;
  }

  String getLastname() {
    return this.lastname;
  }

  // equals(…) and hashCode() implementations
}
射影 DTO の定型コードを避ける

@Value アノテーションを提供するプロジェクト Lombok (英語) を使用すると、DTO のコードを劇的に簡素化できます(前のインターフェース例で示した Spring の @Value アノテーションと混同しないでください)。Project Lombok の @Value アノテーションを使用する場合、前述のサンプル DTO は次のようになります。

@Value
class NamesOnly {
  String firstname, lastname;
}

フィールドはデフォルトで private final であり、クラスはすべてのフィールドを取得し、equals(…) および hashCode() メソッドを自動的に実装するコンストラクターを公開します。

動的射影

これまで、コレクションの戻り値型または要素型として射影型を使用しました。ただし、呼び出し時に使用するタイプを選択することもできます(これにより、動的になります)。動的射影を適用するには、次の例に示すようなクエリメソッドを使用します。

例 74: 動的射影パラメーターを使用するリポジトリ
interface PersonRepository extends Repository<Person, UUID> {

  <T> Flux<T> findByLastname(String lastname, Class<T> type);
}

この方法では、次の例に示すように、メソッドを使用して、そのままで、または射影を適用して集計を取得できます。

例 75: 動的射影でリポジトリを使用する
void someMethod(PersonRepository people) {

  Flux<Person> aggregates =
    people.findByLastname("Matthews", Person.class);

  Flux<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}
結果のマッピング

インターフェースまたは DTO 射影を返すクエリメソッドは、実際のクエリによって生成された結果に基づいています。インターフェース射影は通常、最初にドメインタイプへのマッピング結果に依存して、潜在的な @Column タイプのマッピングを検討し、実際の射影プロキシは、潜在的に部分的にマテリアライズされたエンティティを使用して射影データを公開します。

DTO 射影の結果マッピングは、実際のクエリタイプによって異なります。派生クエリはドメインタイプを使用して結果をマップし、Spring Data はドメインタイプで使用可能なプロパティのみから DTO インスタンスを作成します。ドメインタイプで使用できない DTO のプロパティの宣言はサポートされていません。

文字列ベースのクエリは、実際のクエリ、特にフィールド射影と結果タイプの宣言が密接に関連しているため、異なるアプローチを使用します。@Query でアノテーションが付けられたクエリメソッドで使用される DTO 射影は、クエリ結果を DTO タイプに直接マップします。ドメインタイプのフィールドマッピングは考慮されません。DTO タイプを直接使用すると、クエリメソッドは、ドメインモデルに制限されない、より動的な射影の恩恵を受けることができます。

14.3. エンティティコールバック

Spring Data インフラストラクチャは、特定のメソッドが呼び出される前後にエンティティを変更するためのフックを提供します。いわゆる EntityCallback インスタンスは、コールバック形式のエンティティをチェックし、潜在的に変更する便利な方法を提供します。
EntityCallback は、特化した ApplicationListener に非常によく似ています。一部の Spring Data モジュールは、特定のエンティティの変更を許可するストア固有のイベント(BeforeSaveEvent など)を公開します。不変の型を操作する場合など、これらのイベントは問題を引き起こす可能性があります。また、イベント発行は ApplicationEventMulticaster に依存しています。非同期 TaskExecutor を使用して構成すると、イベント処理をスレッドに分岐できるため、予測不能な結果を招く可能性があります。

エンティティコールバックは、同期ポイントとリアクティブ API の両方を統合ポイントに提供して、処理チェーン内の明確に定義されたチェックポイントでの順序実行を保証し、潜在的に変更されたエンティティまたはリアクティブラッパータイプを返します。

エンティティコールバックは通常、API タイプによって分離されます。この分離は、同期 API が同期エンティティコールバックのみを考慮し、リアクティブ実装がリアクティブエンティティコールバックのみを考慮することを意味します。

エンティティコールバック API は Spring Data Commons 2.2. で導入されました。これは、エンティティの変更を適用する推奨方法です。既存のストア固有の ApplicationEvents は、登録されている可能性のある EntityCallback インスタンスを呼び出すに公開されます。

14.3.1. エンティティコールバックの実装

EntityCallback は、ジェネリック型引数を介してドメインタイプに直接関連付けられます。通常、各 Spring Data モジュールには、エンティティのライフサイクルをカバーする一連の定義済み EntityCallback インターフェースが付属しています。

例 76: 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 固有のメソッド。潜在的に変更されたインスタンスを返します。
2 永続化する直前のエンティティ。
3 エンティティが永続化されるコレクションなどのストア固有の引数の数。
例 77: リアクティブ 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 固有のメソッド。潜在的に変更されたインスタンスを発行します。
2 永続化する直前のエンティティ。
3 エンティティが永続化されるコレクションなどのストア固有の引数の数。
オプションのエンティティコールバックパラメーターは、実装 Spring Data モジュールによって定義され、EntityCallback.callback() の呼び出しサイトから推測されます。

次の例に示すように、アプリケーションのニーズに合ったインターフェースを実装します。

例 78: 例 BeforeSaveCallback
class 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 要件に応じたコールバックの実装。
2 同じドメインタイプのエンティティコールバックが複数存在する場合、エンティティコールバックを潜在的にオーダーします。優先順位は最低です。

14.3.2. エンティティコールバックの登録

EntityCallback Bean は、ApplicationContext に登録されている場合に、ストア固有の実装によってピックアップされます。ほとんどのテンプレート API はすでに ApplicationContextAware を実装しているため、ApplicationContext にアクセスできます。

次の例は、有効なエンティティコールバック登録のコレクションを説明しています。

例 79: 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 // ...
  }
}
1BeforeSaveCallback は @Order アノテーションからオーダーを受け取ります。
2BeforeSaveCallback は、Ordered インターフェース実装を介してオーダーを受け取ります。
3 ラムダ式を使用した BeforeSaveCallback。デフォルトでは順不同で、最後に呼び出されます。ラムダ式によって実装されたコールバックはタイピング情報を公開しないため、割り当て不可能なエンティティでこれらを呼び出すと、コールバックのスループットに影響することに注意してください。class または enum を使用して、コールバック Bean のタイプフィルタリングを有効にします。
4 単一の実装クラスに複数のエンティティコールバックインターフェースを組み合わせます。

14.3.3. 特定の EntityCallbacks を保存する

Spring Data R2DBC は、監査サポートに EntityCallback API を使用し、次のコールバックに反応します。

表 4: サポートされているエンティティのコールバック
折り返し電話 メソッド 説明 順序

BeforeConvertCallback

onBeforeConvert(T entity, SqlIdentifier table)

ドメインオブジェクトが OutboundRow に変換される前に呼び出されます。

Ordered.LOWEST_PRECEDENCE

AfterConvertCallback

onAfterConvert(T entity, SqlIdentifier table)

ドメインオブジェクトがロードされた後に呼び出されます。
行から読み取った後、ドメインオブジェクトを変更できます。

Ordered.LOWEST_PRECEDENCE

AuditingEntityCallback

onBeforeConvert(T entity, SqlIdentifier table)

作成または変更 された監査可能なエンティティをマークする

100

BeforeSaveCallback

onBeforeSave(T entity, OutboundRow row, SqlIdentifier table)

ドメインオブジェクトが保存される前に呼び出されます。
マップされたすべてのエンティティ情報を含む OutboundRow を永続化するために、ターゲットを変更できます。

Ordered.LOWEST_PRECEDENCE

AfterSaveCallback

onAfterSave(T entity, OutboundRow row, SqlIdentifier table)

ドメインオブジェクトが保存される前に呼び出されます。
ドメインオブジェクトを変更して、保存後に返されるようにすることができます。OutboundRow には、マップされたすべてのエンティティ情報が含まれています。

Ordered.LOWEST_PRECEDENCE

14.4. 複数のデータベースでの作業

複数の、潜在的に異なるデータベースを操作する場合、アプリケーションは構成に対して異なるアプローチを必要とします。提供されている AbstractR2dbcConfiguration サポートクラスは、Dialect が派生する単一の ConnectionFactory を想定しています。そうは言っても、複数のデータベースで動作するように Spring Data R2DBC を構成するには、いくつかの Bean を自分で定義する必要があります。

R2DBC リポジトリは、リポジトリを実装するために R2dbcEntityOperations を必要とします。AbstractR2dbcConfiguration を使用せずにリポジトリをスキャンする簡単な構成は次のようになります。

@Configuration
@EnableR2dbcRepositories(basePackages = "com.acme.mysql", entityOperationsRef = "mysqlR2dbcEntityOperations")
static class MySQLConfiguration {

    @Bean
    @Qualifier("mysql")
    public ConnectionFactory mysqlConnectionFactory() {
        return  …
    }

    @Bean
    public R2dbcEntityOperations mysqlR2dbcEntityOperations(@Qualifier("mysql") ConnectionFactory connectionFactory) {

        DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);

        return new R2dbcEntityTemplate(databaseClient, MySqlDialect.INSTANCE);
    }
}

@EnableR2dbcRepositories では、databaseClientRef または entityOperationsRef のいずれかを介して構成できることに注意してください。さまざまな DatabaseClient Bean を使用すると、同じタイプの複数のデータベースに接続するときに役立ちます。ダイアレクトが異なるさまざまなデータベースシステムを使用する場合は、代わりに @EnableR2dbcRepositories(entityOperationsRef = …) ` を使用してください。

15. 監査

15.1. 基本

Spring Data は、エンティティの作成者または変更者と変更がいつ発生したかを透過的に追跡する高度なサポートを提供します。その機能を活用するには、アノテーションを使用するか、インターフェースを実装することで定義できる監査メタデータをエンティティクラスに装備する必要があります。

15.1.1. アノテーションベースの監査メタデータ

エンティティを作成または変更したユーザーをキャプチャーする @CreatedBy と @LastModifiedBy、および変更が発生したときにキャプチャーする @CreatedDate と @LastModifiedDate を提供します。

例 80: 監査対象エンティティ
class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  //  …  further properties omitted
}

ご覧のとおり、どの情報をキャプチャーするかに応じて、アノテーションを選択的に適用できます。変更が行われたときにキャプチャーするアノテーションは、タイプ Joda-Time、DateTime、レガシー Java Date および Calendar、JDK8 の日付と時刻タイプ、および long または Long のプロパティで使用できます。

15.1.2. インターフェースベースの監査メタデータ

アノテーションを使用して監査メタデータを定義したくない場合は、ドメインクラスに Auditable インターフェースを実装させることができます。すべての監査プロパティの setter メソッドを公開します。

また、便利な基本クラス AbstractAuditable もあります。これは、インターフェースメソッドを手動で実装する必要を回避するために拡張できます。これにより、ドメインクラスの Spring Data への結合が増加しますが、これは避けたい場合があります。通常、監査メタデータを定義するアノテーションベースのメソッドは、侵襲性が低く、柔軟性が高いため好まれます。

15.1.3. AuditorAware

@CreatedBy または @LastModifiedBy を使用する場合、監査インフラストラクチャは何らかの形で現在のプリンシパルを認識する必要があります。そのために、現在のユーザーまたはアプリケーションと対話するシステムが誰であるかをインフラストラクチャに伝えるために実装する必要がある AuditorAware<T> SPI インターフェースを提供します。ジェネリックタイプ T は、@CreatedBy または @LastModifiedBy アノテーションが付けられたプロパティのタイプを定義します。

次の例は、Spring Security の Authentication オブジェクトを使用するインターフェースの実装を示しています。

例 81: Spring Security に基づく 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 に基づいて、どこからでも検索できると想定しています。

15.1.4. ReactiveAuditorAware

リアクティブインフラストラクチャを使用する場合、コンテキスト情報を利用して @CreatedBy または @LastModifiedBy 情報を提供することができます。アプリケーションと対話している現在のユーザーまたはシステムが誰であるかをインフラストラクチャに通知するために実装する必要がある ReactiveAuditorAware<T> SPI インターフェースを提供します。ジェネリック型 T は、@CreatedBy または @LastModifiedBy でアノテーションが付けられたプロパティがどの型でなければならないかを定義します。

次の例は、リアクティブ Spring Security の Authentication オブジェクトを使用するインターフェースの実装を示しています。

例 82: Spring Security に基づく 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 に基づいて、どこからでも検索できると想定しています。

15.2. R2DBC の一般的な監査構成

次の例に示すように、Spring Data R2DBC 1.2, 監査は、構成クラスに @EnableR2dbcAuditing アノテーションを付けることで有効にできるためです。

例 83: JavaConfig を使用した監査のアクティブ化
@Configuration
@EnableR2dbcAuditing
class Config {

  @Bean
  public ReactiveAuditorAware<AuditableUser> myAuditorProvider() {
      return new AuditorAwareImpl();
  }
}

タイプ ReactiveAuditorAware の Bean を ApplicationContext に公開すると、監査インフラストラクチャはそれを自動的に取得し、それを使用して、ドメインタイプに設定する現在のユーザーを決定します。ApplicationContext に複数の実装が登録されている場合は、@EnableR2dbcAuditing の auditorAwareRef 属性を明示的に設定することで、使用する実装を選択できます。

16. マッピング

MappingR2dbcConverter により、豊富なマッピングサポートが提供されます。MappingR2dbcConverter には、ドメインオブジェクトをデータ行にマッピングできる豊富なメタデータモデルがあります。マッピングメタデータモデルは、ドメインオブジェクトのアノテーションを使用して作成されます。ただし、インフラストラクチャは、メタデータ情報の唯一のソースとしてアノテーションを使用することに限定されません。MappingR2dbcConverter では、一連の規則に従って、追加のメタデータを提供せずにオブジェクトを行にマップすることもできます。

このセクションでは、オブジェクトを行にマッピングするための規則の使用方法や、アノテーションベースのマッピングメタデータでこれらの規則をオーバーライドする方法など、MappingR2dbcConverter の機能について説明します。

16.1. オブジェクトマッピングの基礎

このセクションでは、Spring Data オブジェクトマッピング、オブジェクト作成、フィールドとプロパティへのアクセス、可変性と不変性の基礎について説明します。このセクションは、基になるデータストア(JPA など)のオブジェクトマッピングを使用しない Spring Data モジュールにのみ適用されることに注意してください。また、インデックス、列名やフィールド名のカスタマイズなど、ストア固有のオブジェクトマッピングについては、ストア固有のセクションを参照してください。

Spring Data オブジェクトマッピングの中心的なロールは、ドメインオブジェクトのインスタンスを作成し、ストアネイティブデータ構造をそれらにマッピングすることです。つまり、2 つの基本的な手順が必要です。

  1. 公開されたコンストラクターの 1 つを使用したインスタンスの作成。

  2. すべての公開されたプロパティを具体化するインスタンスの設定。

16.1.1. オブジェクト作成

Spring Data は、そのタイプのオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。

  1. 引数なしのコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。

  2. 引数を取る単一のコンストラクターがある場合は、それが使用されます。

  3. 引数を取る複数のコンストラクターがある場合、Spring Data が使用するコンストラクターに @PersistenceConstructor のアノテーションを付ける必要があります。

値の解決は、コンストラクターの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含む、プロパティが設定されるかのように解決が実行されます。また、これには、クラスファイルで使用可能なパラメーター名情報、またはコンストラクターに存在する @ConstructorProperties アノテーションのいずれかが必要です。

値の解決は、ストア固有の SpEL 式を使用した Spring Framework の @Value 値アノテーションを使用してカスタマイズできます。詳細については、ストア固有のマッピングに関するセクションを参照してください。

オブジェクト作成の詳細

リフレクションのオーバーヘッドを回避するために、Spring Data オブジェクトの作成では、デフォルトで実行時に生成されるファクトリクラスを使用します。これにより、ドメインクラスコンストラクターが直接呼び出されます。つまりこの例のタイプ:

class Person {
  Person(String firstname, String lastname) {  …  }
}

実行時にこれと意味的に同等のファクトリクラスを作成します。

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

これにより、リフレクションよりも約 10% パフォーマンスが向上します。ドメインクラスがこのような最適化の対象となるには、一連の制約に従う必要があります。

  • プライベートクラスであってはなりません

  • 非静的内部クラスであってはなりません

  • CGLib プロキシクラスであってはなりません

  • Spring Data で使用されるコンストラクターはプライベートであってはなりません

これらの条件のいずれかが一致する場合、Spring Data はリフレクションを介してエンティティのインスタンス化にフォールバックします。

16.1.2. プロパティ設定

エンティティのインスタンスが作成されると、Spring Data はそのクラスの残りのすべての永続プロパティを設定します。エンティティのコンストラクターによって既に入力されていない場合(つまり、コンストラクターの引数リストを介して使用される場合)、ID プロパティが最初に入力され、循環オブジェクト参照の解決が可能になります。その後、コンストラクターによってまだ設定されていないすべての非一時的なプロパティがエンティティインスタンスに設定されます。そのために、次のアルゴリズムを使用します。

  1. プロパティが不変であるが with …  メソッドを公開している場合(以下を参照)、with …  メソッドを使用して、新しいプロパティ値を持つ新しいエンティティインスタンスを作成します。

  2. プロパティアクセス(つまり、getter および setter を介したアクセス)が定義されている場合、setter メソッドを呼び出しています。

  3. プロパティが変更可能な場合、フィールドを直接設定します。

  4. プロパティが不変の場合、永続化操作(オブジェクト作成を参照)で使用されるコンストラクターを使用して、インスタンスのコピーを作成します。

  5. デフォルトでは、フィールド値を直接設定します。

プロパティ設定の詳細

オブジェクト構築の最適化と同様に、Spring Data ランタイム生成のアクセサークラスを使用して、エンティティインスタンスと対話します。

class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}
例 84: 生成されたプロパティアクセサー
class PersonPropertyAccessor implements PersistentPropertyAccessor {

  private static final MethodHandle firstname;              (2)

  private Person person;                                    (1)

  public void setProperty(PersistentProperty property, Object value) {

    String name = property.getName();

    if ("firstname".equals(name)) {
      firstname.invoke(person, (String) value);             (2)
    } else if ("id".equals(name)) {
      this.person = person.withId((Long) value);            (3)
    } else if ("lastname".equals(name)) {
      this.person.setLastname((String) value);              (4)
    }
  }
}
1PropertyAccessor は、基礎となるオブジェクトの可変インスタンスを保持します。これは、そうでなければ不変のプロパティの変更を可能にするためです。
2 デフォルトでは、Spring Data はフィールドアクセスを使用してプロパティ値を読み書きします。private フィールドの可視性ルールに従って、MethodHandles はフィールドとの対話に使用されます。
3 クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。withId(…) を呼び出すと、新しい Person オブジェクトが作成されます。後続のすべての変更は、新しいインスタンスで行われ、前のインスタンスは変更されません。
4property-access を使用すると、MethodHandles を使用せずに直接メソッドを呼び出すことができます。

これにより、リフレクションよりも約 25% パフォーマンスが向上します。ドメインクラスがこのような最適化の対象となるには、一連の制約に従う必要があります。

  • タイプは、デフォルトまたは java パッケージに存在してはなりません。

  • 型とそのコンストラクターは public でなければなりません

  • 内部クラスである型は static でなければなりません。

  • 使用される Java ランタイムは、元の ClassLoader でクラスを宣言できるようにする必要があります。Java 9 以降には特定の制限があります。

デフォルトでは、Spring Data は生成されたプロパティアクセサーを使用しようとし、制限が検出された場合はリフレクションベースのものにフォールバックします。

次のエンティティを見てみましょう。

例 85: サンプルエンティティ
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;
  }
}
1identifier プロパティは final ですが、コンストラクターで null に設定されます。クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。元の Person インスタンスは、新しいインスタンスが作成されるときに変更されません。通常、ストア管理される他のプロパティにも同じパターンが適用されますが、永続化操作のために変更する必要がある場合があります。永続化コンストラクター(6 を参照)は事実上コピーコンストラクターであり、プロパティの設定は新しい識別子値が適用された新しいインスタンスの作成に変換されるため、wither メソッドはオプションです。
2firstname および lastname プロパティは、getter を介して潜在的に公開される通常の不変のプロパティです。
3age プロパティは不変ですが、birthday プロパティから派生しています。示されている設計では、Spring Data は宣言された唯一のコンストラクターを使用するため、データベース値はデフォルト設定よりも優先されます。計算が優先されることを意図している場合でも、このコンストラクターがパラメーターとして age を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは年齢フィールドを設定しようとし、不変で no with …  メソッドが存在します。
4comment プロパティは可変であり、フィールドを直接設定することで入力されます。
5remarks プロパティは可変であり、comment フィールドを直接設定するか、setter メソッドを呼び出して設定します。
6 このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの核となる考え方は、追加のコンストラクターの代わりにファクトリメソッドを使用して、@PersistenceConstructor によるコンストラクターの明確化の必要性を回避することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。

16.1.3. 一般的な推奨事項

  • 不変オブジェクトに固執しようとする — 不変オブジェクトは、オブジェクトを具体化するのはコンストラクターのみを呼び出すだけなので、簡単に作成できます。また、これにより、クライアントオブジェクトがオブジェクトの状態を操作できるようにする setter メソッドがドメインオブジェクトに散らばるのを防ぎます。それらが必要な場合は、同じ場所に配置された限られたタイプでのみ呼び出せるように、パッケージを保護することをお勧めします。コンストラクターのみの実体化は、プロパティの設定よりも最大 30% 高速です。

  • すべての引数のコンストラクターを提供する  —  エンティティを不変の値としてモデル化できない、またはしたくない場合でも、オブジェクトのマッピングがプロパティの設定をスキップできるため、エンティティのすべてのプロパティを引数として取るコンストラクターを提供することには価値があります。最適なパフォーマンスのため。

  • @PersistenceConstructor を回避するために、オーバーロードされたコンストラクターの代わりにファクトリメソッドを使用します — 最適なパフォーマンスに必要なすべての引数コンストラクターでは、通常、自動生成識別子などを省略したアプリケーションユースケース固有のコンストラクターを公開します。これらの all-args コンストラクターのバリアントを公開する静的ファクトリメソッド。

  • 生成されたインスタンス生成クラスとプロパティアクセッサクラスを使用できるようにする制約を必ず守ってください。

  • 生成される識別子については、すべての引数の永続化コンストラクター(推奨)または with …  メソッドと組み合わせて final フィールドを使用します

  • Lombok を使用してボイラープレートコードを回避します — 永続化操作は通常、すべての引数を取るコンストラクターを必要とするため、その宣言はフィールド割り当てに対するボイラープレートパラメーターの退屈な繰り返しとなりますが、Lombok の @AllArgsConstructor を使用することで回避することができます。

16.1.4. Kotlin サポート

Spring Data は、Kotlin の仕様を適合させて、オブジェクトの作成と変更を可能にします。

Kotlin オブジェクトの作成

Kotlin クラスはインスタンス化がサポートされており、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の data クラス Person を検討してください。

data class Person(val id: String, val name: String)

上記のクラスは、明示的なコンストラクターを持つ典型的なクラスにコンパイルされます。別のコンストラクターを追加してこのクラスをカスタマイズし、@PersistenceConstructor でアノテーションを付けてコンストラクターの設定を示します。

data class Person(var id: String, val name: String) {

    @PersistenceConstructor
    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(…) メソッドを生成するときに、新しいインスタンスを作成できます。

16.2. 規約ベースのマッピング

MappingR2dbcConverter には、追加のマッピングメタデータが提供されていない場合にオブジェクトを行にマッピングするためのいくつかの規則があります。規則は次のとおりです。

  • 短い Java クラス名は、次の方法でテーブル名にマップされます。クラス com.bigbank.SavingsAccount は savings_account テーブル名にマップします。

  • ネストされたオブジェクトはサポートされていません。

  • コンバーターは、登録されている Spring コンバーターを使用して、オブジェクトプロパティのデフォルトのマッピングを行の列と値に上書きします。

  • オブジェクトのフィールドは、行の列との間の変換に使用されます。パブリック JavaBean プロパティは使用されません。

  • コンストラクター引数名が行の最上位の列名と一致する単一の非ゼロ引数コンストラクターがある場合、そのコンストラクターが使用されます。それ以外の場合は、引数のないコンストラクターが使用されます。引数がゼロではないコンストラクターが複数ある場合、例外がスローされます。

16.3. マッピング設定

デフォルトでは(明示的に設定されていない限り)、DatabaseClient を作成すると MappingR2dbcConverter のインスタンスが作成されます。MappingR2dbcConverter の独自のインスタンスを作成できます。独自のインスタンスを作成することにより、Spring コンバーターを登録して、データベースとの間で特定のクラスをマッピングできます。

Java ベースのメタデータを使用して、MappingR2dbcConverter と DatabaseClient および ConnectionFactory を構成できます。次の例では、Spring の Java ベースの構成を使用しています。

例 86: R2DBC マッピングサポートを構成する @Configuration クラス
@Configuration
public class MyAppConfig extends AbstractR2dbcConfiguration {

  public ConnectionFactory connectionFactory() {
    return ConnectionFactories.get("r2dbc: … ");
  }

  // the following are optional

  @Override
  protected List<Object> getCustomConverters() {

    List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
    converterList.add(new org.springframework.data.r2dbc.test.PersonReadConverter());
    converterList.add(new org.springframework.data.r2dbc.test.PersonWriteConverter());
    return converterList;
  }
}

AbstractR2dbcConfiguration では、ConnectionFactory を定義するメソッドを実装する必要があります。

r2dbcCustomConversions メソッドをオーバーライドすることにより、コンバーターにコンバーターを追加できます。

AbstractR2dbcConfiguration は DatabaseClient インスタンスを作成し、databaseClient という名前でコンテナーに登録します。

16.4. メタデータベースのマッピング

Spring Data R2DBC サポート内のオブジェクトマッピング機能を最大限に活用するには、マップされたオブジェクトに @Table アノテーションを付ける必要があります。マッピングフレームワークにこのアノテーションを付ける必要はありませんが(アノテーションがなくても POJO は正しくマッピングされます)、クラスパススキャナーでドメインオブジェクトを見つけて前処理し、必要なメタデータを抽出できます。このアノテーションを使用しない場合、マッピングフレームワークは、ドメインオブジェクトのプロパティとそのメソッドを認識できるように内部メタデータモデルを構築する必要があるため、ドメインオブジェクトを最初に保存するときにアプリケーションのパフォーマンスがわずかに低下します。永続化します。次の例は、ドメインオブジェクトを示しています。

例 87: ドメインオブジェクトの例
package com.mycompany.domain;

@Table
public class Person {

  @Id
  private Long id;

  private Integer ssn;

  private String firstName;

  private String lastName;
}
@Id アノテーションは、どのプロパティを主キーとして使用するかをマッパーに伝えます。

16.4.1. デフォルトのタイプマッピング

次の表は、エンティティのプロパティタイプがマッピングにどのように影響するかを説明しています。

ソースタイプ ターゲットタイプ コメント

プリミティブ型とラッパー型

パススルー

明示的なコンバーターを使用してカスタマイズできます。

JSR-310 日付 / 時刻型

パススルー

明示的なコンバーターを使用してカスタマイズできます。

StringBigIntegerBigDecimal および UUID

パススルー

明示的なコンバーターを使用してカスタマイズできます。

Enum

String

明示的なコンバーターを登録することでカスタマイズできます。

Blob および Clob

パススルー

明示的なコンバーターを使用してカスタマイズできます。

byte[]ByteBuffer

パススルー

バイナリペイロードと見なされます。

Collection<T>

T の配列

構成されたドライバーでサポートされている場合は配列タイプへの変換、それ以外ではサポートされていません。

プリミティブ型、ラッパー型、および String の配列

ラッパー型の配列 (たとえば int[] → Integer[])

構成されたドライバーでサポートされている場合は配列タイプへの変換、それ以外ではサポートされていません。

ドライバー固有のタイプ

パススルー

中古 R2dbcDialect をシンプルタイプとしてコントリビュート。

複雑なオブジェクト

ターゲットタイプは、登録された Converter に依存します。

明示的なコンバーターが必要です。それ以外の場合はサポートされていません。

列のネイティブデータ型は、R2DBC ドライバーの型マッピングに依存します。ドライバーは、ジオメトリタイプなどの追加のシンプルタイプを提供できます。

16.4.2. マッピングアノテーションの概要

MappingR2dbcConverter は、メタデータを使用してオブジェクトの行へのマッピングを駆動できます。次のアノテーションを使用できます。

  • @Id: 主キーをマークするためにフィールドレベルで適用されます。

  • @Table: このクラスがデータベースへのマッピングの候補であることを示すために、クラスレベルで適用されます。データベースが保存されているテーブルの名前を指定できます。

  • @Transient: デフォルトでは、すべてのフィールドが行にマップされます。このアノテーションは、それが適用されるフィールドをデータベースに格納することから除外します。コンバーターはコンストラクターの引数の値を具体化できないため、永続的なコンストラクター内では一時的なプロパティを使用できません。

  • @PersistenceConstructor: 指定されたコンストラクターをマークする  —  パッケージでさえ保護されたもの  —  データベースからオブジェクトをインスタンス化するときに使用します。コンストラクターの引数は、名前によって、取得した行の値にマップされます。

  • @Value: このアノテーションは Spring Framework の一部です。マッピングフレームワーク内で、コンストラクター引数に適用できます。これにより、Spring 式言語ステートメントを使用して、ドメインオブジェクトの構築に使用される前に、データベースで取得されたキーの値を変換できます。特定の行の列を参照するには、次のような式を使用する必要があります。: @Value("#root.myProperty") ここで、root は指定された Row のルートを指します。

  • @Column: フィールドレベルで適用され、行に表示される列の名前を記述するため、クラスのフィールド名とは異なる名前を使用できます。

  • @Version: フィールドレベルで適用され、楽観的ロックに使用され、保存操作の変更がチェックされます。値は null (プリミティブ型の場合は zero)であり、エンティティが新規であるためのマーカーと見なされます。最初に格納される値は zero (プリミティブ型の場合は one)です。バージョンは、更新のたびに自動的にインクリメントされます。詳細については、楽観的ロックを参照してください。

マッピングメタデータインフラストラクチャは、テクノロジーに依存しない別個の spring-data-commons プロジェクトで定義されています。アノテーションベースのメタデータをサポートするために、R2DBC サポートでは特定のサブクラスが使用されます。他の戦略を導入することもできます(需要がある場合)。

16.4.3. カスタマイズされたオブジェクト構築

マッピングサブシステムでは、コンストラクターに @PersistenceConstructor アノテーションを付けることにより、オブジェクトの構成をカスタマイズできます。コンストラクターのパラメーターに使用される値は、次の方法で解決されます。

  • パラメーターに @Value アノテーションが付けられている場合、指定された式が評価され、結果がパラメーター値として使用されます。

  • Java タイプに、入力行の指定されたフィールドと名前が一致するプロパティがある場合、そのプロパティ情報を使用して、入力フィールド値を渡す適切なコンストラクターパラメーターを選択します。これは、パラメーター名情報が Java .class ファイルに存在する場合にのみ機能します。これは、デバッグ情報を使用してソースをコンパイルするか、Java 8 の javac の -parameters コマンドラインスイッチを使用して実現できます。

  • そうでない場合、MappingException がスローされ、指定されたコンストラクターパラメーターをバインドできなかったことを示します。

class OrderItem {

  private @Id final String id;
  private final int quantity;
  private final double unitPrice;

  OrderItem(String id, int quantity, double unitPrice) {
    this.id = id;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }

  // getters/setters ommitted
}

16.4.4. 明示的なコンバーターを使用したマッピングのオーバーライド

オブジェクトを保管および照会する場合、R2dbcConverter インスタンスを使用して、すべての Java タイプから OutboundRow インスタンスへのマッピングを処理すると便利なことがよくあります。ただし、R2dbcConverter インスタンスにほとんどの作業を行わせたい場合がありますが、パフォーマンスを最適化するために、特定のタイプの変換を選択的に処理できます。

変換を自分で選択的に処理するには、1 つ以上の org.springframework.core.convert.converter.Converter インスタンスを R2dbcConverter に登録します。

AbstractR2dbcConfiguration の r2dbcCustomConversions メソッドを使用して、コンバーターを構成できます。この章の最初の例は、 Java を使用して構成を実行する方法を示しています。

カスタムトップレベルエンティティ変換では、変換に非対称型が必要です。受信データは、R2DBC の Row から抽出されます。(INSERT/UPDATE ステートメントで使用される)送信データは OutboundRow として表され、後でステートメントにアセンブルされます。

Spring コンバーター実装の次の例は、Row から Person POJO に変換します。

@ReadingConverter
 public class PersonReadConverter implements Converter<Row, Person> {

  public Person convert(Row source) {
    Person p = new Person(source.get("id", String.class),source.get("name", String.class));
    p.setAge(source.get("age", Integer.class));
    return p;
  }
}

コンバーターは特異なプロパティに適用されることに注意してください。コレクションプロパティ(例: Collection<Person>)は反復され、要素ごとに変換されます。コレクションコンバーター(例: Converter<List<Person>>, OutboundRow)はサポートされていません。

R2DBC は、ボックス化されたプリミティブ(int.class ではなく Integer.class)を使用して、プリミティブ値を返します。

次の例は、Person から OutboundRow に変換します。

@WritingConverter
public class PersonWriteConverter implements Converter<Person, OutboundRow> {

  public OutboundRow convert(Person source) {
    OutboundRow row = new OutboundRow();
    row.put("id", SettableValue.from(source.getId()));
    row.put("name", SettableValue.from(source.getFirstName()));
    row.put("age", SettableValue.from(source.getAge()));
    return row;
  }
}
明示的なコンバーターを使用した列挙型マッピングのオーバーライド

Postgres (GitHub) などの一部のデータベースは、データベース固有の列挙型列タイプを使用して、列挙値をネイティブに書き込むことができます。Spring Data は、移植性を最大化するために、デフォルトで Enum 値を String 値に変換します。実際の列挙値を保持するには、ソースタイプとターゲットタイプが実際の列挙タイプを使用して Enum.name() 変換を使用しないようにする @Writing コンバーターを登録します。さらに、ドライバーが列挙型を表す方法を認識できるように、ドライバーレベルで列挙型を構成する必要があります。

次の例は、Color 列挙値をネイティブに読み書きするための関連コンポーネントを示しています。

enum Color {
    Grey, Blue
}

class ColorConverter extends EnumWriteSupport<Color> {

}


class Product {
    @Id long id;
    Color color;

    //  …
}

17. Kotlin サポート

Kotlin (英語) は、JVM(およびその他のプラットフォーム)をターゲットとする静的に型付けされた言語であり、簡潔でエレガントなコードを作成できると同時に、Java で作成された既存のライブラリとの優れた相互運用性 (英語) を提供します。

Spring Data は、Kotlin に対するファーストクラスのサポートを提供し、開発者は、Spring Data が Kotlin ネイティブフレームワークであるかのように Kotlin アプリケーションを作成できます。

Kotlin で Spring アプリケーションを構築する最も簡単な方法は、Spring Boot とその専用 Kotlin サポートを活用することです。この包括的なチュートリアルでは、start.spring.io (英語) を使用して Kotlin で Spring Boot アプリケーションを構築する方法を説明します。

17.1. 要件

Spring Data は Kotlin 1.3 をサポートし、kotlin-stdlib (英語) (または kotlin-stdlib-jdk8 (英語) などのバリアントの 1 つ)と kotlin-reflect (英語) がクラスパスに存在する必要があります。start.spring.io (英語) を介して Kotlin プロジェクトをブートストラップする場合、これらはデフォルトで提供されます。

17.2. null 安全

Kotlin の主要な機能の 1 つは、コンパイル時に null 値をきれいに処理する null 安全 (英語) です。これにより、Optional などのラッパーのコストを支払うことなく、null 値宣言と「値または値なし」セマンティクスの表現により、アプリケーションの安全性が向上します。(Kotlin では、null 許容値を持つ関数構成体を使用できます。Kotlinnull セーフティに関する包括的なガイド (英語) を参照してください。)

Java では、型システムで null 安全性を表現できませんが、Spring Data API には、org.springframework.lang パッケージで宣言された JSR-305 (英語) ツールフレンドリーアノテーションが付けられています。デフォルトでは、Kotlin で使用される Java API の型はプラットフォーム型 (英語) として認識され、そのため null チェックが緩和されます。JSR-305 アノテーションの Kotlin サポート (英語) および Spring nullability アノテーションは、Spring Data API 全体に対して Kotlin 開発者に null の安全性を提供し、コンパイル時に null 関連の課題を処理するという利点があります。

Spring Data リポジトリに null の安全性がどのように適用されるかについては、リポジトリメソッドの null 処理を参照してください。

-Xjsr305 コンパイラフラグに次のオプションを追加することにより、JSR-305 チェックを構成できます。-Xjsr305={strict|warn|ignore}

Kotlin バージョン 1.1 + の場合、デフォルトの動作は -Xjsr305=warn と同じです。strict 値は、Spring Data API の null の安全性を考慮に入れる必要があります。Spring API から推論された Kotlin タイプ。ただし、Spring API の null 可能性宣言がマイナーリリース間でも進化し、将来さらに多くのチェックが追加される可能性があることを知って使用する必要があります。

ジェネリック型の引数、可変引数、および配列要素の NULL 可能性はまだサポートされていませんが、今後のリリースでサポートされる予定です。

17.3. オブジェクトマッピング

Kotlin オブジェクトの具体化方法の詳細については、Kotlin サポートを参照してください。

17.4. 拡張

Kotlin 拡張機能 (英語) は、既存のクラスを追加機能で拡張する機能を提供します。Spring Data Kotlin API はこれらの拡張機能を使用して、既存の Spring API に新しい Kotlin 固有の便利な機能を追加します。

Kotlin 拡張機能を使用するには、インポートする必要があることに注意してください。静的インポートと同様に、ほとんどの場合、IDE は自動的にインポートを提案するはずです。

例:Kotlin の型パラメーターを具体化 (英語) は JVM ジェネリック型消去 (英語) の回避策を提供し、Spring Data はこの機能を利用するためのいくつかの拡張機能を提供します。これにより、より良い Kotlin API が可能になります。

Java で SWCharacter オブジェクトのリストを取得するには、通常、次のように記述します。

Flux<SWCharacter> characters = client.select().from(SWCharacter.class).fetch().all();

Kotlin および Spring Data 拡張機能を使用すると、代わりに以下を記述できます。

val characters =  client.select().from<SWCharacter>().fetch().all()
// or (both are equivalent)
val characters : Flux<SWCharacter> = client.select().from().fetch().all()

Java と同様に、Kotlin の characters は厳密に型指定されていますが、Kotlin の巧妙な型推論により、構文を短くすることができます。

Spring Data R2DBC は、次の拡張機能を提供します。

  • DatabaseClient および Criteria のジェネリックサポートを具体化しました。

  • DatabaseClientコルーチン拡張。

17.5. コルーチン

Kotlin コルーチン (英語) は、ノンブロッキングコードを命令的に記述することを可能にする軽量スレッドです。言語側では、suspend 関数は非同期操作の抽象化を提供し、ライブラリ側では kotlinx.coroutines (GitHub) async { } (英語) などの関数と Flow (英語) などの型を提供します。

Spring Data モジュールは、次のスコープでコルーチンのサポートを提供します。

17.5.1. 依存関係

kotlinx-coroutines-corekotlinx-coroutines-reactive および kotlinx-coroutines-reactor の依存関係がクラスパスにある場合、コルーチンのサポートが有効になります。

例 88: Maven pom.xml に追加する依存関係
<dependency>
  <groupId>org.jetbrains.kotlinx</groupId>
  <artifactId>kotlinx-coroutines-core</artifactId>
</dependency>

<dependency>
  <groupId>org.jetbrains.kotlinx</groupId>
  <artifactId>kotlinx-coroutines-reactive</artifactId>
</dependency>

<dependency>
  <groupId>org.jetbrains.kotlinx</groupId>
  <artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>
サポートされているバージョン 1.3.0 以上。

17.5.2. Reactive はコルーチンにどのように変換されますか?

戻り値の場合、Reactive API から Coroutines API への変換は次のとおりです。

  • fun handler(): Mono<Void> は suspend fun handler() になります

  • fun handler(): Mono<T> は、Mono を空にできるかどうかに応じて、suspend fun handler(): T または suspend fun handler(): T? になります。(より静的に型付けされるという利点がある)

  • fun handler(): Flux<T> は fun handler(): Flow<T> になります

Flow (英語) は、コルーチンの世界で Flux に相当し、ホットストリームまたはコールドストリーム、有限ストリームまたは無限ストリームに適していますが、主な違いは次のとおりです。

コルーチンと並行してコードを実行する方法など、詳細については、Spring、コルーチン、および Kotlin Flow とのリアクティブ化 (英語) に関するこのブログ投稿を参照してください。

17.5.3. リポジトリ

コルーチンリポジトリの例を次に示します。

interface CoroutineRepository : CoroutineCrudRepository<User, String> {

    suspend fun findOne(id: String): User

    fun findByFirstname(firstname: String): Flow<User>

    suspend fun findAllByFirstname(id: String): List<User>
}

コルーチンリポジトリはリアクティブリポジトリ上に構築されており、Kotlin のコルーチンを介したデータアクセスのノンブロッキング性を公開します。コルーチンリポジトリのメソッドは、クエリメソッドまたはカスタム実装のいずれかによってサポートできます。カスタム実装メソッドを呼び出すと、コルーチンの呼び出しが実際の実装メソッドに伝播されます。カスタムメソッドが suspend -able の場合、実装メソッドが Mono や Flux などのリアクティブ型を返す必要はありません。

コルーチンリポジトリは、リポジトリが CoroutineCrudRepository インターフェースを継承する場合にのみ検出されます。

付録

付録 A: リポジトリクエリキーワード

サポートされているクエリキーワード

次の表は、Spring Data リポジトリクエリ派生メカニズムで一般的にサポートされているキーワードの一覧です。ただし、ここにリストされている一部のキーワードは特定のストアでサポートされていない可能性があるため、サポートされているキーワードの正確なリストについては、ストア固有のドキュメントを参照してください。

表 5: クエリキーワード
論理キーワード キーワード表現

AND

And

OR

Or

AFTER

AfterIsAfter

BEFORE

BeforeIsBefore

CONTAINING

ContainingIsContainingContains

BETWEEN

BetweenIsBetween

ENDING_WITH

EndingWithIsEndingWithEndsWith

EXISTS

Exists

FALSE

FalseIsFalse

GREATER_THAN

GreaterThanIsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqualIsGreaterThanEqual

IN

InIsIn

IS

IsEquals, (またはキーワードなし)

IS_EMPTY

IsEmptyEmpty

IS_NOT_EMPTY

IsNotEmptyNotEmpty

IS_NOT_NULL

NotNullIsNotNull

IS_NULL

NullIsNull

LESS_THAN

LessThanIsLessThan

LESS_THAN_EQUAL

LessThanEqualIsLessThanEqual

LIKE

LikeIsLike

NEAR

NearIsNear

NOT

NotIsNot

NOT_IN

NotInIsNotIn

NOT_LIKE

NotLikeIsNotLike

REGEX

RegexMatchesRegexMatches

STARTING_WITH

StartingWithIsStartingWithStartsWith

TRUE

TrueIsTrue

WITHIN

WithinIsWithin

付録 B: リポジトリクエリの戻り値のタイプ

サポートされているクエリの戻り値のタイプ

次の表に、Spring Data リポジトリで一般的にサポートされる戻り値の型を示します。ただし、ここにリストされている一部のタイプは特定のストアでサポートされていない可能性があるため、サポートされる戻り型の正確なリストについてはストア固有のドキュメントを参照してください。

地理空間タイプ(GeoResultGeoResultsGeoPage など)は、地理空間クエリをサポートするデータストアでのみ使用できます。
表 6: クエリの戻り型
戻りの型 説明

void

戻り値がないことを示します。

プリミティブ

Java プリミティブ。

ラッパーの種類

Java ラッパータイプ。

T

一意のエンティティ。クエリメソッドが最大で 1 つの結果を返すことを期待します。結果が見つからない場合、null が返されます。複数の結果が IncorrectResultSizeDataAccessException をトリガーします。

Iterator<T>

Iterator

Collection<T>

Collection

List<T>

List

Optional<T>

Java 8 または Guava Optional クエリメソッドが最大で 1 つの結果を返すことを期待します。結果が見つからない場合、Optional.empty() または Optional.absent() が返されます。複数の結果が IncorrectResultSizeDataAccessException をトリガーします。

Option<T>

Scala または Vavr Option タイプのいずれか。前述の Java 8 の Optional と同じ意味の振る舞い。

Stream<T>

Java 8 Stream

Streamable<T>

Iterable の便利な拡張機能で、結果のストリーミング、マッピング、フィルタリング、連結などのメソッドを直接公開します。

Streamable を実装し、Streamable コンストラクターまたはファクトリメソッド引数を取るタイプ

Streamable を引数として取るコンストラクターまたは  … .of(…)/ … .valueOf(…) ファクトリメソッドを公開する型。詳細については、カスタムのストリーミング可能なラッパータイプを返すを参照してください。

Vavr SeqListMapSet

Vavr コレクションタイプ。詳細については、Vavr コレクションのサポートを参照してください。

Future<T>

Future メソッドに @Async のアノテーションが付けられることを期待し、Spring の非同期メソッド実行機能を有効にする必要があります。

CompletableFuture<T>

Java 8 CompletableFuture メソッドに @Async のアノテーションが付けられることを期待し、Spring の非同期メソッド実行機能を有効にする必要があります。

ListenableFuture

org.springframework.util.concurrent.ListenableFuture メソッドに @Async のアノテーションが付けられることを期待し、Spring の非同期メソッド実行機能を有効にする必要があります。

Slice

使用可能なデータがさらにあるかどうかを示すサイズのデータチャンク。Pageable メソッドパラメーターが必要です。

Page<T>

結果の総数などの追加情報を含む SlicePageable メソッドパラメーターが必要です。

GeoResult<T>

参照場所までの距離などの追加情報を含む結果エントリ。

GeoResults<T>

参照場所までの平均距離などの追加情報を含む GeoResult<T> のリスト。

GeoPage<T>

参照位置までの平均距離など、Page と GeoResult<T>

Mono<T>

リアクティブリポジトリを使用して 0 個または 1 個の要素を放出するプロジェクト Reactor Mono。クエリメソッドが最大で 1 つの結果を返すことを期待します。結果が見つからない場合、Mono.empty() が返されます。複数の結果が IncorrectResultSizeDataAccessException をトリガーします。

Flux<T>

プロジェクト Reactor Flux は、リアクティブリポジトリを使用してゼロ、1 つ、または多くの要素を放出します。Flux を返すクエリは、無限の数の要素も放出できます。

Single<T>

リアクティブリポジトリを使用して単一の要素を放出する RxJava Single。クエリメソッドが最大で 1 つの結果を返すことを期待します。結果が見つからない場合、Mono.empty() が返されます。複数の結果が IncorrectResultSizeDataAccessException をトリガーします。

Maybe<T>

リアクティブリポジトリを使用して 0 個または 1 個の要素を放出する RxJava Maybe。クエリメソッドが最大で 1 つの結果を返すことを期待します。結果が見つからない場合、Mono.empty() が返されます。複数の結果が IncorrectResultSizeDataAccessException をトリガーします。

Flowable<T>

リアクティブリポジトリを使用してゼロ、1 つ、または多くの要素を放出する RxJava FlowableFlowable を返すクエリは、無限の数の要素も放出できます。

付録 C: 移行ガイド

次のセクションでは、Spring Data R2DBC の新しいバージョンに移行する方法について説明します。

1.1.x から 1.2.x へのアップグレード

Spring Data R2DBC は、R2DBC が Spring アプリケーションとどの程度統合できるかを評価することを目的として開発されました。主な側面の 1 つは、R2DBC サポートが有用であることが証明されたら、コアサポートを Spring Framework に移行することでした。Spring Framework 5.3 には、新しいモジュール Spring R2DBC(spring-r2dbc)が付属しています。

spring-r2dbc は、Spring Data R2DBC によって最初に提供されたコア R2DBC 機能(DatabaseClient のスリムなバリアント、トランザクションマネージャー、接続ファクトリの初期化、例外変換)を提供します。1.2.0 リリースは、次のセクションで概説するいくつかの変更を加えることにより、Spring R2DBC で提供されるものと一致します。

Spring R2DBC の DatabaseClient は、純粋な SQL 指向のインターフェースをカプセル化するより軽量な実装です。SQL ステートメントを実行する方法が DatabaseClient.execute(…) から DatabaseClient.sql(…) に変更されていることに気付くでしょう。CRUD 操作用の流暢な API は R2dbcEntityTemplate に移行しました。

非推奨

  • o.s.d.r2dbc.core.DatabaseClient とそのサポートクラス ConnectionAccessorFetchSpecSqlProvider およびその他のいくつかの非推奨。NamedParameterExpander などの名前付きパラメーターサポートクラスは、Spring R2DBC の DatabaseClient 実装によってカプセル化されているため、これはそもそも内部 API であったため、代替は提供していません。org.springframework.r2dbc.core から入手可能な o.s.r2dbc.core.DatabaseClient およびそれらの Spring R2DBC 代替を使用してください。エンティティベースのメソッド(select/insert/update/delete)メソッドは、バージョン 1.1 で導入された R2dbcEntityTemplate を介して利用できます。

  • o.s.d.r2dbc.connectionfactoryo.s.d.r2dbc.connectionfactory.init および o.s.d.r2dbc.connectionfactory.lookup パッケージの非推奨。o.s.r2dbc.connection にある Spring R2DBC のバリアントを使用してください。

  • o.s.d.r2dbc.convert.ColumnMapRowMapper の非推奨。代わりに o.s.r2dbc.core.ColumnMapRowMapper を使用してください。

  • バインディングサポートクラス o.s.d.r2dbc.dialect.BindingsBindMarkerBindMarkersBindMarkersFactory および関連するタイプの非推奨。org.springframework.r2dbc.core.binding の代替を使用してください。

  • BadSqlGrammarExceptionUncategorizedR2dbcException の非推奨と o.s.d.r2dbc.support での例外変換。Spring R2DBC は、現在 o.s.r2dbc.connection.ConnectionFactoryUtils#convertR2dbcException を通じて利用可能な SPI なしのスリムな例外変換バリアントを提供します。

Spring R2DBC が提供する代替の使用

移行を容易にするために、いくつかの非推奨のタイプは、Spring R2DBC によって提供されるそれらの代替のサブタイプになりました。Spring Data R2DBC では、いくつかのメソッドが変更されたか、Spring R2DBC タイプを受け入れる新しいメソッドが導入されました。具体的には、次のクラスが変更されます。

  • R2dbcEntityTemplate

  • R2dbcDialect

  • org.springframework.data.r2dbc.query のタイプ

これらのタイプを直接操作する場合は、インポートを確認して更新することをお勧めします。

重大な変更

  • OutboundRow とステートメントマッパーが SettableValue の使用から Parameter に切り替わりました

  • リポジトリファクトリサポートには、o.s.data.r2dbc.core.DatabaseClient ではなく o.s.r2dbc.core.DatabaseClient が必要です。

依存関係の変更

Spring R2DBC を使用するには、次の依存関係を必ず含めてください。

  • org.springframework:spring-r2dbc