© 2018-2020 The original authors.

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

序文

Spring Data JDBC プロジェクトは、Spring のコアコンセプトを、ドメイン主導の設計原則に合わせた JDBC データベースを使用するソリューションの開発に適用します。集約を格納および照会するための高レベルの抽象化として「テンプレート」を提供します。

このドキュメントは、Spring Data JDBC サポートのリファレンスガイドです。概念とセマンティクスと構文について説明します。

このセクションでは、いくつかの基本的な導入を行います。このドキュメントの残りの部分では、Spring Data JDBC 機能についてのみ言及し、ユーザーが SQL および Spring の概念に精通していることを前提としています。

1. Spring の学習

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

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

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

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

2. 要件

Spring Data JDBC バイナリには、JDK レベル 8.0 以降および Spring Framework 5.2.9.RELEASE 以降が必要です。

データベースに関しては、Spring Data JDBC には、ベンダー固有のフレーバーよりも一般的な SQL 機能を抽象化するためのダイアレクトが必要です。Spring Data JDBC は、以下のデータベースを直接サポートしています。

  • DB2

  • H2

  • HSQLDB

  • MariaDB

  • Microsoft SQL Server

  • MySQL

  • Postgres

別のデータベースを使用すると、アプリケーションは起動しません。ダイアレクトのセクションには、そのような場合の対処方法の詳細が含まれています。

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

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

コミュニティフォーラム

スタックオーバーフロー (英語) 上の Spring Data は、すべての Spring Data(ドキュメントだけではない)ユーザーが情報を共有し、互いに助け合うためのタグです。登録は投稿にのみ必要です。

専門サポート

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

4. 開発のフォロー

Spring Data JDBC ソースコードリポジトリ、ナイトリービルド、およびスナップショットアーティファクトについては、Spring Data JDBC ホームページを参照してください。Spring Data は、スタックオーバーフロー (英語) のコミュニティを通じて開発者と対話することにより、Spring コミュニティのニーズに最適に対応できます。バグが発生した場合、または改善を提案したい場合は、Spring Data 課題トラッカー (英語) でチケットを作成してください。Spring エコシステムの最新ニュースやお知らせを最新の状態に保つには、Spring コミュニティポータルに登録してください。Spring ブログ (英語) や Twitter のプロジェクトチーム(SpringData (英語) )をフォローすることもできます。

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

6. 注目の新機能

このセクションでは、各バージョンの重要な変更について説明します。

6.1. Spring Data JDBC 2.0 の新機能

  • 楽観的ロックのサポート。

  • PagingAndSortingRepository のサポート。

  • クエリ導出

  • H2 の完全サポート。

  • すべての SQL 識別子はデフォルトで引用されます。

  • 列が欠落しても例外が発生しなくなりました。

6.2. Spring Data JDBC 1.1 の新機能

  • @Embedded エンティティのサポート。

  • byte[] を BINARY として保存します。

  • JdbcAggregateTemplate の専用 insert メソッド。

  • 読み取り専用のプロパティのサポート。

6.3. Spring Data JDBC 1.0 の新機能

  • CrudRepository の基本的なサポート。

  • @Query サポート。

  • MyBatis サポート。

  • ID 生成。

  • イベントのサポート。

  • 監査。

  • CustomConversions.

7. 依存関係

個々の 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>

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

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

7.2. Spring Framework

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

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

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

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

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

8.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);
}

8.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");
      }
    }

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

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

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

8.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 が実行時にインスタンスを作成してはならないすべてのリポジトリインターフェースに、そのアノテーションを必ず追加してください。

8.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 {  …  }

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

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

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

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

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

8.4.1. クエリ検索戦略

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

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

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

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

8.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)を提供することにより、静的な順序を適用できます。動的な並べ替えをサポートするクエリメソッドを作成するには、「特別なパラメーター処理」を参照してください。

8.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 命名規則に従うことを強くお勧めします(つまり、プロパティ名にアンダースコアを使用せず、代わりにキャメルケースを使用します)。

8.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()));

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

8.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 などになります。

8.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 を返します。

8.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> をサポートしているわけではありません。

8.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 を使用します。

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

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

8.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 で終わるすべてのインターフェースをインスタンス化から除外します。

8.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 の定義にも当てはまります。ストア固有の構成について説明しているセクションを参照してください。

8.5.3. スタンドアロン使用

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

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

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

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

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

8.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>

8.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" />

8.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 つが呼び出されるたびに呼び出されます。

8.8. Spring Data 拡張

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

8.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);

8.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 解決から除外します。

8.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>

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

9. JDBC リポジトリ

この章では、JDBC のリポジトリサポートの特殊性について説明します。これは、Spring Data リポジトリの操作で説明されているコアリポジトリサポートに基づいています。そこで説明されている基本的な概念をしっかりと理解している必要があります。

9.1. なぜ Spring Data JDBC なのでしょうか?

Java の世界におけるリレーショナルデータベースの主な永続化 API は、確かに JPA であり、独自の Spring Data モジュールを持っています。なぜ別のものがあるのでしょうか?

JPA は、開発者を支援するために多くのことを行います。とりわけ、エンティティへの変更を追跡します。遅延読み込みを行います。さまざまなオブジェクト構造を、同等の幅広いデータベース設計にマッピングできます。

これはすばらしいことで、多くのことが非常に簡単になります。基本的な JPA チュートリアルを参照してください。しかし、JPA が特定のことを行う理由については、しばしば混乱を招きます。また、JPA では概念的に非常に単純なことがかなり難しくなります。

Spring Data JDBC は、以下の設計上の決定を受け入れることにより、概念的にはるかにシンプルになることを目指しています。

  • エンティティをロードすると、SQL ステートメントが実行されます。これが完了すると、完全に読み込まれたエンティティができます。遅延読み込みやキャッシュは行われません。

  • エンティティを保存すると、保存されます。そうしない場合、そうではありません。ダーティトラッキングやセッションはありません。

  • エンティティをテーブルにマッピングする方法の簡単なモデルがあります。おそらく、かなり単純な場合にのみ機能します。気に入らない場合は、独自の戦略をコーディングする必要があります。Spring Data JDBC は、アノテーションを使用して戦略をカスタマイズするための非常に限られたサポートのみを提供します。

