© 2018-2019 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.1.0 リリースの新機能

9.2. Spring Data R2DBC 1.0.0 リリースの新機能

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

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

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

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

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

9.3. Spring Data R2DBC 1.0.0RC1 の新機能

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

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

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

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

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

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

9.4. Spring Data R2DBC 1.0.0M2 の新機能

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

9.5. Spring Data R2DBC 1.0.0M1 の新機能

  • 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-releasetrain</artifactId>
      <version>Neumann-SR4</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

現在のリリーストレインバージョンは Neumann-SR4 です。トレイン名はアルファベット順に昇順になり、現在利用可能なトレインがここにリストされます (GitHub) 。バージョン名は次のパターンに従います: ${name}-${release}、ここで release は以下のいずれかです。

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

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

  • RC1RC2 など:リリース候補

  • RELEASE: GA リリース

  • SR1SR2 など:サービスリリース

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.2.9.RELEASE 以上の 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 で登録されます。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(…) メソッドの 1 つが呼び出されるたびに呼び出されます。

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 操作を実行する際の生産性を向上させる DatabaseClient ヘルパークラス。

  • Spring のポータブルデータアクセス例外階層への例外変換。

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

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

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

ほとんどのタスクでは、DatabaseClient またはリポジトリサポートを使用する必要があります。どちらもリッチマッピング機能を使用します。DatabaseClient は、アドホック 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.1.4.RELEASE</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.2.9.RELEASE</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 クラスを作成して永続化できます。

package org.spring.r2dbc.example;

public class Person {

  private String id;
  private String name;
  private 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 + "]";
  }
}

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

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

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

package org.spring.r2dbc.example;

public class R2dbcApp {

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