9.2. ドメイン駆動設計およびリレーショナルデータベース。

すべての Spring Data モジュールは、ドメインドリブンデザインの「リポジトリ」、「集約」、および「集約ルート」の概念に触発されています。これらは、Spring Data JDBC にとってさらに重要な可能性があります。これは、リレーショナルデータベースを操作する際の通常の慣行にある程度反するからです。

集約は、アトミックな変更間で一貫性が保証されるエンティティのグループです。古典的な例は、Order と OrderItems です。Order のプロパティ(たとえば、numberOfItems は OrderItems の実際の数と一貫性があります)は、変更が加えられても一貫性を保ちます。

集合体全体の参照は常に一貫しているとは限りません。最終的に一貫性が保証されます。

各集約には、集約のエンティティの 1 つである集約ルートが 1 つだけあります。集約は、その集約ルートのメソッドを介してのみ操作されます。これらは、前述のアトミックな変更です。

リポジトリは、特定のタイプのすべての集約のコレクションのように見える永続ストアの抽象化です。一般的に、Spring Data の場合、これは集約ルートごとに 1 つの Repository が必要であることを意味します。さらに、Spring Data JDBC の場合、これは、集約ルートから到達可能なすべてのエンティティがその集約ルートの一部と見なされることを意味します。Spring Data JDBC は、集約のみが集約の非ルートエンティティを格納するテーブルへの外部キーを持ち、他のエンティティが非ルートエンティティを指すことはないと想定しています。

現在の実装では、集約ルートから参照されるエンティティは削除され、Spring Data JDBC によって再作成されます。

データベースの作業および設計のスタイルに一致する実装で、リポジトリメソッドを上書きできます。

9.3. 入門

作業環境をブートストラップ設定する簡単な方法は、Eclipse Pleiades All in One (STS, Lombok 付属) または STS (英語) または Spring Initializr (英語) から Spring ベースのプロジェクトを作成することです。

まず、実行中のデータベースサーバーをセットアップする必要があります。JDBC アクセス用にデータベースを構成する方法については、ベンダーのドキュメントを参照してください。

STS で Spring プロジェクトを作成するには:

  1. ファイル→新規→ Spring テンプレートプロジェクト→シンプル Spring ユーティリティプロジェクトに移動し、プロンプトが表示されたらはいを押します。次に、プロジェクトとパッケージ名(org.spring.jdbc.example など)を入力します。

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

    <dependencies>
    
      <!-- other dependency elements omitted -->
    
      <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jdbc</artifactId>
        <version>2.0.4.RELEASE</version>
      </dependency>
    
    </dependencies>
  3. pom.xml の Spring のバージョンを次のように変更する

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

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

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

9.4. サンプルリポジトリ

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

9.5. アノテーションベースの構成

次の例に示すように、Spring Data JDBC リポジトリのサポートは、Java 構成を介したアノテーションによってアクティブにできます。

例 54: Java 構成を使用した Spring Data JDBC リポジトリ
@Configuration
@EnableJdbcRepositories                                                                (1)
class ApplicationConfig extends AbstractJdbcConfiguration {                            (2)

    @Bean
    public DataSource dataSource() {                                                   (3)

        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.HSQL).build();
    }

    @Bean
    NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource) { (4)
        return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean
    TransactionManager transactionManager(DataSource dataSource) {                     (5)
        return new DataSourceTransactionManager(dataSource);
    }
}
1@EnableJdbcRepositories は、Repository から派生したインターフェースの実装を作成する
2AbstractJdbcConfiguration は、Spring Data JDBC に必要なさまざまなデフォルト Bean を提供する
3 データベースに接続する DataSource を作成します。これは、次の 2 つの Bean メソッドで必要です。
4Spring Data JDBC がデータベースにアクセスするために使用する NamedParameterJdbcOperations を作成します。
5Spring Data JDBC は、Spring JDBC によって提供されるトランザクション管理を利用します。

前の例の構成クラスは、spring-jdbc の EmbeddedDatabaseBuilder API を使用して、組み込み HSQL データベースをセットアップします。次に、DataSource を使用して NamedParameterJdbcOperations と TransactionManager をセットアップします。@EnableJdbcRepositories を使用して、最終的に Spring Data JDBC リポジトリをアクティブ化します。基本パッケージが構成されていない場合は、構成クラスが存在するパッケージを使用します。AbstractJdbcConfiguration を拡張すると、さまざまな Bean が確実に登録されます。メソッドを上書きすると、セットアップをカスタマイズできます(以下を参照)。

この構成は、Spring Boot を使用してさらに簡略化できます。Spring Boot では、スターター spring-boot-starter-data-jdbc が依存関係に含まれていれば、DataSource で十分です。他のすべては Spring Boot によって行われます。

このセットアップでカスタマイズしたいことがいくつかあります。

9.5.1. ダイアレクト

Spring Data JDBC は、インターフェース Dialect の実装を使用して、データベースまたはその JDBC ドライバーに固有の動作をカプセル化します。デフォルトでは、AbstractJdbcConfiguration は、使用中のデータベースを正しい Dialect レジスタに特定しようとします。この動作は、jdbcDialect(NamedParameterJdbcOperations) を上書きすることで変更できます。

ダイアレクトがないデータベースを使用している場合、アプリケーションは起動しません。その場合、Dialect 実装を提供するようベンダーに依頼する必要があります。または、次のこともできます。

  1. 独自の Dialect を実装します。

  2. Dialect を返す JdbcDialectProvider を実装します。

  3. META-INF に spring.factories リソースを作成してプロバイダーを登録し、行を追加して登録を実行します
    org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=<fully qualified name of your JdbcDialectProvider>

9.6. 永続化エンティティ

CrudRepository.save(…) メソッドを使用して、集計を保存できます。集約が新しい場合、集約ルートの挿入が発生し、その後に直接または間接的に参照されるすべてのエンティティの挿入ステートメントが続きます。

集約ルートが新規ではない場合、すべての参照エンティティが削除され、集約ルートが更新され、すべての参照エンティティが再度挿入されます。インスタンスが新しいかどうかは、インスタンスの状態の一部であることに注意してください。

このアプローチには、明らかな欠点がいくつかあります。参照されたエンティティのうち実際に変更されたものがわずかしかない場合、削除と挿入は無駄です。このプロセスは改善される可能性があり、おそらく改善される予定ですが、Spring Data JDBC が提供できるものには特定の制限があります。集約の以前の状態はわかりません。そのため、更新プロセスは常にデータベースで見つかったものをすべて取得し、save メソッドに渡されたエンティティの状態に変換する必要があります。

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

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

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

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

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

オブジェクト作成

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

プロパティ設定

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

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

例 56: サンプルエンティティ
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 によるコンストラクターの明確化の必要性を回避することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。
一般的な推奨事項
  • 不変オブジェクトに固執しようとする — 不変オブジェクトは、オブジェクトを具体化するのはコンストラクターのみを呼び出すだけなので、簡単に作成できます。また、これにより、クライアントオブジェクトがオブジェクトの状態を操作できるようにする setter メソッドがドメインオブジェクトに散らばるのを防ぎます。それらが必要な場合は、同じ場所に配置された限られたタイプでのみ呼び出せるように、パッケージを保護することをお勧めします。コンストラクターのみの実体化は、プロパティの設定よりも最大 30% 高速です。

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

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

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

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

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

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

9.6.2. エンティティでサポートされているタイプ

現在、次のタイプのプロパティがサポートされています。

  • すべてのプリミティブ型とそれらのボックス化された型 (intfloatIntegerFloat など)

  • 列挙型は名前にマップされます。

  • String

  • java.util.Datejava.time.LocalDatejava.time.LocalDateTime および java.time.LocalTime

  • データベースでサポートされている場合、上記のタイプの配列とコレクションは、配列タイプの列にマップできます。

  • データベースドライバーが受け入れるもの。

  • 他のエンティティへの参照。これらは、1 対 1 の関連、または埋め込み型と見なされます。1 対 1 の関連エンティティが id 属性を持つことはオプションです。参照されたエンティティのテーブルには、参照しているエンティティのテーブルと同じ名前の追加の列があると予想されます。NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path) を実装することにより、この名前を変更できます。埋め込みエンティティには id は必要ありません。存在する場合は無視されます。

  • Set<some entity> は 1 対多の関連と見なされます。参照されたエンティティのテーブルには、参照しているエンティティのテーブルと同じ名前の追加の列があると予想されます。NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path) を実装することにより、この名前を変更できます。

  • Map<simple type, some entity> は、修飾された 1 対多の関連と見なされます。参照されるエンティティのテーブルには、2 つの追加の列が必要です。1 つは外部キーの参照エンティティのテーブルと同じ名前で、もう 1 つはマップキーの同じ名前と追加の _key サフィックスです。NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path) および NamingStrategy.getKeyColumn(RelationalPersistentProperty property) をそれぞれ実装することにより、この動作を変更できます。あるいは、@MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name") を使用して属性にアノテーションを付けることもできます

  • List<some entity> は Map<Integer, some entity> としてマップされます。

参照エンティティの処理は制限されています。これは、上記の集約ルートの考え方に基づいています。別のエンティティを参照する場合、そのエンティティは定義上、集計の一部です。そのため、参照を削除すると、以前に参照されたエンティティが削除されます。これは、参照が 1-1 または 1-n であるが、n-1 または n-m ではないことも意味します。

n-1 または n-m の参照がある場合、定義により、2 つの別個の集計を処理します。それらの間の参照は単純な id 値としてエンコードする必要があり、Spring Data JDBC と適切にマッピングする必要があります。

9.6.3. カスタムコンバーター

AbstractJdbcConfiguration から構成を継承し、メソッド jdbcCustomConversions() を上書きすることにより、デフォルトでサポートされていないタイプのカスタムコンバーターを登録できます。

@Configuration
public class DataJdbcConfiguration extends AbstractJdbcConfiguration {

    @Override
    public JdbcCustomConversions jdbcCustomConversions() {

      return new JdbcCustomConversions(Collections.singletonList(TimestampTzToDateConverter.INSTANCE));

    }

    @ReadingConverter
    enum TimestampTzToDateConverter implements Converter<TIMESTAMPTZ, Date> {

        INSTANCE;

        @Override
        public Date convert(TIMESTAMPTZ source) {
            //...
        }
    }
}

JdbcCustomConversions のコンストラクターは、org.springframework.core.convert.converter.Converter のリストを受け入れます。

コンバーターには、データベースへの読み取りまたはデータベースへの書き込みのみへの適用を制御するために、@ReadingConverter または @WritingConverter のアノテーションを付ける必要があります。

例の TIMESTAMPTZ は、ドメインモデルにより適したものに変換する必要があるデータベース固有のデータ型です。

JdbcValue

値変換では、JdbcValue を使用して、java.sql.Types タイプの JDBC 操作に伝搬される値を強化します。型の派生を使用する代わりに JDBC 固有の型を指定する必要がある場合は、カスタム書き込みコンバーターを登録します。このコンバーターは、値を実際の JDBCType のフィールドを持つ JdbcValue に変換する必要があります。

9.6.4. NamingStrategy

Spring Data JDBC が提供する CrudRepository の標準実装を使用する場合、特定のテーブル構造が期待されます。アプリケーションコンテキストで NamingStrategy(Javadoc) を提供することで、これを調整できます。