  public static void main(String[] args) throws Exception {

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

    DatabaseClient client = DatabaseClient.create(connectionFactory);

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

    client.insert()
      .into(Person.class)
      .using(new Person("joe", "Joe", 34))
      .then()
      .as(StepVerifier::create)
      .verifyComplete();

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

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

2018-11-28 10:47:03,893 DEBUG ata.r2dbc.function.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 ata.r2dbc.function.DefaultDatabaseClient: 908 - Executing SQL statement [INSERT INTO person (id, name, age) VALUES($1, $2, $3)]
2018-11-28 10:47:04,092 DEBUG ata.r2dbc.function.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(DatabaseClient)で中央ヘルパークラスのインスタンスを作成できます。

  • マッパーは、追加のメタデータを必要とせずに、標準の 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. DatabaseClient の概要

Spring Data R2DBC には、データベースの相互作用のためのリアクティブなノンブロッキング DatabaseClient が含まれています。クライアントには、宣言型構成のためのリアクティブタイプを備えた関数で流暢な API があります。DatabaseClient は、リソース処理(接続の開始や終了など)をカプセル化して、アプリケーションコードが SQL クエリを実行したり、高レベルの機能(データの挿入や選択など)を呼び出したりできるようにします。

DatabaseClient は最近開発されたアプリケーションコンポーネントであり、時間の経過とともに拡張される可能性が高い便利なメソッドの最小限のセットを提供します。
一度構成すると、DatabaseClient はスレッドセーフになり、複数のインスタンスで再利用できます。

DatabaseClient のもう 1 つの中心的な機能は、R2DBC ドライバーによってスローされた例外を Spring のポータブルデータアクセス例外階層に変換することです。詳細については、「例外変換」を参照してください。

次のセクションには、Spring コンテナーのコンテキストで DatabaseClient を処理する方法の例が含まれています。

13.4.1. DatabaseClient オブジェクトの作成

DatabaseClient オブジェクトを作成する最も簡単な方法は、次のように静的ファクトリメソッドを使用することです。

DatabaseClient.create(ConnectionFactory connectionFactory)

上記の方法は、デフォルト設定で DatabaseClient を作成します。

DatabaseClient.builder() から Builder インスタンスを取得することもできます。次のメソッドを呼び出すことにより、クライアントをカスタマイズできます。

  • … .exceptionTranslator(…): 特定の R2dbcExceptionTranslator を指定して、R2DBC 例外を Spring のポータブルデータアクセス例外階層に変換する方法をカスタマイズします。詳細については、「例外変換」を参照してください。

  • … .dataAccessStrategy(…): SQL クエリの生成方法とオブジェクトのマッピング方法を設定します。

一度構築されると、DatabaseClient インスタンスは不変です。ただし、次の例に示すように、元のインスタンスに影響を与えることなく、クローンを作成して変更されたコピーを作成できます。

DatabaseClient client1 = DatabaseClient.builder()
  .exceptionTranslator(exceptionTranslatorA).build();

DatabaseClient client2 = client1.mutate()
  .exceptionTranslator(exceptionTranslatorB).build();

13.4.2. データベース接続の制御

Spring Data R2DBC は、ConnectionFactory を介してデータベースへの接続を取得します。ConnectionFactory は R2DBC 仕様の一部であり、一般化された接続ファクトリです。コンテナーまたはフレームワークで、接続プールとトランザクション管理の課題をアプリケーションコードから隠すことができます。

Spring Data R2DBC を使用する場合、R2DBC ドライバーを使用して ConnectionFactory を作成できます。ConnectionFactory 実装は、同じ接続または異なる接続を返すか、接続プールを提供できます。DatabaseClient は、ConnectionFactory を使用して、複数の操作にわたる特定の接続に親和性を持たずに、各操作の接続を作成および解放します。

H2 をデータベースとして使用すると仮定すると、典型的なプログラムによるセットアップは次のリストのようになります。

H2ConnectionConfiguration config =  …  (1)
ConnectionFactory factory = new H2ConnectionFactory(config); (2)

DatabaseClient client = DatabaseClient.create(factory); (3)
1 データベース固有の構成を準備する (ホスト、ポート、資格情報など)
2 その構成を使用して接続ファクトリを作成します。
3 その接続ファクトリを使用する DatabaseClient を作成します。

13.5. 例外変換

Spring フレームワークは、さまざまなデータベースおよびマッピングテクノロジーの例外変換を提供します。R2DBC の Spring サポートは、R2dbcExceptionTranslator インターフェースの実装を提供することにより、この機能を継承します。

R2dbcExceptionTranslator は、R2dbcException と Spring 自身の org.springframework.dao.DataAccessException の間で変換できるクラスによって実装されるインターフェースです。これは、データアクセス戦略に関して不可知です。実装は、精度を高めるために汎用(SQLState コードなど)または独自仕様(Postgres エラーコードなど)にすることができます。

R2dbcExceptionSubclassTranslator は、デフォルトで使用される R2dbcExceptionTranslator の実装です。R2DBC の分類された例外階層を考慮して、これらを Spring の一貫した例外階層に変換します。R2dbcExceptionSubclassTranslator は、例外を変換できない場合、SqlStateR2dbcExceptionTranslator をフォールバックとして使用します。

SqlErrorCodeR2dbcExceptionTranslator は、Spring JDBC の SQLErrorCodes を使用して特定のベンダーコードを使用します。SQLState 実装よりも正確です。エラーコードの変換は、SQLErrorCodes と呼ばれる JavaBean タイプクラスに保持されているコードに基づいています。このクラスのインスタンスは、SQLErrorCodesFactory によって作成され、読み込まれます。SQLErrorCodesFactory は、Spring のデータアクセスモジュールから sql-error-codes.xml という名前の構成ファイルの内容に基づいて SQLErrorCodes を作成するためのファクトリです。このファイルにはベンダーコードが入力され、ConnectionFactoryMetadata から取得した ConnectionFactoryName に基づいています。使用している実際のデータベースのコードが使用されます。

SqlErrorCodeR2dbcExceptionTranslator は、次の順序で一致ルールを適用します。

  1. サブクラスによって実装されるカスタム変換。通常、提供された具体的な SqlErrorCodeR2dbcExceptionTranslator が使用されるため、この規則は適用されません。サブクラスの実装を実際に提供した場合にのみ適用されます。

  2. SQLErrorCodes クラスの customSqlExceptionTranslator プロパティとして提供される SQLExceptionTranslator インターフェースのカスタム実装。

  3. エラーコードの一致が適用されます。

  4. フォールバックトランスレーターを使用します。

デフォルトでは、SQLErrorCodesFactory はエラーコードとカスタム例外変換の定義に使用されます。それらは sql-error-codes.xml という名前のファイル(クラスパス上にある必要があります)から検索され、一致する SQLErrorCodes インスタンスは、使用中のデータベースのデータベースメタデータのデータベース名に基づいて配置されます。SQLErrorCodesFactory には Spring JDBC が必要です。

次の例に示すように、SqlErrorCodeR2dbcExceptionTranslator を継承できます。

public class CustomSqlErrorCodeR2dbcExceptionTranslator extends SqlErrorCodeR2dbcExceptionTranslator {

  protected DataAccessException customTranslate(String task, String sql, R2dbcException r2dbcex) {

    if (sqlex.getErrorCode() == -12345) {
      return new DeadlockLoserDataAccessException(task, r2dbcex);
    }

    return null;
  }
}

前の例では、特定のエラーコード(-12345)が変換されますが、他のエラーはデフォルトのトランスレーター実装によって変換されずに残ります。このカスタムトランスレーターを使用するには、exceptionTranslator ビルダーメソッドを使用して DatabaseClient を構成する必要があり、このトランスレーターが必要なすべてのデータアクセス処理にこの DatabaseClient を使用する必要があります。次の例は、このカスタムトランスレータの使用方法を示しています。

ConnectionFactory connectionFactory =  … ;

CustomSqlErrorCodeR2dbcExceptionTranslator exceptionTranslator =
  new CustomSqlErrorCodeR2dbcExceptionTranslator();

DatabaseClient client = DatabaseClient.builder()
  .connectionFactory(connectionFactory)
  .exceptionTranslator(exceptionTranslator)
  .build();

13.6. ステートメントの実行

DatabaseClient は、ステートメントを実行する基本機能を提供します。次の例は、新しいテーブルを作成する最小限で完全に機能するコードに含める必要があるものを示しています。

Mono<Void> completion = client.execute("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
        .then();

DatabaseClient は、便利で流れるように使用できるように設計されています。実行仕様の各段階で中間、継続、および終了メソッドを公開します。上記の例では、then() を使用して、クエリ(または SQL クエリに複数のステートメントが含まれる場合はクエリ)が完了するとすぐに完了する完了 Publisher を返します。

execute(…) は、SQL クエリ文字列またはクエリ Supplier<String> のいずれかを受け入れ、クエリがデータベースに送信されるまで実際のクエリの作成を延期します。

13.6.1. クエリの実行

SQL クエリは、値または影響を受ける行の数を返すことができます。DatabaseClient は、発行されたクエリに応じて、更新された行の数または行自体を返すことができます。

次の例は、更新された行の数を返す UPDATE ステートメントを示しています。

Mono<Integer> affectedRows = client.execute("UPDATE person SET name = 'Joe'")
        .fetch().rowsUpdated();

SELECT クエリを実行すると、異なるタイプの結果、特に表形式の結果が返されます。通常、表形式のデータは、各 Row をストリーミングすることによって消費されます。前の例で fetch() の使用に気づいたかもしれません。fetch() は、使用するデータの量を指定できる継続演算子です。

Mono<Map<String, Object>> first = client.execute("SELECT id, name FROM person")
        .fetch().first();

first() を呼び出すと、結果から最初の行が返され、残りの行が破棄されます。次の演算子を使用してデータを使用できます。

  • first() は、結果全体の最初の行を返します。

  • one() は結果を 1 つだけ返し、結果にさらに行が含まれていると失敗します。

  • all() は、結果のすべての行を返します。

  • rowsUpdated() は、影響を受ける行の数(INSERT カウント、UPDATE カウント)を返します。

デフォルトでは、DatabaseClient クエリは結果を列名の Map として値に返します。次のように、as(Class<T>) 演算子を適用することにより、タイプマッピングをカスタマイズできます。

Flux<Person> all = client.execute("SELECT id, name FROM mytable")
        .as(Person.class)
        .fetch().all();

as(…) は規約ベースのオブジェクトマッピングを適用し、結果の列を POJO にマップします。

13.6.2. マッピング結果

抽出 BiFunction<Row, RowMetadata, T> を提供することにより、Map および POJO 結果抽出より結果抽出をカスタマイズできます。抽出機能は、R2DBC の Row および RowMetadata オブジェクトと直接対話し、任意の値(特異値、コレクションとマップ、およびオブジェクト)を返すことができます。

次の例では、id 列を抽出し、その値を出力します。

Flux<String> names = client.execute("SELECT name FROM person")
        .map((row, rowMetadata) -> row.get("id", String.class))
        .all();
null は ?

リレーショナルデータベースの結果には、null 値を含めることができます。Reactive Streams 仕様は、null 値の発行を禁止しています。この要件により、抽出機能での適切な null 処理が義務付けられています。Row から null 値を取得できますが、null 値を発行しないでください。null 値をオブジェクトにラップして(たとえば、特異値の場合は Optional)、抽出機能によって null 値が直接戻されないようにする必要があります。

13.6.3. クエリへの値のバインド

一般的なアプリケーションでは、何らかの入力に従って行を選択または更新するために、パラメーター化された SQL ステートメントが必要です。これらは通常、WHERE 句によって制約された SELECT ステートメント、または入力パラメーターを受け入れる INSERT および UPDATE ステートメントです。パラメーター化されたステートメントは、パラメーターが適切にエスケープされない場合、SQL インジェクションのリスクを負います。DatabaseClient は R2DBC の bind API を利用して、クエリパラメーターの SQL インジェクションのリスクを排除します。execute(…) 演算子を使用してパラメーター化された SQL ステートメントを提供し、パラメーターを実際の Statement にバインドできます。次に、R2DBC ドライバーは、準備されたステートメントとパラメーター置換を使用してステートメントを実行します。

パラメーターバインディングは、2 つのバインディング戦略をサポートしています。

  • インデックスによる、ゼロベースのパラメーターインデックスの使用。

  • 名前別、プレースホルダー名を使用。

次の例は、クエリのパラメーターバインドを示しています。

db.execute("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
    .bind("id", "joe")
    .bind("name", "Joe")
    .bind("age", 34);
R2DBC ネイティブバインドマーカー

R2DBC は、実際のデータベースベンダーに依存するデータベースネイティブバインドマーカーを使用します。例として、Postgres は $1$2$n などのインデックス付きマーカーを使用します。別の例は SQL Server で、これは @ で始まる名前付きバインドマーカーを使用します。

これは、バインドマーカーとして ? を必要とする JDBC とは異なります。JDBC では、実際のドライバーは、ステートメントの処理の一部として ? バインドマーカーをデータベースネイティブマーカーに変換します。

Spring Data R2DBC を使用すると、:name 構文でネイティブバインドマーカーまたは名前付きバインドマーカーを使用できます。

名前付きパラメーターのサポートでは、R2dbcDialect インスタンスを使用して、クエリの実行時に名前付きパラメーターをネイティブバインドマーカーに拡張します。これにより、さまざまなデータベースベンダー間でクエリの移植性をある程度確保できます。

クエリプリプロセッサは、名前の Collection パラメーターを一連のバインドマーカーに展開し、引数の数に基づいて動的なクエリを作成する必要をなくします。ネストされたオブジェクト配列は、(たとえば)選択リストを使用できるように拡張されています。

次のクエリを検討してください。

SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))

上記のクエリはパラメーター化して、次のように実行できます。

List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann",  50});

db.execute("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
    .bind("tuples", tuples)
選択リストの使用はベンダーに依存します。

次の例は、IN 述語を使用したより単純なバリアントを示しています。

db.execute("SELECT id, name, state FROM table WHERE age IN (:ages)")
    .bind("ages", Arrays.asList(35, 50))

13.6.4. ステートメントフィルター

次の例に示すように、Statement フィルター(StatementFilterFunction)から DatabaseClient まで登録して、実行時にステートメントをインターセプトおよび変更できます。

db.execute("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
    .bind("name",  …)
    .bind("state",  …)

DatabaseClient は、UnaryOperator<Statement> を受け入れる単純化された filter(…) オーバーロードも公開します。

db.execute("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter(s -> s.returnGeneratedValues("id"))
    .bind("name",  …)
    .bind("state",  …)

db.execute("SELECT id, name, state FROM table")
    .filter(s -> s.fetchSize(25))

StatementFilterFunction は、Statement のフィルタリングと Result オブジェクトのフィルタリングを可能にします。

13.7. Fluent Data Access API

DatabaseClient の SQL API は、あらゆるタイプの SQL を実行するための最大限の柔軟性を提供します。DatabaseClient は、データのクエリ、挿入、更新、削除などの一般的なアドホックユースケースに対して、より狭いインターフェースを提供します。

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

次の簡単なクエリを検討してください。

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

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

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

クエリ結果は、次の 3 つの方法で使用できます。

  • Spring Data の mapping-metadata を使用したオブジェクトマッピング(例: as(Class<T>)

  • 列名が値にマップされる Map<String, Object> として。列名は、大文字と小文字を区別しない方法で検索されます。

  • R2DBC Row および RowMetadata に直接アクセスするためのマッピング BiFunction を提供します。

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

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

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

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

  • rowsUpdated: 影響を受ける行の数を消費します。通常、INSERTUPDATE および DELETE ステートメントで使用されます。

13.7.1. データの選択

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 クエリとともに使用できます。

SELECT 操作のメソッド

select() エントリポイントは、クエリのオプションを提供するいくつかの追加メソッドを公開します。

  • from(Class<T>): マップされたオブジェクトを使用してソーステーブルを指定します。デフォルトでは、結果を T として返します。

  • from(String): ソーステーブル名を指定します。デフォルトでは、結果を Map<String, Object> として返します。

  • as(Class<T>): 結果を T にマップします。

  • map(BiFunction<Row, RowMetadata, T>): 結果を抽出するマッピング関数を提供します。

  • project(String…​ columns): 返す列を指定します。

  • matching(Criteria)WHERE 条件を宣言して、結果をフィルタリングします。

  • orderBy(Order)ORDER BY 句を宣言して結果をソートします。

  • page(Page pageable): 結果内の特定のページを取得します。返される結果のサイズを制限し、オフセットから読み取ります。

  • fetch(): 呼び出し宣言をフェッチステージに移行して、結果消費の多重度を宣言します。

13.7.2. データの挿入

insert() エントリポイントを使用してデータを挿入できます。select() と同様に、insert() は自由形式のマッピングされたオブジェクトの挿入を許可します。

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

Mono<Void> insert = databaseClient.insert()
        .into(Person.class)                       (1)
        .using(new Person(…))                     (2)
        .then();                                  (3)
1into(…) メソッドで Person を使用すると、マッピングメタデータに基づいて INTO テーブルが設定されます。また、挿入する Person オブジェクトを受け入れる insert ステートメントを準備します。
2 スカラー Person オブジェクトを提供します。または、Publisher を指定して、INSERT ステートメントのストリームを実行することもできます。このメソッドは、すべての非 null 値を抽出して挿入します。
3then() を使用して、詳細を消費せずにオブジェクトを挿入します。ステートメントを変更すると、生成されたキーを消費するために、影響を受ける行の数または表形式の結果を消費できます。

次の例に示すように、挿入は型指定のない操作もサポートします。

Mono<Void> insert = databaseClient.insert()
        .into("person")                           (1)
        .value("firstname", "John")               (2)
        .nullValue("lastname")                    (3)
        .then();                                  (4)
1person テーブルへの挿入を開始します。
2firstname に null 以外の値を指定します。
3lastname を null に設定します。
4then() を使用して、詳細を消費せずにオブジェクトを挿入します。ステートメントを変更すると、生成されたキーを消費するために、影響を受ける行の数または表形式の結果を消費できます。
INSERT 操作のメソッド

insert() エントリポイントは、次の追加メソッドを公開して、操作のオプションを提供します。

  • into(Class<T>): マップされたオブジェクトを使用してターゲットテーブルを指定します。デフォルトでは、結果を T として返します。

  • into(String): ターゲット表名を指定します。デフォルトでは、結果を Map<String, Object> として返します。

  • using(T): 挿入するオブジェクトを指定します。

  • using(Publisher<T>): 挿入するオブジェクトのストリームを受け入れます。

  • table(String): ターゲット表名をオーバーライドします。

  • value(String, Object): 挿入する列値を提供します。

  • nullValue(String): 挿入する null 値を提供します。

  • map(BiFunction<Row, RowMetadata, T>): 結果を抽出するマッピング関数を提供します。

  • then(): 結果を消費せずに INSERT を実行します。

  • fetch(): 呼び出し宣言をフェッチステージに移行して、結果消費の多重度を宣言します。

13.7.3. データを更新する

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

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

Person modified =  …

Mono<Void> update = databaseClient.update()
  .table(Person.class)                      (1)
  .using(modified)                          (2)
  .then();                                  (3)
1Person を table(…) メソッドとともに使用すると、マッピングメタデータに基づいて更新するようにテーブルが設定されます。
2 スカラー Person オブジェクト値を提供します。using(…) は変更されたオブジェクトを受け入れ、主キーを派生させ、すべての列の値を更新します。
3then() を使用して、詳細を消費せずにオブジェクトの行を更新します。ステートメントを変更すると、影響を受ける行の数を消費することもできます。

更新では、次の例に示すように、型指定のない操作もサポートしています。

Mono<Void> update = databaseClient.update()
  .table("person")                           (1)
  .using(Update.update("firstname", "Jane")) (2)
  .matching(where("firstname").is("John"))   (3)
  .then();                                   (4)
1person テーブルを更新します。
2 更新する列の Update 定義を提供します。
3 発行されたクエリは、firstname 列で WHERE 条件を宣言して、更新する行をフィルタリングします。
4then() を使用して、詳細を消費せずにオブジェクトの行を更新します。ステートメントを変更すると、影響を受ける行の数を消費することもできます。
UPDATE 操作のメソッド

update() エントリポイントは、次の追加メソッドを公開して、操作のオプションを提供します。

  • table(Class<T>): マップされたオブジェクトを使用して、ターゲットテーブルを指定します。デフォルトで結果を T として返します。

  • table(String): ターゲット表名を指定します。デフォルトでは、結果を Map<String, Object> として返します。

  • using ((T) ` 更新するオブジェクトを指定します。条件自体を導き出します。

  • using(Update): 更新定義を指定します。

  • matching(Criteria)WHERE 条件を宣言して、更新する行を示します。

  • then(): 結果を消費せずに UPDATE を実行します。

  • fetch(): 更新された行の数をフェッチするフェッチステージへの呼び出し宣言を移行します。

13.7.4. データを削除する

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

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

Mono<Void> delete = databaseClient.delete()
  .from(Person.class)                       (1)
  .matching(where("firstname").is("John")   (2)
    .and("lastname").in("Doe", "White"))
  .then();                                  (3)
1from(…) メソッドで Person を使用すると、マッピングメタデータに基づいて FROM テーブルが設定されます。
2 発行されたクエリは、firstname および lastname 列で WHERE 条件を宣言して、削除する行をフィルターします。
3then() を使用して、詳細を消費せずにオブジェクトから行を削除します。ステートメントを変更すると、影響を受ける行の数を消費することもできます。
DELETE 操作のメソッド

delete() エントリポイントは、次の追加メソッドを公開して、操作のオプションを提供します。

  • from(Class<T>): マップされたオブジェクトを使用して、ターゲットテーブルを指定します。デフォルトでは、結果を T として返します。

  • from(String): ターゲット表名を指定します。デフォルトでは、結果を Map<String, Object> として返します。

  • matching(Criteria)WHERE 条件を宣言して、削除する行を定義します。

  • then(): 結果を消費せずに DELETE を実行します。

  • fetch(): 削除された行の数をフェッチするフェッチステージへの呼び出し宣言を移行します。

13.8. トランザクション

リレーショナルデータベースを使用する場合の一般的なパターンは、トランザクションによって保護されている作業単位内で複数のクエリをグループ化することです。リレーショナルデータベースは通常、トランザクションを単一のトランスポート接続に関連付けます。異なる接続を使用すると、異なるトランザクションが使用されます。Spring Data R2DBC には、Spring のトランザクション管理を使用して、同じトランザクション内で複数のステートメントをグループ化できる DatabaseClient のトランザクション認識が含まれています。Spring Data R2DBC は、R2dbcTransactionManager を使用した ReactiveTransactionManager の実装を提供します。

次の例は、トランザクションをプログラムで管理する方法を示しています

例 55: プログラムによるトランザクション管理
ReactiveTransactionManager tm = new R2dbcTransactionManager(connectionFactory);
TransactionalOperator operator = TransactionalOperator.create(tm); (1)

DatabaseClient client = DatabaseClient.create(connectionFactory);

Mono<Void> atomicOperation = client.execute("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
  .bind("id", "joe")
  .bind("name", "Joe")
  .bind("age", 34)
  .fetch().rowsUpdated()
  .then(client.execute("INSERT INTO contacts (id, name) VALUES(:id, :name)")
    .bind("id", "joe")
    .bind("name", "Joe")
    .fetch().rowsUpdated())
  .then()
  .as(operator::transactional); (2)
});
1TransactionalOperator を ReactiveTransactionManager に関連付けます。
2 操作を TransactionalOperator にバインドします。

Spring の宣言的トランザクション管理は、次の例が示すように、トランザクション境界に対する低侵襲のアノテーションベースのアプローチです。

例 56: 宣言的トランザクション管理
@Configuration
@EnableTransactionManagement                                                           (1)
class Config extends AbstractR2dbcConfiguration {

  @Override
  public ConnectionFactory connectionFactory() {
    return // ...
  }

  @Bean
  ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) { (2)
    return new R2dbcTransactionManager(connectionFactory);
  }
}

@Service
class MyService {

  private final DatabaseClient client;

  MyService(DatabaseClient client) {
    this.client = client;
  }

  @Transactional
  public Mono<Void> insertPerson() {

    return client.execute("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
      .bind("id", "joe")
      .bind("name", "Joe")
      .bind("age", 34)
      .fetch().rowsUpdated()
      .then(client.execute("INSERT INTO contacts (id, name) VALUES(:id, :name)")
        .bind("id", "joe")
        .bind("name", "Joe")
        .fetch().rowsUpdated())
      .then();
  }
}
1 宣言的なトランザクション管理を有効にします。
2 リアクティブトランザクション機能をサポートする ReactiveTransactionManager 実装を提供します。

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: 個人エンティティへのページングアクセス
@RunWith(SpringRunner.class)
@ContextConfiguration
public class PersonRepositoryTests {

  @Autowired PersonRepository repository;

  @Test
  public void readsAllEntitiesCorrectly() {

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

  @Test
  public 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 パラメーターバインディングと同等です)。

public interface PersonRepository extends ReactiveCrudRepository<Person, String> {

  @Query("SELECT * FROM person WHERE lastname = :#{[0]}")
  List<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 列の自動インクリメント列がある場合、生成された値は、データベースに挿入された後にエンティティに設定されます。

重要な制約の 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 のままです。
3version = 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 表現を聞かせてそれ以前

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

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

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

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

例 72: 射影 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() メソッドを自動的に実装するコンストラクターを公開します。

動的射影

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

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

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

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

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

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

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

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

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

R2DBC リポジトリは、リポジトリを実装するために DatabaseClient および ReactiveDataAccessStrategy または 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) {

        DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(MySqlDialect.INSTANCE);
        DatabaseClient databaseClient = DatabaseClient.builder()
                .connectionFactory(connectionFactory)
                .dataAccessStrategy(strategy)
                .build();

        return new R2dbcEntityTemplate(databaseClient, strategy);
    }
}

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

15. データベース接続の制御

このセクションでは以下について説明します。

15.1. ConnectionFactory を使用する

Spring は、ConnectionFactory を介してデータベースへの R2DBC 接続を取得します。ConnectionFactory は R2DBC 仕様の一部であり、一般化された接続ファクトリです。コンテナーまたはフレームワークは、接続プールとトランザクション管理の課題をアプリケーションコードから隠すことができます。開発者として、データベースへの接続方法に関する詳細を知る必要はありません。それは、ConnectionFactory をセットアップする管理者の責任です。ほとんどの場合、コードを開発およびテストするときに両方のロールを果たしますが、本番データソースがどのように構成されているかを必ずしも知る必要はありません。

Spring の R2DBC レイヤーを使用する場合、サードパーティが提供する接続プール実装を使用して独自に構成できます。一般的な実装は R2DBC Pool です。Spring ディストリビューションの実装は、テストのみを目的としており、プールを提供していません。

ConnectionFactory を構成するには:

  1. 通常は R2DBC ConnectionFactory を取得するため、ConnectionFactory との接続を取得します。

  2. R2DBC URL を提供します。(正しい値については、ドライバーのドキュメントを参照してください。)

次の例は、Java で ConnectionFactory を構成する方法を示しています。

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

15.2. ConnectionFactoryUtils を使用する

ConnectionFactoryUtils クラスは、ConnectionFactory から接続を取得して接続を閉じる(必要な場合) static メソッドを提供する便利で強力なヘルパークラスです。ConnectionFactoryTransactionManager などのサブスクライバー Context -bound 接続をサポートします。

15.3. SmartConnectionFactory の実装

SmartConnectionFactory インターフェースは、リレーショナルデータベースへの接続を提供できるクラスによって実装する必要があります。ConnectionFactory インターフェースを継承し、それを使用するクラスが、特定の操作後に接続を閉じる必要があるかどうかを照会できるようにします。この使用方法は、接続を再利用する必要があることがわかっている場合に効率的です。

15.4. TransactionAwareConnectionFactoryProxy を使用する

TransactionAwareConnectionFactoryProxy は、ターゲット ConnectionFactory のプロキシです。プロキシは、ConnectionFactory をターゲットとしてラップし、Spring が管理するトランザクションの認識を追加します。

15.5. ConnectionFactoryTransactionManager を使用する

ConnectionFactoryTransactionManager クラスは、単一の R2DBC データソース用の ReactiveTransactionManager 実装です。指定されたデータソースからサブスクライバ Context に R2DBC 接続をバインドし、データソースごとに 1 つのサブスクライバ接続を可能にします。

R2DBC の標準 ConnectionFactory.create() ではなく、ConnectionFactoryUtils.getConnection(ConnectionFactory) を介して R2DBC 接続を取得するには、アプリケーションコードが必要です。すべてのフレームワーククラス(DatabaseClient など)は、この戦略を暗黙的に使用します。このトランザクションマネージャーで使用しない場合、ルックアップ戦略は一般的なものとまったく同じように動作します。どのような場合でも使用できます。

ConnectionFactoryTransactionManager クラスは、接続に適用されるカスタム分離レベルをサポートします。

16. ConnectionFactory の初期化

org.springframework.data.r2dbc.connectionfactory.init パッケージは、既存の ConnectionFactory の初期化をサポートします。サーバーまたは組み込みデータベースで実行されるインスタンスを初期化する必要がある場合があります。

16.1. @Bean メソッドを使用したデータベースの初期化

データベースを初期化したい場合、ConnectionFactory Bean への参照を提供できる場合

例 75: ConnectionFactoryInitializer を使用して ConnectionFactory を初期化する
@Configuration
public class InitializerConfiguration {

	@Bean
	public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {

		ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
		initializer.setConnectionFactory(connectionFactory);

		CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
		populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("com/foo/sql/db-schema.sql")));
		populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("com/foo/sql/test-data1.sql")));
		initializer.setDatabasePopulator(populator);

		return initializer;
	}
}

上記の例は、データベースに対して指定された 2 つのスクリプトを実行します。最初のスクリプトはスキーマを作成し、2 番目のスクリプトはテーブルにテストデータセットを取り込みます。

データベース初期化子のデフォルトの動作は、提供されたスクリプトを無条件に実行することです。これは、必ずしも希望するものとは限りません。たとえば、既にテストデータが含まれているデータベースに対してスクリプトを実行する場合です。データを誤って削除する可能性は、最初にテーブルを作成してからデータを挿入するという一般的なパターン(前述)に従うことで低減されます。テーブルが既に存在する場合、最初のステップは失敗します。

ただし、既存のデータの作成と削除をさらに制御するために、ConnectionFactoryInitializer と ResourceDatabasePopulator は、初期化のオン / オフの切り替えなど、さまざまなスイッチをサポートしています。

; 文字がスクリプトにまったく存在しない場合は、各ステートメントを ; または改行で区切る必要があります。次の例に示すように、グローバルに制御することも、スクリプトごとにスクリプトを制御することもできます。

例 76: ステートメント区切り文字のカスタマイズ
@Configuration
public class InitializerConfiguration {

	@Bean
	public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {

		ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();

		initializer.setConnectionFactory(connectionFactory);

		ResourceDatabasePopulator populator = new ResourceDatabasePopulator(new ClassPathResource("com/foo/sql/db-schema.sql"));
		populator.setSeparator("@@");                                                (1)
		initializer.setDatabasePopulator(populator);

		return initializer;
	}
}
1 区切りスクリプトを @@ に設定します。

この例では、スキーマスクリプトはステートメント区切り文字として @@ を使用します。

16.1.1. データベースに依存する他のコンポーネントの初期化

大規模なクラスのアプリケーション(Spring コンテキストが開始されるまでデータベースを使用しないアプリケーション)は、データベースイニシャライザーをさらに複雑にすることなく使用できます。アプリケーションがそれらのいずれでもない場合、このセクションの残りを読む必要があるかもしれません。

データベース初期化子は ConnectionFactory インスタンスに依存し、その初期化コールバックで提供されるスクリプトを実行します(XML Bean 定義の init-method、コンポーネントの @PostConstruct メソッド、または InitializingBean を実装するコンポーネントの afterPropertiesSet() メソッドに類似)。他の Bean が同じデータソースに依存し、初期化コールバックでデータソースを使用する場合、データがまだ初期化されていないために問題が発生する可能性があります。これの一般的な例は、熱心に初期化され、アプリケーションの起動時にデータベースからデータをロードするキャッシュです。

この課題を回避するには、2 つのオプションがあります。

  1. キャッシュ初期化戦略を後のフェーズに変更するか

  2. データベース初期化子が最初に初期化されていることを確認してください

キャッシュの初期化戦略を変更するのは、アプリケーションが制御下にある場合で、そうでない場合は簡単です。これを実装する方法についてのいくつかの提案は次のとおりです。

  • 最初の使用時にキャッシュを遅延初期化して、アプリケーションの起動時間を改善します。

  • キャッシュまたはキャッシュを初期化する別のコンポーネントに Lifecycle または SmartLifecycle を実装させます。アプリケーションコンテキストが起動すると、autoStartup フラグを設定して SmartLifecycle を自動的に起動できます。また、囲んでいるコンテキストで ConfigurableApplicationContext.start() を呼び出すことにより、ライフサイクルを手動で起動できます。

  • Spring ApplicationEvent または同様のカスタムオブザーバーメカニズムを使用して、キャッシュの初期化をトリガーします。ContextRefreshedEvent は(すべての Bean が初期化された後)使用準備ができたときに常にコンテキストによって公開されるため、多くの場合、これは便利なフックです(これが SmartLifecycle のデフォルトの動作です)。

データベース初期化子が最初に初期化されるようにすることも簡単です。これを実装する方法に関するいくつかの提案は次のとおりです。

  • Spring BeanFactory のデフォルトの動作に依存します。つまり、Bean は登録順に初期化されます。アプリケーションモジュールを並べ替える一連の @Import 構成の一般的な慣行を採用し、データベースとデータベースの初期化が最初にリストされるようにすることで、簡単に調整できます。

  • ConnectionFactory とそれを使用するビジネスコンポーネントを分離し、別々の ApplicationContext インスタンスに配置して起動順序を制御します(たとえば、親コンテキストには ConnectionFactory が含まれ、子コンテキストにはビジネスコンポーネントが含まれます)。

17. マッピング

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

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

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

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

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

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

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

17.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 はリフレクションを介してエンティティのインスタンス化にフォールバックします。

17.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;
  }
}
例 77: 生成されたプロパティアクセサー
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 は生成されたプロパティアクセサーを使用しようとし、制限が検出された場合はリフレクションベースのものにフォールバックします。

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

例 78: サンプルエンティティ
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 によるコンストラクターの明確化の必要性を回避することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。

17.1.3. 一般的な推奨事項

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

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

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

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

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

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

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

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

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

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

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

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

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

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

17.3. マッピング設定

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

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

例 79: 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 という名前でコンテナーに登録します。

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

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

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

@Table
public class Person {

  @Id
  private Long id;

  private Integer ssn;

  private String firstName;

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

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

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

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

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

パススルー

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

JSR-310 日付 / 時刻型

パススルー

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

StringBigIntegerBigDecimal および UUID

パススルー

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

Blob および Clob

パススルー

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

byte[]ByteBuffer

パススルー

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

Collection<T>

T の配列

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

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

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

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

複雑なオブジェクト

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

class OrderItem {

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

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

  // getters/setters ommitted
}

17.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;
  }
}

18. Kotlin サポート

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

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

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

18.1. 要件

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

18.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 可能性はまだサポートされていませんが、今後のリリースでサポートされる予定です。

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

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

18.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コルーチン拡張。

18.5. コルーチン

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

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

18.5.1. 依存関係

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

例 81: 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 以上。

18.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 とのリアクティブ化 (英語) に関するこのブログ投稿を参照してください。

18.5.3. リポジトリ

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

interface CoroutineRepository : CoroutineCrudRepository<User, String> {

    suspend fun findOne(id: String): User

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

コルーチンリポジトリはリアクティブリポジトリ上に構築され、Kotlin のコルーチンを介したデータアクセスのノンブロッキング性を公開します。Coroutines リポジトリのメソッドは、クエリメソッドまたはカスタム実装のいずれかによってバックアップできます。カスタム実装メソッドが suspend`able without requiring the implementation method to return a reactive type such as `Mono または Flux の場合、カスタム実装メソッドを呼び出すと、実際の実装メソッドにコルーチン呼び出しが伝播されます。

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

付録

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

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

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

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

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 など)は、地理空間クエリをサポートするデータストアでのみ使用できます。
表 5: クエリの戻り型
戻りの型 説明

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 を返すクエリは、無限の数の要素も放出できます。