9.6.5. Custom table names

NamingStrategy がデータベーステーブル名と一致しない場合、@Table(Javadoc) アノテーションを使用して名前をカスタマイズできます。このアノテーションの要素 value は、カスタムテーブル名を提供します。次の例では、MyEntity クラスをデータベースの CUSTOM_TABLE_NAME テーブルにマップします。

@Table("CUSTOM_TABLE_NAME")
public class MyEntity {
    @Id
    Integer id;

    String name;
}

9.6.6. Custom column names

NamingStrategy がデータベースの列名と一致しない場合、@Column(Javadoc) アノテーションを使用して名前をカスタマイズできます。このアノテーションの要素 value は、カスタム列名を提供します。次の例は、MyEntity クラスの name プロパティをデータベースの CUSTOM_COLUMN_NAME 列にマップします。

public class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

@MappedCollection(Javadoc) アノテーションは、参照タイプ(1 対 1 の関連)またはセット、リスト、マップ(1 対多の関連)で使用できます。アノテーションの idColumn 要素は、他のテーブルの id 列を参照する外部キー列のカスタム名を提供します。次の例では、MySubEntity クラスに対応するテーブルに NAME 列と、MyEntity ID の CUSTOM_MY_ENTITY_ID_COLUMN_NAME 列が関連の理由で含まれています。

public class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_MY_ENTITY_ID_COLUMN_NAME")
    Set<MySubEntity> subEntities;
}

public class MySubEntity {
    String name;
}

List および Map を使用する場合、List のデータセットの位置または Map のエンティティのキー値の追加列が必要です。この追加の列名は、@MappedCollection(Javadoc) アノテーションの keyColumn 要素を使用してカスタマイズできます。

public class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
    List<MySubEntity> name;
}

public class MySubEntity {
    String name;
}

9.6.7. 埋め込みエンティティ

埋め込みエンティティは、データベースにテーブルが 1 つしかない場合でも、java データモデルに値オブジェクトを保持するために使用されます。次の例では、MyEntity が @Embedded アノテーションでマップされています。この結果、データベースには、id および name (EmbeddedEntity クラスの)の 2 つの列を持つテーブル my_entity が期待されます。

ただし、結果セット内で name 列が実際に null である場合、@Embedded の onEmpty に従って、プロパティ embeddedEntity 全体が null に設定されます。ネストされたすべてのプロパティが null である場合、null はオブジェクトになります。
この動作とは反対に、USE_EMPTY は、デフォルトコンストラクターまたは結果セットから NULL 可能パラメーター値を受け入れるコンストラクターを使用して、新しいインスタンスを作成しようとします。

例 57: 埋め込みオブジェクトのサンプルコード
public class MyEntity {

    @Id
    Integer id;

    @Embedded(onEmpty = USE_NULL) (1)
    EmbeddedEntity embeddedEntity;
}

public class EmbeddedEntity {
    String name;
}
1null の name の場合、NullembeddedEntityUSE_EMPTY を使用して、name プロパティの潜在的な null 値で embeddedEntity をインスタンス化します。

エンティティで複数回値オブジェクトが必要な場合は、@Embedded アノテーションのオプションの prefix 要素を使用してこれを実現できます。この要素はプレフィックスを表し、埋め込みオブジェクトの各列名の先頭に追加されます。

@Embedded(onEmpty = USE_NULL) および @Embedded(onEmpty = USE_EMPTY) のショートカット @Embedded.Nullable および @Embedded.Empty を使用して、冗長性を減らし、同時に JSR-305 @javax.annotation.Nonnull を適切に設定します。

public class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1@Embedded(onEmpty = USE_NULL) のショートカット。

Collection または Map を含む埋め込みエンティティは、少なくとも空のコレクションまたはマップを含むため、常に空ではないと見なされます。そのようなエンティティは、@Embedded(onEmpty = USE_NULL)を使用している場合でも null になることはありません。

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

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

テーブル 2: Spring Data JDBC でエンティティが新しいかどうかを検出するためのオプション

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

デフォルトでは、Spring Data JDBC は指定されたエンティティの識別子プロパティをインスペクションします。識別子プロパティが null の場合、エンティティは新しいと見なされます。それ以外の場合は、新規ではないと見なされます。

Persistable の実装

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

EntityInformation の実装

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

9.6.9. ID 生成

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

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

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

9.6.10. 楽観的ロック

Spring Data JDBC は、集約ルートで @Version(Javadoc) のアノテーションが付けられた数値属性による楽観的ロックをサポートします。Spring Data JDBC がそのようなバージョン属性を持つ集約を保存するたびに、次の 2 つのことが起こります。集約ルートの更新ステートメントには、データベースに格納されているバージョンが実際に変更されていないことをチェックする where 句が含まれます。そうでない場合は、OptimisticLockingFailureException がスローされます。また、バージョン属性はエンティティとデータベースの両方で増加するため、同時アクションは変更を認識し、上記のように該当する場合は OptimisticLockingFailureException をスローします。

このプロセスは、新しい集合体の挿入にも適用されます。null または 0 バージョンは新しいインスタンスを示し、その後、増加したインスタンスはインスタンスを新規ではないものとしてマークします。UUID が使用されます。

削除中にバージョンチェックも適用されますが、バージョンは増加しません。

9.7. クエリメソッド

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

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

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

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

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

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

  Person findFirstByLastname(String lastname);                                      (4)

  @Query("SELECT * FROM person WHERE lastname = :lastname")
  List<Person> findByLastname(String lastname);                                     (5)
}
1 このメソッドは、指定された lastname を持つすべての人々のクエリを示します。クエリは、And および Or と連結できる制約のメソッド名を解析することによって導出されます。メソッド名は SELECT … FROM person WHERE firstname = :firstname のクエリ式になります。
2Pageable を使用して、オフセットと並べ替えのパラメーターをデータベースに渡します。
3 指定された条件で単一のエンティティを検索します。一意でない結果の場合は、IncorrectResultSizeDataAccessException で完了します。
4<3> とは対照的に、クエリがより多くの結果ドキュメントを生成した場合でも、最初のエンティティは常に出力されます。
5findByLastname メソッドは、指定された姓を持つすべての人々のクエリを表示します。

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

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

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

findByAgeBetween(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

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

9.7.1. クエリ検索戦略

JDBC モジュールは、@Query アノテーションの文字列またはプロパティファイルの名前付きクエリとして、クエリを手動で定義することをサポートしています。メソッドの名前からクエリを派生させることは現在サポートされていません。

9.7.2. @Query を使用する

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

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

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

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

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

9.7.3. 名前付きクエリ

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

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

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

カスタム RowMapper

@Query(rowMapperClass = …​.) を使用するか、メソッド戻り型ごとに RowMapperMap Bean を登録して RowMapper を登録することにより、使用する RowMapper を構成できます。次の例は、DefaultQueryMappingConfiguration を登録する方法を示しています。

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

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

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

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

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

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

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

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

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

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

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

  • void

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

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

9.8. MyBatis 統合

CRUD 操作とクエリメソッドは MyBatis に委譲できます。このセクションでは、MyBatis と統合するように Spring Data JDBC を構成する方法、およびクエリの実行とライブラリへのマッピングを引き継ぐために従うべき規則について説明します。

9.8.1. 構成

MyBatis を Spring Data JDBC に適切にプラグインする最も簡単な方法は、MyBatisJdbcConfiguration をアプリケーション構成にインポートすることです。

@Configuration
@EnableJdbcRepositories
@Import(MyBatisJdbcConfiguration.class)
class Application {

  @Bean
  SqlSessionFactoryBean sqlSessionFactoryBean() {
    // Configure MyBatis here
  }
}

ご覧のとおり、MyBatisJdbcConfiguration は ApplicationContext で最終的に利用可能になるために SqlSession Bean に依存しているため、宣言する必要があるのは SqlSessionFactoryBean だけです。

9.8.2. 使用規則

CrudRepository の各操作に対して、Spring Data JDBC は複数のステートメントを実行します。アプリケーションコンテキストに SqlSessionFactory (GitHub) がある場合、Spring Data は、各ステップで、SessionFactory がステートメントを提供するかどうかをチェックします。見つかった場合は、そのステートメント(構成されたエンティティへのマッピングを含む)が使用されます。

ステートメントの名前は、エンティティタイプの完全修飾名と Mapper. およびステートメントの種類を決定する String を連結することにより構築されます。例: org.example.User のインスタンスが挿入される場合、Spring Data JDBC は org.example.UserMapper.insert という名前のステートメントを探します。

ステートメントが実行されると、[MyBatisContext] のインスタンスが引数として渡され、さまざまな引数がステートメントで使用可能になります。

次の表は、使用可能な MyBatis ステートメントについて説明しています。

名前 目的 このステートメントをトリガーする可能性のある CrudRepository メソッド MyBatisContext で利用可能な属性

insert

単一のエンティティを挿入します。これは、集約ルートによって参照されるエンティティにも適用されます。

savesaveAll.

getInstance: 保存するインスタンス

getDomainType: 保存するエンティティのタイプ。

get(<key>): 参照エンティティの ID。<key> は、NamingStrategy によって提供される後方参照列の名前です。

update

単一のエンティティを更新します。これは、集約ルートによって参照されるエンティティにも適用されます。

savesaveAll.

getInstance: 保存するインスタンス

getDomainType: 保存するエンティティのタイプ。

delete

単一のエンティティを削除します。

deletedeleteById.

getId: 削除するインスタンスの ID

getDomainType: 削除するエンティティのタイプ。

deleteAll-<propertyPath>

指定されたプロパティパスのプレフィックスとして使用されるタイプの集約ルートによって参照されるすべてのエンティティを削除します。ステートメント名のプレフィックスに使用されるタイプは、削除されるエンティティの名前ではなく、集約ルートの名前であることに注意してください。

deleteAll.

getDomainType: 削除するエンティティのタイプ。

deleteAll

プレフィックスとして使用されるタイプのすべての集約ルートを削除する

deleteAll.

getDomainType: 削除するエンティティのタイプ。

delete-<propertyPath>

指定された propertyPath を持つ集約ルートによって参照されるすべてのエンティティを削除する

deleteById.

getId: 参照されるエンティティが削除される集約ルートの ID。

getDomainType: 削除するエンティティのタイプ。

findById

ID で集約ルートを選択する

findById.

getId: ロードするエンティティの ID。

getDomainType: ロードするエンティティのタイプ。

findAll

すべての集約ルートを選択する

findAll.

getDomainType: ロードするエンティティのタイプ。

findAllById

ID 値によって集約ルートのセットを選択する

findAllById.

getId: ロードするエンティティの ID 値のリスト。

getDomainType: ロードするエンティティのタイプ。

findAllByProperty-<propertyName>

別のエンティティによって参照されるエンティティのセットを選択します。参照エンティティのタイプがプレフィックスに使用されます。参照されるエンティティタイプは、接尾辞として使用されます。このメソッドは非推奨です。代わりに findAllByPath を使用してください

すべての find* メソッド。findAllByPath にクエリが定義されていない場合

getId: ロードするエンティティを参照するエンティティの ID。

getDomainType: ロードするエンティティのタイプ。

findAllByPath-<propertyPath>

プロパティパスを介して別のエンティティによって参照されるエンティティのセットを選択します。

すべての find* メソッド。

getIdentifier: 集約ルートの ID と、すべてのパス要素のキーおよびリストインデックスを保持する Identifier

getDomainType: ロードするエンティティのタイプ。

findAllSorted

ソートされたすべての集約ルートを選択する

findAll(Sort).

getSort: ソート仕様。

findAllPaged

オプションでソートされた集約ルートのページを選択する

findAll(Page).

getPageable: ページング仕様。

count

プレフィックスとして使用されるタイプの集約ルートの数を数えます

count

getDomainType: カウントする集約ルートのタイプ。

9.9. ライフサイクルイベント

Spring Data JDBC は、アプリケーションコンテキストで一致する ApplicationListener Bean に発行されるイベントをトリガーします。例:集約が保存される前に、次のリスナーが呼び出されます。

@Bean
public ApplicationListener<BeforeSaveEvent<Object>> loggingSaves() {

	return event -> {

		Object entity = event.getEntity();
		LOG.info("{} is getting saved.");
	};
}

特定のドメインタイプのイベントのみを処理する場合は、AbstractRelationalEventListener からリスナーを派生させ、XXX がイベントタイプを表す 1 つ以上の onXXX メソッドを上書きします。コールバックメソッドは、ドメインタイプとそのサブタイプに関連するイベントに対してのみ呼び出されるため、これ以上キャストする必要はありません。

public class PersonLoadListener extends AbstractRelationalEventListener<Person> {

	@Override
	protected void onAfterLoad(AfterLoadEvent<Person> personLoad) {
		LOG.info(personLoad.getEntity());
	}
}

次の表に、使用可能なイベントを示します。

表 4: 利用可能なイベント
イベント 公開されたとき

BeforeDeleteEvent(Javadoc)

集約ルートが削除される前。

AfterDeleteEvent(Javadoc)

集約ルートが削除された後。

BeforeConvertEvent(Javadoc)

集約ルートが保存される前(つまり、挿入または更新されますが、更新または削除されるかどうかの決定が行われた後)。

BeforeSaveEvent(Javadoc)

集約ルートが保存される前(つまり、挿入または更新されますが、更新または削除されるかどうかの決定が行われた後)。

AfterSaveEvent(Javadoc)

集約ルートが保存された(つまり、挿入または更新された)後。

AfterLoadEvent(Javadoc)

集約ルートがデータベース ResultSet から作成され、そのすべてのプロパティが設定された後。

ライフサイクルイベントは ApplicationEventMulticaster に依存します。SimpleApplicationEventMulticaster の場合、TaskExecutor を使用して構成できるため、イベントが処理されるときの保証はありません。

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

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

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

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

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

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

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

例 60: EntityCallback の構造
@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {

	/**
	 * Entity callback method invoked before a domain object is saved.
	 * Can return either the same or a modified instance.
	 *
	 * @return the domain object to be persisted.
	 */
	T onBeforeSave(T entity <2>, String collection <3>); (1)
}
1 エンティティが保存される前に呼び出される BeforeSaveCallback 固有のメソッド。潜在的に変更されたインスタンスを返します。
2 永続化する直前のエンティティ。
3 エンティティが永続化されるコレクションなどのストア固有の引数の数。
例 61: リアクティブ EntityCallback の構造
@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {

	/**
	 * Entity callback method invoked on subscription, before a domain object is saved.
	 * The returned Publisher can emit either the same or a modified instance.
	 *
	 * @return Publisher emitting the domain object to be persisted.
	 */
	Publisher<T> onBeforeSave(T entity <2>, String collection <3>); (1)
}
1 エンティティが保存される前に、サブスクリプションで呼び出される BeforeSaveCallback 固有のメソッド。潜在的に変更されたインスタンスを発行します。
2 永続化する直前のエンティティ。
3 エンティティが永続化されるコレクションなどのストア固有の引数の数。
オプションのエンティティコールバックパラメーターは、実装 Spring Data モジュールによって定義され、EntityCallback.callback() の呼び出しサイトから推測されます。

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

例 62: 例 BeforeSaveCallback
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered {      (2)

	@Override
	public Object onBeforeSave(Person entity, String collection) {                   (1)

		if(collection == "user") {
		    return // ...
		}

		return // ...
	}

	@Override
	public int getOrder() {
		return 100;                                                                  (2)
	}
}
1 要件に応じたコールバックの実装。
2 同じドメインタイプのエンティティコールバックが複数存在する場合、エンティティコールバックを潜在的にオーダーします。優先順位は最低です。

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

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

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

例 63: EntityCallback Bean 登録の例
@Order(1)                                                           (1)
@Component
class First implements BeforeSaveCallback<Person> {

	@Override
	public Person onBeforeSave(Person person) {
		return // ...
	}
}

@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
                                                           Ordered { (2)

	@Override
	public Object onBeforeSave(Person entity, String collection) {
		// ...
	}

	@Override
	public int getOrder() {
		return 100;                                                  (2)
	}
}

@Configuration
public class EntityCallbackConfiguration {

    @Bean
    BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() {   (3)
        return (BeforeSaveCallback<Person>) it -> // ...
    }
}

@Component
class UserCallbacks implements BeforeConvertCallback<User>,
                                        BeforeSaveCallback<User> {   (4)

	@Override
	public Person onBeforeConvert(User user) {
		return // ...
	}

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

9.10.3. ストア固有の EntityCallbacks

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

表 5: 利用可能なコールバック
EntityCallback 公開されたとき

BeforeDeleteCallback(Javadoc)

集約ルートが削除される前。

AfterDeleteCallback(Javadoc)

集約ルートが削除された後。

BeforeConvertCallback(Javadoc)

集約ルートが保存される前(つまり、挿入または更新されますが、更新または削除されるかどうかの決定が行われた後)。

BeforeSaveCallback(Javadoc)

集約ルートが保存される前(つまり、挿入または更新されますが、更新または削除されるかどうかの決定が行われた後)。

AfterSaveCallback(Javadoc)

集約ルートが保存された(つまり、挿入または更新された)後。

AfterLoadCallback(Javadoc)

データベース ResultSet から集約ルートが作成され、そのすべてのプロパティが設定された後。

9.11. カスタム変換

Spring Data JDBC を使用すると、カスタムコンバーターを登録して、データベースでの値のマッピング方法に影響を与えることができます。現在、コンバーターはプロパティレベルでのみ適用されます。

9.11.1. 登録済みの Spring コンバーターを使用したプロパティの記述

次の例は、Boolean オブジェクトから String 値に変換する Converter の実装を示しています。

import org.springframework.core.convert.converter.Converter;

@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {

    @Override
    public String convert(Boolean source) {
        return source != null && source ? "T" : "F";
    }
}

ここで注意すべき点がいくつかあります。Boolean と String はどちらも単純なタイプであるため、Spring Data はこのコンバーターを適用する方向(読み取りまたは書き込み)のヒントを必要とします。このコンバーターに @WritingConverter のアノテーションを付けることにより、すべての Boolean プロパティを String としてデータベースに書き込むように Spring Data に指示します。

9.11.2. Spring コンバーターを使用した読み取り

次の例は、String から Boolean 値に変換する Converter の実装を示しています。

@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {

    @Override
    public Boolean convert(String source) {
        return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
    }
}

ここで注意すべき点がいくつかあります。String と Boolean はどちらも単純なタイプであるため、Spring Data はこのコンバーターを適用する方向(読み取りまたは書き込み)のヒントを必要とします。このコンバーターに @ReadingConverter のアノテーションを付けることにより、Spring Data に、Boolean プロパティに割り当てる必要があるデータベースからのすべての String 値を変換するように指示します。

9.11.3. JdbcConverter を使用した Spring コンバーターの登録

class MyJdbcConfiguration extends AbstractJdbcConfiguration {

    //  …

    @Overwrite
    @Bean
    public JdbcCustomConversions jdbcCustomConversions() {
        return new JdbcCustomConversions(Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter()));
    }
}

次の Spring Converter 実装の例は、String からカスタム Email 値オブジェクトに変換します。

@ReadingConverter
public class EmailReadConverter implements Converter<String, Email> {

  public Email convert(String source) {
    return Email.valueOf(source);
  }
}

ソースとターゲットのタイプがネイティブタイプである Converter を記述した場合、それを読み取りコンバーターと見なすか、書き込みコンバーターと見なすべきかを判断できません。コンバーターインスタンスを両方として登録すると、望ましくない結果が生じる可能性があります。例: Converter<String, Long> があいまいですが、書き込み時にすべての String インスタンスを Long インスタンスに変換しようとしても意味がありません。インフラストラクチャーにコンバーターを一方向にのみ登録させるために、コンバーターの実装で使用される @ReadingConverter および @WritingConverter アノテーションを提供します。

インスタンスはクラスパスまたはコンテナースキャンから取得されないため、コンバーターは明示的な登録の対象となります。変換サービスへの不要な登録と、そのような登録に起因する副作用を回避するためです。コンバーターは、ソースおよびターゲットのタイプに基づいて、登録されたコンバーターの登録および照会を可能にする中央機能として CustomConversions に登録されます。

CustomConversions には、事前に定義された一連のコンバーター登録が付属しています。

  • java.timejava.util.Date タイプと String タイプ間の変換用の JSR-310 コンバーター。

  • 非推奨 : org.joda.time、JSR-310、および java.util.Date 間の変換用の Joda Time Converters。

  • 非推奨 : org.joda.time、JSR-310、および java.util.Date 間の変換用の ThreeTenBackport コンバーター。

ローカルテンポラルタイプ(LocalDateTime から java.util.Date など)のデフォルトコンバーターは、システムデフォルトのタイムゾーン設定に依存して、これらのタイプ間で変換します。独自のコンバーターを登録することにより、デフォルトのコンバーターをオーバーライドできます。
コンバーターの明確化

一般に、Converter の実装は、変換元と変換先のソース型とターゲット型をインスペクションします。これらの 1 つが、基になるデータアクセス API がネイティブに処理できるタイプかどうかに応じて、コンバーターインスタンスを読み取りまたは書き込みコンバーターとして登録します。次の例は、書き込みコンバーターと読み取りコンバーターを示しています(違いは Converter の修飾子の順序にあることに注意してください)。

// Write converter as only the target type is one that can be handled natively
class MyConverter implements Converter<Person, String> {  …  }

// Read converter as only the source type is one that can be handled natively
class MyConverter implements Converter<String, Person> {  …  }

9.12. ロギング

Spring Data JDBC は、それ自体ではほとんどまたはまったくロギングを行いません。代わりに、SQL ステートメントを発行する JdbcTemplate のメカニズムにより、ログが提供されます。実行されている SQL ステートメントをインスペクションする場合は、Spring の NamedParameterJdbcTemplate または MyBatis のロギングをアクティブにします。

9.13. トランザクション性

リポジトリインスタンスの CRUD メソッドは、デフォルトではトランザクションです。読み取り操作の場合、トランザクション構成 readOnly フラグは true に設定されます。他のすべては、デフォルトのトランザクション構成が適用されるように、プレーンな @Transactional アノテーションで構成されます。詳細については、SimpleJdbcRepository(Javadoc) の Javadoc を参照してください。リポジトリで宣言されたメソッドのいずれかのトランザクション構成を微調整する必要がある場合は、次のようにリポジトリインターフェースでメソッドを再宣言します。

例 64: CRUD のカスタムトランザクション設定
public interface UserRepository extends CrudRepository<User, Long> {

  @Override
  @Transactional(timeout = 10)
  public List<User> findAll();

  // Further query method declarations
}

上記により、findAll() メソッドはタイムアウトが 10 秒で、readOnly フラグなしで実行されます。

トランザクションの動作を変更する別の方法は、通常複数のリポジトリをカバーするファサードまたはサービス実装を使用することです。その目的は、非 CRUD 操作のトランザクション境界を定義することです。次の例は、このようなファサードを作成する方法を示しています。

例 65: ファサードを使用して複数のリポジトリ呼び出しのトランザクションを定義する
@Service
class UserManagementImpl implements UserManagement {

  private final UserRepository userRepository;
  private final RoleRepository roleRepository;

  @Autowired
  public UserManagementImpl(UserRepository userRepository,
    RoleRepository roleRepository) {
    this.userRepository = userRepository;
    this.roleRepository = roleRepository;
  }

  @Transactional
  public void addRoleToAllUsers(String roleName) {

    Role role = roleRepository.findByName(roleName);

    for (User user : userRepository.findAll()) {
      user.addRole(role);
      userRepository.save(user);
    }
}

上記の例では、addRoleToAllUsers(…) の呼び出しがトランザクション内で実行されます(既存のトランザクションに参加するか、まだ実行されていない場合は新しいトランザクションを作成します)。リポジトリのトランザクション設定は無視されます。これは、外部のトランザクション設定が実際に使用されるリポジトリを決定するためです。<tx:annotation-driven /> を明示的にアクティブにするか、@EnableTransactionManagement を使用して、ファサードが機能するようにアノテーションベースの構成を取得する必要があることに注意してください。上記の例では、コンポーネントスキャンを使用することを前提としています。

9.13.1. トランザクションクエリメソッド

クエリメソッドをトランザクション対応にするには、次の例に示すように、定義するリポジトリインターフェースで @Transactional を使用します。

例 66: クエリメソッドで @Transactional を使用する
@Transactional(readOnly = true)
public interface UserRepository extends CrudRepository<User, Long> {

  List<User> findByLastname(String lastname);

  @Modifying
  @Transactional
  @Query("delete from User u where u.active = false")
  void deleteInactiveUsers();
}

通常、ほとんどのクエリメソッドはデータの読み取りのみを行うため、readOnly フラグを true に設定する必要があります。それとは対照的に、deleteInactiveUsers() は @Modifying アノテーションを使用し、トランザクション構成をオーバーライドします。このメソッドは readOnly フラグが false に設定されています。

読み取り専用クエリにトランザクションを使用することは間違いなく合理的であり、readOnly フラグを設定することでトランザクションをそのようにマークできます。ただし、これは、操作クエリをトリガーしないことのチェックとしては機能しません(ただし、一部のデータベースは読み取り専用トランザクション内で INSERT および UPDATE ステートメントを拒否します)。代わりに、readOnly フラグは、パフォーマンスの最適化のための基礎となる JDBC ドライバーへのヒントとして伝搬されます。

9.14. 監査

9.14.1. 基本

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

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

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

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

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  //  …  further properties omitted
}

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

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

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

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

AuditorAware

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

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

例 68: Spring Security に基づく AuditorAware の実装
class SpringSecurityAuditorAware implements AuditorAware<User> {

  public Optional<User> getCurrentAuditor() {

    return Optional.ofNullable(SecurityContextHolder.getContext())
			  .map(SecurityContext::getAuthentication)
			  .filter(Authentication::isAuthenticated)
			  .map(Authentication::getPrincipal)
			  .map(User.class::cast);
  }
}

実装は、Spring Security が提供する Authentication オブジェクトにアクセスし、UserDetailsService 実装で作成したカスタム UserDetails インスタンスを検索します。ここでは、UserDetails 実装を介してドメインユーザーを公開しているが、見つかった Authentication に基づいて、どこからでも検索できると想定しています。

9.15. JDBC 監査

監査をアクティブにするには、次の例に示すように、@EnableJdbcAuditing を構成に追加します。

例 69: Java 構成を使用した監査のアクティブ化
@Configuration
@EnableJdbcAuditing
class Config {

  @Bean
  public AuditorAware<AuditableUser> auditorProvider() {
    return new AuditorAwareImpl();
  }
}

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

付録

付録 A: 用語集

AOP

アスペクト指向プログラミング

CRUD

作成、読み取り、更新、削除 - 基本的な永続化操作

依存性注入

外部からコンポーネントへのコンポーネントの依存関係を渡すパターン。コンポーネントを解放して、依存関係自体をルックアップします。詳細については、https://ja.wikipedia.org/wiki/ 依存性の注入 を参照してください。

JPA

Java Persistence API

Spring

Java アプリケーションフレームワーク  —  /projects/spring-framework

付録 B: 名前空間リファレンス

<repositories /> 要素

<repositories /> 要素は、Spring Data リポジトリインフラストラクチャのセットアップをトリガーします。最も重要な属性は base-package です。これは、Spring Data リポジトリインターフェースをスキャンするパッケージを定義します。「XML 構成」を参照してください。次の表は、<repositories /> 要素の属性を説明しています。

表 6: 属性
名前 説明

base-package

自動検出モードで *Repository を継承するリポジトリインターフェース(実際のインターフェースは特定の Spring Data モジュールによって決定されます)をスキャンするパッケージを定義します。設定されたパッケージのすべてのパッケージもスキャンされます。ワイルドカードが許可されます。

repository-impl-postfix

カスタムリポジトリ実装を自動検出するための接尾辞を定義します。名前が構成された接尾辞で終わるクラスは、候補と見なされます。デフォルトは Impl です。

query-lookup-strategy

ファインダー照会の作成に使用される戦略を決定します。詳細については、「クエリ検索戦略」を参照してください。デフォルトは create-if-not-found です。

named-queries-location

外部で定義されたクエリを含むプロパティファイルを検索する場所を定義します。

consider-nested-repositories

ネストされたリポジトリインターフェース定義を考慮する必要があるかどうか。デフォルトは false です。

付録 C: Populators 名前空間リファレンス

<populator /> 要素

<populator /> 要素を使用すると、Spring Data リポジトリインフラストラクチャを介してデータストアにデータを入力できます。[1]

表 7: 属性
名前 説明

locations

リポジトリからオブジェクトを読み取るためのファイルの場所には、データが入力されます。

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

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

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

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

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

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

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

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

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

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


1. XML 構成を参照

Unofficial Translation by spring.pleiades.io. See the original content.