© 2014-2022 The original author(s).

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

序文

このリファレンスドキュメントでは、Spring Data Couchbase ライブラリの一般的な使用方法について説明します。

プロジェクト情報

Spring Data Couchbase 3.x から 4.x への移行

この章は、4.x に導入された主な変更点のクイックリファレンスであり、移行時に考慮すべき事項の概要を示します。

Couchbase サーバーの最小バージョンは暗黙的に 5.5 以降にプルアップられており、少なくとも 6.0.x を実行することをお勧めします。

構成

主な目的は Java SDK 2 から 3 に移行することであったため、新しい SDK に適応するように構成が変更され、長期的にはスコープとコレクションを準備するために構成が変更されました (ただし、コレクションのサポートなしでも使用できます)。

XML 構成のサポートは廃止されたため、java/ アノテーションベースの構成のみがサポートされます。

構成は引き続き AbstractCouchbaseConfiguration を継承する必要がありますが、RBAC (ロールベースのアクセス制御) が必須になっているため、構成するにはさまざまなプロパティをオーバーライドする必要があります: getConnectionStringgetUserNamegetPasswordgetBucketName。オプションでデフォルト以外のスコープを使用したい場合は、getScopeName メソッドをオーバーライドできます。証明書ベースの認証を使用する場合、またはパスワード認証をカスタマイズする必要がある場合は、authenticator メソッドをオーバーライドしてこのタスクを実行できることに注意してください。

新しい SDK には構成に使用される環境がまだ残っているため、必要に応じて configureEnvironment メソッドをオーバーライドしてカスタム構成を提供できます。

詳しくは、インストールと構成を参照してください。

Spring Boot バージョンの互換性

Spring Boot 2.3.x 以上は Spring Data Couchbase 4.x に依存します。SDK 2 と 3 は同じクラスパス上に存在できないため、Couchbase の以前のバージョンは使用できません。

エンティティ

エンティティの処理方法は変更されていませんが、SDK ではアノテーションが提供されなくなったため、Spring-Data 関連のアノテーションのみがサポートされます。

具体的には:

  • com.couchbase.client.java.repository.annotation.Id が import org.springframework.data.annotation.Id になりました

  • com.couchbase.client.java.repository.annotation.Field が import org.springframework.data.couchbase.core.mapping.Field になりました

org.springframework.data.couchbase.core.mapping.Document アノテーションは変わりません。

詳しくは、エンティティのモデリングを参照してください。

自動インデックス管理

自動インデックス管理が再設計され、より柔軟なインデックス作成が可能になりました。新しいアノテーションが導入され、@ViewIndexed@N1qlSecondaryIndexed@N1qlPrimaryIndexed などの古いアノテーションが削除されました。

詳しくは、自動インデックス管理を参照してください。

テンプレートと ReactiveTemplate

Couchbase SDK 3 では RxJava のサポートが削除され、代わりに Reactor のサポートが追加されているため、couchbaseTemplate と reactiveCouchbaseTemplate の両方に AbstractCouchbaseConfiguration から直接アクセスできます。

テンプレートは完全に見直され、多くのメソッドオーバーロードの代わりに流れるような API を使用して構成するようになりました。これには、将来、ナビゲートを複雑にするオーバーロードをさらに導入することなく機能を継承できるという利点があります。

次の表では、3.x のメソッド名について説明し、4.x の同等のメソッド名と比較しています。

表 1: テンプレートメソッドの比較
SDC 3.xSDC 4.x

save

Id による更新挿入

insert

Id による挿入

update

Id による置き換え

findById

findById

findByView

(削除されました)

findBySpatialView

(削除されました)

N1QL で見つける

findByQuery

findByN1QLProjection

findByQuery

クエリ N1QL

(SDK を直接呼び出す)

exists

existsById

remove

ID による削除

execute

(SDK を直接呼び出す)

さらに、3.x では利用できなかった次のメソッドが追加されました。

表 2: 4.x でのテンプレートの追加
名前 説明

removeByQuery

N1QL クエリを通じてエンティティを削除できるようにします

findByAnalytics

分析サービスを通じて検索を実行します

findFromReplicasById

findById と似ていますが、レプリカが考慮されます

API を基盤となる SDK セマンティクスに近づけて統合し、連携させ、相互に関連付けて操作しやすくすることを試みました。

詳しくは、テンプレートと直接操作を参照してください。

リポジトリとクエリ

  • org.springframework.data.couchbase.core.query.Query が org.springframework.data.couchbase.repository.Query になりました

  • org.springframework.data.couchbase.repository.ReactiveCouchbaseSortingRepository は削除されました。ReactiveSortingRepository または ReactiveCouchbaseRepository の拡張を検討してください

  • org.springframework.data.couchbase.repository.CouchbasePagingAndSortingRepository は削除されました。PagingAndSortingRepository または CouchbaseRepository の拡張を検討してください

ビューのサポートは削除され、N1QL クエリは、すべてのカスタムリポジトリメソッドおよびデフォルトの組み込みメソッドの 第一級オブジェクトになりました。

動作自体は、クエリ導出がどのように機能するかについて、以前のバージョンから変更されていません。以前は機能していたが現在は機能しなくなったクエリが発生した場合は、お知らせください。

新しい ScanConsistency アノテーションを使用して、N1QL クエリのデフォルトのスキャン一貫性をオーバーライドすることができます。

メソッド getCouchbaseOperations() も削除されました。クラス CouchbaseTemplate または Cluster を介して、ネイティブ Java SDK からすべてのメソッドに引き続きアクセスできます。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.stereotype.Service;
import com.couchbase.client.java.Cluster;

@Service
public class MyService {

    @Autowired
    private CouchbaseTemplate couchbaseTemplate;

    @Autowired
    private Cluster cluster;
}

詳細については、Couchbase リポジトリを参照してください。

全文検索 (FTS)

FTS API は簡素化され、Cluster クラス経由でアクセスできるようになりました。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.search.result.SearchResult;
import com.couchbase.client.java.search.result.SearchRow;
import com.couchbase.client.core.error.CouchbaseException;

@Service
public class MyService {

    @Autowired
    private Cluster cluster;

    public void myMethod() {
        try {
          final SearchResult result = cluster
            .searchQuery("index", SearchQuery.queryString("query"));

          for (SearchRow row : result.rows()) {
            System.out.println("Found row: " + row);
          }

          System.out.println("Reported total rows: "
            + result.metaData().metrics().totalRows());
        } catch (CouchbaseException ex) {
          ex.printStackTrace();
        }
    }
}

詳細については、FTS ドキュメント (英語) を参照してください。

1. Spring Data のアップグレード

Spring Data の以前のバージョンからアップグレードする方法については、プロジェクトの wiki [GitHub] (英語) で説明されています。リリースノートセクション [GitHub] (英語) のリンクに従って、アップグレードするバージョンを見つけます。

バージョンアップ手順は、常にリリースノートの最初の項目です。複数のバージョンを飛び越えてバージョンアップする場合は、途中のバージョンのリリースノートも確認してください。

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

2. インストールと構成

この章では、ライブラリを使用するときに必要な一般的なインストールと構成の手順について説明します。

2.1. インストール

運用環境での使用を目的としたすべてのバージョンは、Maven Central と Spring リリースリポジトリに配布されます。その結果、ライブラリは他の maven 依存関係と同様に含めることができます。

例 1: maven を介した依存関係を含める
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-couchbase</artifactId>
    <version>5.0.6</version>
</dependency>

これにより、基盤となる Couchbase Java SDK、一般的な Spring 依存関係、さらに JSON マッピングインフラストラクチャとしての Jackson など、いくつかの依存関係が取り込まれます。

Spring スナップショットリポジトリ (英語) (https://repo.spring.io/snapshot ) からスナップショットを取得したり、Spring マイルストーンリポジトリ (英語) (https://repo.spring.io/milestone ) からマイルストーンリリースを取得したりすることもできます。現在の SNAPSHOT 依存関係の使用方法の例を次に示します。

例 2: スナップショットバージョンの使用
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-couchbase</artifactId>
  <version>${version}-SNAPSHOT</version>
</dependency>

<repository>
  <id>spring-libs-snapshot</id>
  <name>Spring Snapshot Repository</name>
  <url>https://repo.spring.io/snapshot</url>
</repository>

クラスパス上で必要な依存関係をすべて取得したら、クラスパスの構成を開始できます。Java 構成のみがサポートされています (XML 構成は 4.0 では削除されています)。

2.2. アノテーションベースの構成 ("JavaConfig" )

開始するには、AbstractCouchbaseConfiguration をサブクラス化し、抽象メソッドを実装するだけです。

例 3: AbstractCouchbaseConfiguration の延長
@Configuration
public class Config extends AbstractCouchbaseConfiguration {

    @Override
    public String getConnectionString() {
        return "couchbase://127.0.0.1";
    }

    @Override
    public String getUserName() {
        return "Administrator";
    }

    @Override
    public String getPassword() {
        return "password";
    }

    @Override
    public String getBucketName() {
        return "travel-sample";
    }
}

接続文字列は、上記のコードに示すように、ホストのリストとオプションのスキーム (couchbase://) で構成されます。提供する必要があるのは、ブートストラップする Couchbase ノードのリスト ( , で区切られる) だけです。開発では 1 つのホストで十分ですが、ここでは 3 ~ 5 つのブートストラップノードを追加することをお勧めします。Couchbase はクラスターからすべてのノードを自動的に選択しますが、アプリケーションの起動中に、指定した唯一のノードで問題が発生する場合があります。

userName および password は、RBAC (ロールベースのアクセス制御) を通じて Couchbase サーバークラスター内で構成されます。bucketName は、この構成に使用するバケットを反映します。

さらに、ClusterEnvironment.Builder を受け取って構成された ClusterEnvironment を返す configureEnvironment メソッドをオーバーライドすることで、SDK 環境を調整できます。

この構成からさらに多くのものをカスタム Bean としてカスタマイズおよびオーバーライドできます (リポジトリ、検証、カスタムコンバーターなど)。

SyncGateway および CouchbaseMobile を使用する場合、_ という接頭辞が付いているフィールドで問題が発生する可能性があります。Spring Data Couchbase はデフォルトで型情報を _class 属性として保存するため、これが問題となる可能性があります。typeKey() をオーバーライドして (たとえば、MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE を返す)、上記の属性の名前を変更します。

アプリケーションを起動すると、Couchbase INFO レベルのログがログに記録され、基盤となる Couchbase Java SDK がデータベースに接続していることが示されます。エラーが報告された場合は、指定された資格情報とホスト情報が正しいことを確認してください。

3. エンティティのモデリング

この章では、エンティティをモデル化する方法と、Couchbase サーバー自体での対応するエンティティの表現について説明します。

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

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

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

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

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

3.1.1. オブジェクト作成

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

  1. @PersistenceCreator でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。

  2. コンストラクターが 1 つしかない場合は、それが使用されます。

  3. 複数のコンストラクターがあり、そのうちの 1 つだけに @PersistenceCreator アノテーションが付けられている場合は、それが使用されます。

  4. 型が Java Record の場合、標準コンストラクターが使用されます。

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

値の解決では、コンストラクター / ファクトリメソッドの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含め、プロパティが入力されたかのように解決が実行されます。これには、クラスファイルで利用可能なパラメーター名情報、またはコンストラクターに存在する @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 はリフレクションを介してエンティティのインスタンス化にフォールバックします。

3.1.2. プロパティ設定

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

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

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

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

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

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

プロパティ設定の詳細

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

class Person {

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

  Person() {
    this.id = null;
  }

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

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

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

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

例 5: サンプルエンティティ
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 を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは age フィールドを設定しようとし、不変で no with …  メソッドが存在します。
4comment プロパティは変更可能で、そのフィールドを直接設定することによって入力されます。
5remarks プロパティは変更可能で、setter メソッドを呼び出すことによって設定されます。
6 このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの中心的な考え方は、@PersistenceCreator によるコンストラクターの曖昧性解消の必要性を回避するために、追加のコンストラクターの代わりにファクトリメソッドを使用することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。Spring Data でオブジェクトのインスタンス化にファクトリメソッドを使用する場合は、@PersistenceCreator でアノテーションを付けます。

3.1.3. 一般的な推奨事項

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

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

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

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

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

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

プロパティのオーバーライド

Java を使用すると、ドメインクラスを柔軟に設計できます。この場合、サブクラスは、スーパークラスで同じ名前ですでに宣言されているプロパティを定義できます。次の例を考えてみましょう。

public class SuperType {

   private CharSequence field;

   public SuperType(CharSequence field) {
      this.field = field;
   }

   public CharSequence getField() {
      return this.field;
   }

   public void setField(CharSequence field) {
      this.field = field;
   }
}

public class SubType extends SuperType {

   private String field;

   public SubType(String field) {
      super(field);
      this.field = field;
   }

   @Override
   public String getField() {
      return this.field;
   }

   public void setField(String field) {
      this.field = field;

      // optional
      super.setField(field);
   }
}

どちらのクラスも、割り当て可能な型を使用して field を定義します。ただし、SubType は SuperType.field をシャドウします。クラスの設計によっては、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチである可能性があります。または、setter で super.setField(…) を呼び出すと、SuperType で field を設定できます。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、これらすべてのメカニズムはある程度の競合を引き起こします。Spring Data は、型が割り当て可能でない場合、スーパー型のプロパティをスキップします。つまり、オーバーライドされたプロパティの型は、オーバーライドとして登録されるスーパー型のプロパティ型に割り当て可能である必要があります。そうでない場合、スーパー型のプロパティは一時的なものと見なされます。通常、個別のプロパティ名を使用することをお勧めします。

Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。

  1. どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに @Transient アノテーションを付けることで、プロパティを除外できます。

  2. データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。

  3. @AccessType(PROPERTY) を使用することは、通常、setter 実装のさらなる仮定を行わずにスーパープロパティを設定することができないため、使用できません。

3.1.4. Kotlin サポート

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

Kotlin オブジェクトの作成

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

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

  1. @PersistenceCreator でアノテーションが付けられたコンストラクターがある場合は、それが使用されます。

  2. 型が Kotlin データクラスの場合、プライマリコンストラクターが使用されます。

  3. @PersistenceCreator でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。

  4. コンストラクターが 1 つしかない場合は、それが使用されます。

  5. 複数のコンストラクターがあり、そのうちの 1 つだけに @PersistenceCreator アノテーションが付けられている場合は、それが使用されます。

  6. 型が Java Record の場合、標準コンストラクターが使用されます。

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

次の data クラス Person を検討してください。

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

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

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

    @PersistenceCreator
    constructor(id: String) : this(id, "unknown")
}

Kotlin は、パラメーターが提供されない場合にデフォルト値を使用できるようにすることで、パラメーターのオプションをサポートしています。Spring Data がパラメーターのデフォルト設定を持つコンストラクターを検出した場合、データストアが値を提供しない(または単に null を返す)場合、Kotlin はパラメーターのデフォルト設定を適用できるため、これらのパラメーターは存在しません。name のパラメーターのデフォルト設定を適用する次のクラスを検討してください。

data class Person(var id: String, val name: String = "unknown")

name パラメーターが結果の一部ではないか、その値が null であるたびに、name は unknown にデフォルト設定されます。

Kotlin データクラスのプロパティ設定

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

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

このクラスは事実上不変です。Kotlin が既存のオブジェクトからすべてのプロパティ値をコピーしてメソッドに引数として提供されたプロパティ値を適用する新しいオブジェクトインスタンスを作成する copy(…) メソッドを生成するときに、新しいインスタンスを作成できます。

Kotlin オーバーライドプロパティ

Kotlin では、プロパティのオーバーライド (英語) を宣言して、サブクラスのプロパティを変更できます。

open class SuperType(open var field: Int)

class SubType(override var field: Int = 1) :
	SuperType(field) {
}

このような配置では、field という名前の 2 つのプロパティがレンダリングされます。Kotlin は、各クラスの各プロパティのプロパティアクセサー(getter および setter)を生成します。事実上、コードは次のようになります。

public class SuperType {

   private int field;

   public SuperType(int field) {
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

public final class SubType extends SuperType {

   private int field;

   public SubType(int field) {
      super(field);
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

SubType の Getter および setter は、SubType.field のみを設定し、SuperType.field は設定しません。このような配置では、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチです。SubType にメソッドを追加して this.SuperType.field = …  を介して SuperType.field を設定することは可能ですが、サポートされている規則の範囲外です。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、プロパティのオーバーライドによってある程度の競合が発生します。通常、個別のプロパティ名を使用することをお勧めします。

Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。

  1. どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに @Transient アノテーションを付けることで、プロパティを除外できます。

  2. データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。

  3. @AccessType(PROPERTY) を使用すると、スーパープロパティが設定できないため使用できません。

3.2. ドキュメントとフィールド

すべてのエンティティには @Document アノテーションを付ける必要がありますが、これは必須ではありません。

また、エンティティ内のすべてのフィールドに @Field アノテーションを付ける必要があります。これは厳密に言えばオプションですが、エッジケースを減らすのに役立ち、エンティティの意図と設計を明確に示します。フィールドを別の名前で保存するために使用することもできます。

常に配置する必要がある特別な @Id アノテーションもあります。ベストプラクティスは、プロパティにも id という名前を付けることです。

以下は非常に単純な User エンティティです。

例 6: フィールドを含む単純なドキュメント
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.mapping.Document;

@Document
public class User {

    @Id
    private String id;

    @Field
    private String firstname;

    @Field
    private String lastname;

    public User(String id, String firstname, String lastname) {
        this.id = id;
        this.firstname = firstname;
        this.lastname = lastname;
    }

    public String getId() {
        return id;
    }

    public String getFirstname() {
        return firstname;
    }

    public String getLastname() {
        return lastname;
    }
}

Couchbase サーバーは、ドキュメントの自動有効期限切れをサポートします。ライブラリは、@Document アノテーションを通じてそのサポートを実装します。ドキュメントが自動的に削除されるまでの秒数に変換される expiry 値を設定できます。変更後 10 秒で期限切れにしたい場合は、@Document(expiry = 10) のように設定します。あるいは、Spring のプロパティサポートと expiryExpression パラメーターを使用して有効期限を構成し、有効期限値を動的に変更できるようにすることもできます。例: @Document(expiryExpression = "${valid.document.expiry}")。プロパティは int 値に解決できる必要があり、2 つのアプローチを混合することはできません。

エンティティで使用されるフィールド名とは対照的に、ドキュメント内のフィールド名を別の表現にしたい場合は、@Field アノテーションに別の名前を設定できます。たとえば、ドキュメントを小さくしたい場合は、名フィールドを @Field("fname") に設定できます。JSON ドキュメントでは、{"firstname": ".."} ではなく {"fname": ".."} が表示されます。

Couchbase 内のすべてのドキュメントには一意のキーが必要であるため、@Id アノテーションが存在する必要があります。このキーは、最大 250 文字の長さの任意の文字列である必要があります。UUID、メールアドレス、その他何でも、あなたのユースケースに合ったものを自由に使用してください。

Couchbase-Server バケットへの書き込みには、オプションで耐久性要件を割り当てることができます。これにより、書き込みをコミットする前に、Couchbase Server に、指定されたドキュメントをメモリ内またはクラスタ全体のディスク上の複数のノードで更新するように指示します。デフォルトの耐久性要件は、@Document アノテーションを使用して構成することもできます。たとえば、@Document(durabilityLevel = DurabilityLevel.MAJORITY) は、変更をデータサービスノードの大部分に強制的に複製します。

3.3. データ型とコンバーター

選択した保存形式は JSON です。これは素晴らしいことですが、多くのデータ表現と同様に、Java で直接表現できるデータ型よりも少ないデータ型を使用できます。すべての非プリミティブ型については、サポートされている型との間で何らかの形式の変換を行う必要があります。

次のエンティティフィールド型の場合、特別な処理を追加する必要はありません。

表 3: 基本タイプ
Java 型 JSON 表現

string

文字列

boolean

boolean

byte

番号

short

番号

int

番号

long

番号

float

番号

double

番号

null

書き込み時に無視される

JSON はオブジェクト (「マップ」) とリストをサポートしているため、Map 型と List 型は自然に変換できます。最後の段落のプリミティブフィールド型のみが含まれている場合は、特別な処理を追加する必要もありません。以下に例を示します。

例 7: 地図とリストを含むドキュメント
@Document
public class User {

    @Id
    private String id;

    @Field
    private List<String> firstnames;

    @Field
    private Map<String, Integer> childrenAges;

    public User(String id, List<String> firstnames, Map<String, Integer> childrenAges) {
        this.id = id;
        this.firstnames = firstnames;
        this.childrenAges = childrenAges;
    }

}

ユーザーとサンプルデータを保存すると、JSON 表現として次のようになります。

例 8: マップとリストを含むドキュメント - JSON
{
    "_class": "foo.User",
    "childrenAges": {
        "Alice": 10,
        "Bob": 5
    },
    "firstnames": [
        "Foo",
        "Bar",
        "Baz"
    ]
}

常にすべてをプリミティブ型とリスト / マップに分解する必要はありません。もちろん、これらのプリミティブ値から他のオブジェクトを構成することもできます。List または Children を保存するように最後の例を変更しましょう。

例 9: 構成されたオブジェクトを含むドキュメント
@Document
public class User {

    @Id
    private String id;

    @Field
    private List<String> firstnames;

    @Field
    private List<Child> children;

    public User(String id, List<String> firstnames, List<Child> children) {
        this.id = id;
        this.firstnames = firstnames;
        this.children = children;
    }

    static class Child {
        private String name;
        private int age;

        Child(String name, int age) {
            this.name = name;
            this.age = age;
        }

    }

}

設定されたオブジェクトは次のようになります。

例 10: 構成されたオブジェクトを含むドキュメント - JSON
{
  "_class": "foo.User",
  "children": [
    {
      "age": 4,
      "name": "Alice"
    },
    {
      "age": 3,
      "name": "Bob"
    }
  ],
  "firstnames": [
    "Foo",
    "Bar",
    "Baz"
  ]
}

ほとんどの場合、Date のような一時的な値も保存する必要があります。JSON に直接保存できないため、変換を行う必要があります。このライブラリは、DateCalendar、JodaTime 型 (クラスパス上にある場合) のデフォルトのコンバーターを実装します。これらはすべて、ドキュメント内ではデフォルトで UNIX タイムスタンプ (数値) として表されます。後で示すように、カスタムコンバーターを使用してデフォルトの動作をいつでもオーバーライドできます。以下に例を示します。

例 11: 日付とカレンダーが記載されたドキュメント
@Document
public class BlogPost {

    @Id
    private String id;

    @Field
    private Date created;

    @Field
    private Calendar updated;

    @Field
    private String title;

    public BlogPost(String id, Date created, Calendar updated, String title) {
        this.id = id;
        this.created = created;
        this.updated = updated;
        this.title = title;
    }

}

設定されたオブジェクトは次のようになります。

例 12: 日付とカレンダーを含むドキュメント - JSON
{
  "title": "a blog post title",
  "_class": "foo.BlogPost",
  "updated": 1394610843,
  "created": 1394610843897
}

オプションで、システムプロパティ org.springframework.data.couchbase.useISOStringConverterForDate を true に設定することで、日付を ISO-8601 準拠の文字列との間で変換できます。コンバーターをオーバーライドしたり、独自のコンバーターを実装したりすることも可能です。このライブラリは、一般的な Spring コンバーターパターンを実装します。Bean の作成時に構成でカスタムコンバーターをプラグインできます。(オーバーライドされた AbstractCouchbaseConfiguration で) 設定する方法は次のとおりです。

例 13: カスタムコンバーター
@Override
public CustomConversions customConversions() {
    return new CustomConversions(Arrays.asList(FooToBarConverter.INSTANCE, BarToFooConverter.INSTANCE));
}

@WritingConverter
public static enum FooToBarConverter implements Converter<Foo, Bar> {
    INSTANCE;

    @Override
    public Bar convert(Foo source) {
        return /* do your conversion here */;
    }

}

@ReadingConverter
public static enum BarToFooConverter implements Converter<Bar, Foo> {
    INSTANCE;

    @Override
    public Foo convert(Bar source) {
        return /* do your conversion here */;
    }

}

カスタム変換については、次の点に留意する必要があります。

  • 明確にするために、コンバーターでは常に @WritingConverter および @ReadingConverter アノテーションを使用してください。特にプリミティブ型変換を扱っている場合、これは誤った変換の可能性を減らすのに役立ちます。

  • 書き込みコンバーターを実装する場合は、必ずプリミティブ型、マップ、リストのみにデコードしてください。より複雑なオブジェクト型が必要な場合は、基盤となる変換エンジンでも理解される CouchbaseDocument および CouchbaseList 型を使用します。最善の策は、できるだけ単純な変換に固執することです。

  • 間違ったコンバーターが実行されることを避けるために、常に、より特殊なコンバーターを汎用コンバーターの前に配置してください。

  • 日付の場合、読み取りコンバーターは ( Long だけでなく) 任意の Number から読み取ることができる必要があります。これは N1QL サポートに必要です。

3.4. 楽観的ロック

状況によっては、ドキュメントに対して変更操作を実行するときに、別のユーザーの変更が上書きされないようにする必要がある場合があります。これには、トランザクション (Couchbase 6.5 以降)、ペシミスティック同時実行 (ロック)、またはオプティミスティック同時実行の 3 つの選択肢があります。

オプティミスティック同時実行は、データに対して実際のロックが保持されず、操作に関する追加情報が保存されない (トランザクションログがない) ため、悲観的同時実行やトランザクションよりも優れたパフォーマンスを提供する傾向があります。

楽観的ロックを実装するために、Couchbase は CAS (比較およびスワップ) アプローチを使用します。ドキュメントが変更されると、CAS 値も変更されます。CAS はクライアントに対して不透明です。唯一知っておく必要があるのは、コンテンツまたはメタ情報が変更されると CAS も変更されるということです。

他のデータストアでは、カウンターが増加する任意のバージョンフィールドを通じて同様の動作を実現できます。Couchbase はこれをより優れた方法でサポートしているため、実装が簡単です。自動オプティミスティックロックのサポートが必要な場合は、次のように長いフィールドに @Version アノテーションを追加するだけです。

例 14: 楽観的ロックを備えたドキュメント。
@Document
public class User {

        @Version
        private long version;

        // constructor, getters, setters...
}

テンプレートまたはリポジトリを通じてドキュメントをロードすると、バージョンフィールドに現在の CAS 値が自動的に入力されます。フィールドにアクセスしたり、自分でフィールドを変更したりしないでください。ドキュメントを再度保存すると、成功するか、OptimisticLockingFailureException で失敗します。このような例外が発生した場合、その後のアプローチは、アプリケーションで何を達成したいかによって異なります。読み込み、更新、書き込みサイクル全体を再試行するか、エラーを上位層に伝播して適切に処理する必要があります。

3.5. 検証

このライブラリは、エンティティ内のアノテーションに直接基づく JSR 303 検証をサポートしています。もちろん、サービス層にあらゆる種類の検証を追加できますが、この方法では実際のエンティティにうまく結合されます。

これを機能させるには、2 つの追加の依存関係を含める必要があります。JSR 303 とそれを実装するライブラリ (Hibernate でサポートされているものなど):

例 15: 検証の依存関係
<dependency>
  <groupId>javax.validation</groupId>
  <artifactId>validation-api</artifactId>
</dependency>
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
</dependency>

次に、構成に 2 つの Bean を追加する必要があります。

例 16: 検証 Bean
@Bean
public LocalValidatorFactoryBean validator() {
    return new LocalValidatorFactoryBean();
}

@Bean
public ValidatingCouchbaseEventListener validationEventListener() {
    return new ValidatingCouchbaseEventListener(validator());
}

JSR303 アノテーションを使用してフィールドにアノテーションを付けることができるようになりました。save() の検証が失敗すると、ConstraintViolationException がスローされます。

例 17: サンプル検証アノテーション
@Size(min = 10)
@Field
private String name;

3.6. 監査

エンティティは、Spring Data 監査メカニズムを通じて自動的に監査できます (どのユーザーがオブジェクトを作成し、オブジェクトを更新したか、その時刻を追跡)。

まず、@Version アノテーション付きフィールドを持つエンティティのみが作成を監査できることに注意してください (そうでない場合、フレームワークは作成を更新として解釈します)。

監査は、フィールドに @CreatedBy@CreatedDate@LastModifiedBy@LastModifiedDate のアノテーションを付けることで機能します。フレームワークは、エンティティを永続化するときに、これらのフィールドに正しい値を自動的に挿入します。xxxDate アノテーションは Date フィールド (または互換性のある、たとえば jodatime クラス) に配置する必要がありますが、xxxBy アノテーションは任意のクラス T のフィールドに配置できます (ただし、両方のフィールドは同じ型である必要があります)。

監査を構成するには、まずコンテキスト内に監査対応の Bean が必要です。この Bean は AuditorAware<T> 型である必要があります (これにより、前述の T 型の xxxBy フィールドに格納できる値を生成できます)。次に、@EnableCouchbaseAuditing アノテーションを使用して、@Configuration クラスで監査を有効にする必要があります。

次に例を示します。

例 18: 監査エンティティのサンプル
@Document
public class AuditedItem {

  @Id
  private final String id;

  private String value;

  @CreatedBy
  private String creator;

  @LastModifiedBy
  private String lastModifiedBy;

  @LastModifiedDate
  private Date lastModification;

  @CreatedDate
  private Date creationDate;

  @Version
  private long version;

  //..omitted constructor/getters/setters/...
}

@CreatedBy と @LastModifiedBy は両方とも String フィールドに配置されているため、AuditorAware は String と連携する必要があることに注意してください。

例 19: サンプル AuditorAware 実装
public class NaiveAuditorAware implements AuditorAware<String> {

  private String auditor = "auditor";

  @Override
  public String getCurrentAuditor() {
    return auditor;
  }

  public void setAuditor(String auditor) {
    this.auditor = auditor;
  }
}

これらすべてを結び付けるために、AuditorAware Bean の宣言と監査のアクティブ化の両方に java 構成を使用します。

例 20: 監査構成のサンプル
@Configuration
@EnableCouchbaseAuditing //this activates auditing
public class AuditConfiguration extends AbstractCouchbaseConfiguration {

    //... a few abstract methods omitted here

    // this creates the auditor aware bean that will feed the annotations
    @Bean
    public NaiveAuditorAware testAuditorAware() {
      return new NaiveAuditorAware();
    }

4. キーの自動生成

この章では、組み込みメカニズムを使用して couchbase ドキュメントキーを自動生成する方法について説明します。サポートされている自動生成戦略は 2 種類あります。

couchbase でサポートされるキーの最大長は 250 バイトです。

4.1. 構成

自動生成されるキーには @GeneratedValue のアノテーションを付ける必要があります。デフォルトの戦略は USE_ATTRIBUTES です。キーのプレフィックスとサフィックスはエンティティ自体の一部として提供できます。これらの値は永続化されず、キーの生成にのみ使用されます。プレフィックスとサフィックスは、order 値を使用して順序付けされます。デフォルトの順序は 0 で、順序のない複数のプレフィックスは前のプレフィックスを上書きします。id の値がすでに使用可能な場合、自動生成はスキップされます。連結の区切り文字は delimiter を使用して指定できます。デフォルトの区切り文字は . です。

例 21: GeneratedValue のアノテーション
@Document
public class User {
     @Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = ".")
     private String id;
     @IdPrefix(order=0)
     private String userPrefix;
     @IdSuffix(order=0)
     private String userSuffix;
     ...
}

4.2. 属性を使用した鍵の生成

ドキュメント属性の組み合わせを使用してキーを生成するのが一般的です。属性を使用したキー生成では、プレフィックスやサフィックスと同様に提供された順序に基づいて、IdAttribute でアノテーションが付けられたすべての属性値が連結されます。

例 22: IdAttribute のアノテーション
@Document
public class User {
     @Id @GeneratedValue(strategy = USE_ATTRIBUTES)
     private String id;
     @IdAttribute
     private String userid;
     ...
}

4.3. uuid を使用したキーの生成

この自動生成では、UUID ランダムジェネレーターを使用して、16 バイトのキースペースを消費するドキュメントキーを生成します。このメカニズムは、テストスキャフォールディングにのみ推奨されます。

例 23: 一意のキー生成のためのアノテーション
@Document
public class User {
     @Id @GeneratedValue(strategy = UNIQUE)
     private String id;
     ...
}

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

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

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

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

5.1. コアコンセプト

Spring Data リポジトリ抽象化の中心的なインターフェースは Repository です。管理するドメインクラスと、ドメインクラスの識別子の型を型引数として取ります。このインターフェースは主に、操作する型をキャプチャーし、このインターフェースを継承するインターフェースを検出するのに役立つマーカーインターフェースとして機能します。CrudRepository (Javadoc) および ListCrudRepository (Javadoc) インターフェースは、管理されているエンティティクラスに高度な CRUD 機能を提供します。

例 24: CrudRepository インターフェース
public interface CrudRepository<T, ID> extends Repository<T, ID> {

  <S extends T> S save(S entity);      (1)

  Optional<T> findById(ID primaryKey); (2)

  Iterable<T> findAll();               (3)

  long count();                        (4)

  void delete(T entity);               (5)

  boolean existsById(ID primaryKey);   (6)

  // … more functionality omitted.
}
1 指定されたエンティティを保存します。
2 指定された ID で識別されるエンティティを返します。
3 すべてのエンティティを返します。
4 エンティティの数を返します。
5 指定されたエンティティを削除します。
6 指定された ID のエンティティが存在するかどうかを示します。

このインターフェースで宣言されたメソッドは、一般に CRUD メソッドと呼ばれます。ListCrudRepository は同等のメソッドを提供しますが、CrudRepository メソッドが Iterable を返すのに対し、それらは List を返します。

また、JpaRepository や MongoRepository などの永続化技術固有の抽象化も提供します。これらのインターフェースは CrudRepository を継承し、CrudRepository などのかなり汎用的な永続化テクノロジーにとらわれないインターフェースに加えて、基礎となる永続化テクノロジーの機能を公開します。

CrudRepository に加えて、エンティティへのページ指定されたアクセスを容易にする追加のメソッドを追加する PagingAndSortingRepository (Javadoc) 抽象化があります。

例 25: PagingAndSortingRepository インターフェース
public interface PagingAndSortingRepository<T, ID>  {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

20 のページサイズで User の 2 番目のページにアクセスするには、次のようなことができます。

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));

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

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

  long countByLastname(String lastname);
}

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

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

  long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);
}

5.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 をセットアップして、これらのインターフェースのプロキシインスタンスを作成します。

    Java
    import org.springframework.data.….repository.config.EnableJpaRepositories;
    
    @EnableJpaRepositories
    class Config { … }
    
    XML
    <?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">
    
       <repositories base-package="com.acme.repositories"/>
    
    </beans>

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

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

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

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

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

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

リポジトリインターフェースを定義するには、最初にドメインクラス固有のリポジトリインターフェースを定義する必要があります。インターフェースは Repository を継承し、ドメインクラスと ID 型に入力する必要があります。そのドメイン型の CRUD メソッドを公開する場合は、CrudRepository、または Repository の代わりにそのバリアントの 1 つを継承できます。

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

リポジトリインターフェースを使い始める方法にはいくつかのバリエーションがあります。

典型的なアプローチは、CrudRepository を継承することです。これにより、CRUD 機能のメソッドが提供されます。CRUD は、Create、Read、Update、Delete の略です。バージョン 3.0 では、CrudRepository と非常によく似た ListCrudRepository も導入されましたが、複数のエンティティを返すメソッドでは、使いやすい Iterable ではなく List が返されます。

リアクティブストアを使用している場合は、使用しているリアクティブフレームワークに応じて ReactiveCrudRepository または RxJava3CrudRepository を選択できます。

Kotlin を使用している場合は、Kotlin のコルーチンを利用する CoroutineCrudRepository を選択できます。

さらに、Sort 抽象化、または最初の場合は Pageable 抽象化を指定できるメソッドが必要な場合は、PagingAndSortingRepositoryReactiveSortingRepositoryRxJava3SortingRepository または CoroutineSortingRepository を継承できます。さまざまな並べ替えリポジトリは、3.0 より前の Spring Data バージョンのようにそれぞれの CRUD リポジトリを継承しなくなったことに注意してください。両方の機能が必要な場合は、両方のインターフェースを継承する必要があります。

Spring Data インターフェースを継承したくない場合は、リポジトリインターフェースに @RepositoryDefinition でアノテーションを付けることもできます。CRUD リポジトリインターフェースの 1 つを継承すると、エンティティを操作するためのメソッドの完全なセットが公開されます。公開するメソッドを選択したい場合は、公開するメソッドを CRUD リポジトリからドメインリポジトリにコピーします。その際、メソッドの戻り値の型を変更できます。Spring Data は、可能であれば戻り値の型を尊重します。例: 複数のエンティティを返すメソッドの場合、Iterable<T>List<T>Collection<T> または VAVR リストを選択できます。

アプリケーション内の多くのリポジトリに同じメソッドのセットが必要な場合は、継承元の独自のベースインターフェースを定義できます。このようなインターフェースには、@NoRepositoryBean のアノテーションを付ける必要があります。これにより、Spring Data はインスタンスを直接作成しようとして失敗し、そのリポジトリのエンティティを特定できないために失敗します。これは、ジェネリクス型変数がまだ含まれているためです。

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

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

5.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)を使用するリポジトリを示しています。

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

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

例 30: ジェネリクスインターフェースを使用したリポジトリ定義
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 にバインドする必要があるかを区別できません。

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

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

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

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

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

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

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

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

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

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

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

5.4.1. クエリ検索戦略

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

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

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

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

5.4.2. クエリ作成

Spring Data リポジトリインフラストラクチャに組み込まれているクエリビルダーメカニズムは、リポジトリのエンティティに対して制約クエリを構築できます。

次の例は、いくつかのクエリを作成する方法を示しています。

例 34: メソッド名からのクエリ作成
interface PersonRepository extends Repository<Person, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

クエリメソッド名の解析は、主語と述語に分けられます。最初の部分(find … Byexists … By)はクエリのサブジェクトを定義し、2 番目の部分は述語を形成します。導入句(主語)には、さらに式を含めることができます。find (または他の導入キーワード)と By の間のテキストは、Distinct などの結果を制限するキーワードの 1 つを使用して、作成するクエリまたは Top/First は、クエリ結果を制限しますに個別のフラグを設定しない限り、説明的であると見なされます。

付録には、クエリメソッドのサブジェクトキーワードとクエリメソッドの述語キーワードの完全なリストが含まれています。これには、並べ替えや大文字小文字の修飾子が含まれます。ただし、最初の By は、実際の条件述語の開始を示す区切り文字として機能します。非常に基本的なレベルでは、エンティティプロパティの条件を定義し、And および Or と連結できます。

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

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

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

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

5.4.3. プロパティ式

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

List<Person> findByAddressZipCode(ZipCode zipCode);

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

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

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

List<Person> findByAddress_ZipCode(ZipCode zipCode);

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

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

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

例 35: クエリメソッドでの PageableSliceSort の使用
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 つの式に集めることができます。

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

ソート式を定義するためのより型安全な方法については、ソート式を定義する型から始め、メソッド参照を使用してソートするプロパティを定義します。

例 37: 型安全 API を使用したソート式の定義
TypedSort<Person> person = Sort.sort(Person.class);

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

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

例 38: QuerydslAPI を使用したソート式の定義
QSort sort = QSort.by(QPerson.firstname.asc())
  .and(QSort.by(QPerson.lastname.desc()));

5.4.5. クエリ結果の制限

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

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

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

複数の結果を返すクエリメソッドは、標準の Java IterableListSet を使用できます。さらに、Spring Data の StreamableIterable のカスタム拡張、Vavr (英語) によって提供されるコレクション型を返すことをサポートします。考えられるすべてのクエリメソッドの戻り値の型について説明している付録を参照してください。

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

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

例 40: 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() { … }
}

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

  private final Streamable<Product> streamable;

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


  @Override
  public Iterator<Product> iterator() {                 (4)
    return streamable.iterator();
  }
}

interface ProductRepository implements Repository<Product, Long> {
  Products findAllByDescriptionContaining(String text); (5)
}
1 製品の価格にアクセスするための API を公開する Product エンティティ。
2Products.of(…) (Lombok アノテーションを使用して作成されたファクトリメソッド)を使用して構築できる Streamable<Product> のラッパー型。Streamable<Product> を使用する標準のコンストラクターも同様に機能します。
3 ラッパー型は追加の API を公開し、Streamable<Product> で新しい値を計算します。
4Streamable インターフェースを実装し、実際の結果に委譲します。
5 そのラッパー型 Products は、クエリメソッドの戻り値の型として直接使用できます。Streamable<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

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

5.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 メタアノテーションにより、ツールベンダー(IDEA (英語) Eclipse (英語) Kotlin (英語) など)は、Spring アノテーションのサポートをハードコードすることなく、一般的な方法で null-safety サポートを提供できます。クエリメソッドの null 可能性制約のランタイムチェックを有効にするには、次の例に示すように、package-info.java で Spring の @NonNullApi を使用して、パッケージレベルで非 null 可能性をアクティブ化する必要があります。

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

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

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

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

例 43: 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 を返します。

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

戻り値の型として Java 8 Stream<T> を使用することにより、クエリメソッドの結果を段階的に処理できます。次の例に示すように、クエリ結果を Stream でラップする代わりに、データストア固有のメソッドを使用してストリーミングを実行します。

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

5.4.9. 非同期クエリ結果

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

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

@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
1 戻り値の型として java.util.concurrent.Future を使用します。
2 戻り値の型として Java 8 java.util.concurrent.CompletableFuture を使用します。

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

このセクションでは、定義されたリポジトリインターフェースのインスタンスと Bean 定義を作成する方法について説明します。

5.5.1. Java 構成

Java 構成クラスでストア固有の @EnableJpaRepositories アノテーションを使用して、リポジトリのアクティブ化の構成を定義します。Spring コンテナーの Java ベースの構成の概要については、Spring リファレンスドキュメントの JavaConfig を参照してください。

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

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

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

5.5.2. XML 構成

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

例 47: 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">

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

</beans:beans>

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

5.5.3. フィルターの使用

デフォルトでは、インフラストラクチャは、構成された基本パッケージにある永続化テクノロジー固有の Repository サブインターフェースを継承するすべてのインターフェースを選択し、そのための Bean インスタンスを作成します。ただし、どのインターフェースに Bean インスタンスが作成されているかをより細かく制御したい場合があります。これを行うには、リポジトリ宣言内でフィルター要素を使用します。セマンティクスは、Spring のコンポーネントフィルターの要素とまったく同じです。詳細については、これらの要素の Spring リファレンスドキュメントを参照してください。

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

例 48: フィルターを使う
Java
@Configuration
@EnableJpaRepositories(basePackages = "com.acme.repositories",
    includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeRepository") },
    excludeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeOtherRepository") })
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}
XML
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
  <context:include-filter type="regex" expression=".*SomeOtherRepository" />
</repositories>

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

5.5.4. スタンドアロンの使用箇所

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

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

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

Spring Data は、コーディングをほとんど行わずにクエリメソッドを作成するためのさまざまなオプションを提供します。ただし、これらのオプションがニーズに合わない場合は、リポジトリメソッドの独自のカスタム実装を提供することもできます。このセクションでは、その方法について説明します。

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

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

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

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

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

次に、次のように、リポジトリインターフェースにフラグメントインターフェースを継承させることができます。

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

  // Declare query methods here
}

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

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

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

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

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

  // Declare query methods here
}

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

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

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

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

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

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

リポジトリインフラストラクチャは、リポジトリが見つかったパッケージのクラスをスキャンして、カスタム実装フラグメントを自動検出しようとします。これらのクラスは、デフォルトで Impl になる接尾辞を追加するという命名規則に従う必要があります。

次の例は、デフォルトの接尾辞を使用するリポジトリと、接尾辞のカスタム値を設定するリポジトリを示しています。

例 57: 構成例
Java
@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
XML
<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 に一致します。

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

例 59: カスタム実装の手動接続
Java
class MyClass {
  MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {
    …
  }
}
XML
<repositories base-package="com.acme.repository" />

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

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

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

例 60: カスタムリポジトリベースクラス
class MyRepositoryImpl<T, ID>
  extends SimpleJpaRepository<T, ID> {

  private final EntityManager entityManager;

  MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);

    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }

  @Transactional
  public <S extends T> S save(S entity) {
    // implementation goes here
  }
}
このクラスには、ストア固有のリポジトリファクトリ実装が使用するスーパークラスのコンストラクターが必要です。リポジトリの基本クラスに複数のコンストラクターがある場合は、EntityInformation とストア固有のインフラストラクチャオブジェクト(EntityManager またはテンプレートクラスなど)を取得するコンストラクターをオーバーライドします。

最後のステップは、カスタマイズされたリポジトリ基本クラスを Spring Data インフラストラクチャに認識させることです。構成では、次の例に示すように、repositoryBaseClass を使用してこれを行うことができます。

例 61: カスタムリポジトリ基本クラスの構成
Java
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML
<repositories base-package="com.acme.repository"
     base-class="….MyRepositoryImpl" />

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

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

例 62: 集約ルートからのドメインイベントの公開
class AnAggregateRoot {

    @DomainEvents (1)
    Collection<Object> domainEvents() {
        // … return events you want to get published here
    }

    @AfterDomainEventPublication (2)
    void callbackMethod() {
       // … potentially clean up domain events list
    }
}
1@DomainEvents を使用するメソッドは、単一のイベントインスタンスまたはイベントのコレクションのいずれかを返すことができます。引数を取ってはいけません。
2 すべてのイベントが公開された後、@AfterDomainEventPublication でアノテーションが付けられたメソッドがあります。これを使用して、公開するイベントのリストを(他の用途の中でも)潜在的にクリーンアップできます。

メソッドは、Spring Data リポジトリの save(…)saveAll(…)delete(…) または deleteAll(…) メソッドのいずれかが呼び出されるたびに呼び出されます。

5.8. Spring Data 拡張

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

5.8.1. Querydsl 拡張

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

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

例 63: 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 を継承します。

例 64: リポジトリでの 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);

5.8.2. Web サポート

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

例 65: Spring Data Web サポートの有効化
Java
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
XML
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />

@EnableSpringDataWebSupport アノテーションは、いくつかのコンポーネントを登録します。これらについては、このセクションの後半で説明します。また、クラスパスで Spring HATEOAS を検出し、統合コンポーネント(存在する場合)も登録します。

基本的な Web サポート
XML で Spring Data Web サポートを有効にする

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

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

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

  • Jackson モジュールは、使用する Spring Data モジュールに応じて、Point や Distance などの型を逆 / 直列化するか、特定の型を格納します。

DomainClassConverter クラスの使用

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

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

例 67: コントローラーメソッドの引数として 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 インスタンスを派生させようとします。

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

page

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

size

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

sort

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

この動作をカスタマイズするには、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_pagething2_page などを設定する必要があります。

メソッドに渡されるデフォルトの Pageable は PageRequest.of(0, 20) と同等ですが、Pageable パラメーターの @PageableDefault アノテーションを使用してカスタマイズできます。

Pageable のハイパーメディアサポート

Spring HATEOAS には、表現モデルクラス(PagedResources)が付属しています。これにより、Page インスタンスのコンテンツを、必要な Page メタデータと、クライアントがページを簡単にナビゲートできるようにするリンクで強化できます。Page から PagedResources への変換は、PagedResourcesAssembler と呼ばれる Spring HATEOAS ResourceAssembler インターフェースの実装によって行われます。次の例は、PagedResourcesAssembler をコントローラーメソッドの引数として使用する方法を示しています。

例 68: コントローラーメソッドの引数として PagedResourcesAssembler を使用する
@Controller
class PersonController {

  @Autowired PersonRepository repository;

  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable,
    PagedResourcesAssembler assembler) {

    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

前の例に示すように、構成を有効にすると、PagedResourcesAssembler をコントローラーメソッドの引数として使用できます。その上で toResources(…) を呼び出すと、次の効果があります。

  • Page のコンテンツは、PagedResources インスタンスのコンテンツになります。

  • PagedResources オブジェクトは PageMetadata インスタンスをアタッチし、Page および基礎となる PageRequest からの情報が取り込まれます。

  • PagedResources には、ページの状態に応じて、prev および next リンクが添付される場合があります。リンクは、メソッドがマップする URI を指します。メソッドに追加されたページネーションパラメーターは、PageableHandlerMethodArgumentResolver の設定と一致して、リンクを後で解決できるようにします。

データベースに 30 個の Person インスタンスがあると仮定します。これで、リクエスト(GET http://localhost:8080/persons)をトリガーして、次のような出力を確認できます。

{ "links" : [ { "rel" : "next",
                "href" : "http://localhost:8080/persons?page=1&size=20" }
  ],
  "content" : [
     … // 20 Person instances rendered here
  ],
  "pageMetadata" : {
    "size" : 20,
    "totalElements" : 30,
    "totalPages" : 2,
    "number" : 0
  }
}

アセンブラーは正しい URI を生成し、デフォルト構成を選択して、パラメーターを次のリクエストの Pageable に解決しました。つまり、その構成を変更すると、リンクは自動的に変更に準拠します。デフォルトでは、アセンブラーはそれが呼び出されたコントローラーメソッドを指しますが、ページネーションリンクを構築するためのベースとして使用されるカスタム Link を渡すことにより、それをカスタマイズできます。これにより、PagedResourcesAssembler.toResource(…) メソッドがオーバーロードされます。

Spring Data Jackson モジュール

コアモジュール、および一部のストア固有のモジュールには、Spring Data ドメインで使用される org.springframework.data.geo.Distance や org.springframework.data.geo.Point などの型の Jackson モジュールのセットが付属しています。
これらのモジュールは、Web サポートが有効になり、com.fasterxml.jackson.databind.ObjectMapper が使用可能になるとインポートされます。

初期化中に、SpringDataJacksonConfiguration と同様に SpringDataJacksonModules がインフラストラクチャによって取得されるため、宣言された com.fasterxml.jackson.databind.Module が Jackson ObjectMapper で使用できるようになります。

次のドメイン型のデータバインディングミックスインは、共通のインフラストラクチャによって登録されます。

org.springframework.data.geo.Distance
org.springframework.data.geo.Point
org.springframework.data.geo.Box
org.springframework.data.geo.Circle
org.springframework.data.geo.Polygon

個々のモジュールは、追加の SpringDataJacksonModules を提供する場合があります。
詳細については、ストア固有のセクションを参照してください。

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

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

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

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

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

前の例に示されている型は、Spring MVC ハンドラーメソッドの引数として使用するか、RestTemplate のいずれかのメソッドで ParameterizedTypeReference を使用することで使用できます。上記のメソッド宣言は、指定されたドキュメント内の任意の場所で firstname を見つけようとします。lastname XML ルックアップは、受信ドキュメントのトップレベルで実行されます。の JSON バリアントは、トップレベルの lastname を最初に試行しますが、前者が値を返さない場合は、user サブドキュメントにネストされた lastname も試行します。こうすることで、クライアントが公開メソッドを呼び出さなくても、ソースドキュメントの構造の変更を簡単に軽減できます (通常、クラスベースのペイロードバインディングの欠点です)。

ネストされた射影は、[ 射影 ] に従ってサポートされます。メソッドがインターフェース以外の複雑な型を返す場合、Jackson ObjectMapper が最終値のマッピングに使用されます。

Spring MVC の場合、@EnableSpringDataWebSupport がアクティブになり、必要な依存関係がクラスパスで使用可能になるとすぐに、必要なコンバーターが自動的に登録されます。RestTemplate で使用する場合は、手動で ProjectingJackson2HttpMessageConverter (JSON) または XmlBeamHttpMessageConverter を登録します。

詳細については、標準の Spring Data サンプルリポジトリ [GitHub] (英語) Web 射影の例 [GitHub] (英語) を参照してください。

Querydsl Web サポート

QueryDSL (英語) が統合されているストアの場合、Request クエリ文字列に含まれている属性からクエリを派生させることができます。

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

?firstname=Dave&lastname=Matthews

前の例の User オブジェクトが与えられた場合、次のように QuerydslPredicateArgumentResolver を使用して、クエリ文字列を次の値に解決できます。

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
Querydsl がクラスパスで見つかると、この機能は @EnableSpringDataWebSupport とともに自動的に有効になります。

メソッドシグネチャーに @QuerydslPredicate を追加すると、すぐに使用できる Predicate が提供されます。これは、QuerydslPredicateExecutor を使用して実行できます。

型情報は通常、メソッドの戻り値型から解決されます。その情報は必ずしもドメイン型と一致しないため、QuerydslPredicate の root 属性を使用することをお勧めします。

次の例は、メソッドシグネチャーで @QuerydslPredicate を使用する方法を示しています。

@Controller
class UserController {

  @Autowired UserRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,    (1)
          Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

    model.addAttribute("users", repository.findAll(predicate, pageable));

    return "index";
  }
}
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 解決から除外します。
リポジトリまたは @QuerydslPredicate から特定のバインディングを適用する前に、デフォルトの Querydsl バインディングを保持する QuerydslBinderCustomizerDefaults Bean を登録できます。

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

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

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

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

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

例 71: 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 を使用してリポジトリポピュレータをアンマーシャルする方法を示しています。

例 72: 非整列化リポジトリポピュレーターの宣言 (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>

6. Couchbase リポジトリ

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

デフォルトでは、操作が単一ドキュメント操作であり、ID がわかっている場合、操作はキー / 値によってサポートされます。他のすべての操作では、デフォルトで N1QL クエリが生成されるため、データアクセスのパフォーマンスを高めるために適切なインデックスを作成する必要があります。

クエリに必要な一貫性を調整したり ( 一貫性のあるクエリを参照)、異なるバケットでバックアップされた異なるリポジトリを使用したりできることに注意してください。([couchbase.repository.multibucket] を参照)

6.1. 構成

リポジトリのサポートは常に存在しますが、一般的に、または特定の名前空間に対して有効にする必要があります。AbstractCouchbaseConfiguration を継承する場合は、@EnableCouchbaseRepositories アノテーションを使用するだけです。検索パスを絞り込んだりカスタマイズしたりするためのオプションが多数用意されており、最も一般的なオプションの 1 つは basePackages です。

また、spring boot 内で実行している場合は、autoconfig サポートによってすでにアノテーションが設定されているため、デフォルトをオーバーライドする場合にのみアノテーションを使用する必要があることに注意してください。

例 73: アノテーションベースのリポジトリのセットアップ
@Configuration
@EnableCouchbaseRepositories(basePackages = {"com.couchbase.example.repos"})
public class Config extends AbstractCouchbaseConfiguration {
    //...
}

高度な使用箇所については、[couchbase.repository.multibucket] で説明されています。

6.2. 使用方法

最も単純なケースでは、リポジトリは CrudRepository<T, String> を継承します。ここで、T は公開するエンティティです。UserInfo のリポジトリを見てみましょう。

例 74: UserInfo リポジトリ
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<UserInfo, String> {
}

これは単なるインターフェースであり、実際のクラスではないことに注意してください。コンテキストが初期化されると、バックグラウンドでリポジトリ記述の実際の実装が作成され、通常の Bean を介してそれらにアクセスできるようになります。これは、完全な CRUD セマンティクスをサービス層とアプリケーションに公開しながら、多くの定型コードを節約できることを意味します。

ここで、@Autowire を UserRepository を使用するクラスに送信すると想像してみましょう。どのような方法があるのでしょうか ?

表 5: UserRepository で公開されたメソッド
メソッド 説明

UserInfo 保存 (UserInfo エンティティ)

指定されたエンティティを保存します。

Iterable<UserInfo> save(Iterable<UserInfo> エンティティ)

エンティティのリストを保存します。

UserInfo findOne(文字列 ID)

一意の ID でエンティティを検索します。

ブール値が存在します。(文字列 ID)

指定されたエンティティがその一意の ID によって存在するかどうかを確認します。

Iterable<UserInfo> findAll()

バケット内のこの型のすべてのエンティティを検索します。

Iterable<UserInfo> findAll(Iterable<String> ID)

この型と指定された ID リストによってすべてのエンティティを検索します。

ロング count()

バケット内のエンティティの数を数えます。

void delete(文字列 ID)

ID に基づいてエンティティを削除します。

void 削除 (UserInfo エンティティ)

エンティティを削除します。

void delete(Iterable<UserInfo> エンティティ)

指定されたすべてのエンティティを削除します。

ボイド deleteAll()

バケット内の型ごとにすべてのエンティティを削除します。

それはすごいですね! インターフェースを定義するだけで、管理対象エンティティ上に完全な CRUD 機能が得られます。

公開されたメソッドは多種多様なアクセスパターンを提供しますが、多くの場合、カスタムパターンを定義する必要があります。これを行うには、インターフェースにメソッド宣言を追加します。次のセクションで説明するように、メソッド宣言はバックグラウンドでリクエストに対して自動的に解決されます。

6.3. リポジトリとクエリ

6.3.1. N1QL ベースのクエリ

前提条件は、エンティティが保存されるバケットに PRIMARY INDEX を作成していることです。

次に例を示します。

例 75: N1QL クエリを備えた拡張 UserInfo リポジトリ
public interface UserRepository extends CrudRepository<UserInfo, String> {

    @Query("#{#n1ql.selectEntity} WHERE role = 'admin' AND #{#n1ql.filter}")
    List<UserInfo> findAllAdmins();

    List<UserInfo> findByFirstname(String fname);
}

ここでは、N1QL に基づいた 2 つのクエリ方法を示します。

最初の方法では、Query アノテーションを使用して N1QL ステートメントをインラインで提供します。SpEL (Spring 式言語) は、#{ と } の間の周囲の SpEL 式ブロックによってサポートされます。いくつかの N1QL 固有の値が SpEL を通じて提供されます。

  • #n1ql.selectEntity を使用すると、完全なエンティティ (ドキュメント ID と CAS 値を含む) を構築するために必要なすべてのフィールドがステートメントで選択されることを簡単に確認できます。

  • WHERE 句の #n1ql.filter は、Spring Data が型情報を格納するために使用するフィールドとエンティティ型を一致させる条件を追加します。

  • #n1ql.bucket は、エンティティが保存されているバケットの名前に置き換えられ、バッククォートでエスケープされます。

  • #n1ql.scope は、エンティティが格納されているスコープの名前に置き換えられ、バッククォートでエスケープされます。

  • #n1ql.collection は、エンティティが格納されているコレクションの名前に置き換えられ、バッククォートでエスケープされます。

  • #n1ql.fields は、エンティティを再構築するために必要なフィールドのリスト (SELECT 句など) に置き換えられます。

  • #n1ql.delete は delete from ステートメントに置き換えられます。

  • #n1ql.returning は、エンティティの再構築に必要な return 句に置き換えられます。

常に selectEntity SpEL および filter SpEL とともに WHERE 句を使用することをお勧めします (そうしないと、クエリが他のリポジトリのエンティティの影響を受ける可能性があるため)。

文字列ベースのクエリはパラメーター化されたクエリをサポートします。"$1" のような位置プレースホルダーを使用することもできます。この場合、各メソッドパラメーターは順番に $1$2$3 にマップされます。あるいは、"$someString" 構文を使用して名前付きプレースホルダーを使用することもできます。メソッドのパラメーターは、パラメーターの名前を使用して対応するプレースホルダーと照合されます。このプレースホルダーは、各パラメーター ( Pageable または Sort を除く) に @Param (例: @Param("someString")) のアノテーションを付けることでオーバーライドできます。クエリ内で 2 つのアプローチを混合することはできず、混合すると IllegalArgumentException が発生します。

N1QL プレースホルダーと SpEL を混合できることに注意してください。N1QL プレースホルダーでは引き続きすべてのメソッドパラメーターが考慮されるため、次の例のように正しいインデックスを使用してください。

例 76: SpEL と N1QL プレースホルダーを混合したインラインクエリ
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2")
public List<User> findUsersByDynamicCriteria(String criteriaField, Object criteriaValue)

これにより、たとえば以下と同様に機能するクエリを生成できます。AND name = "someName" または AND age = 3. メソッド宣言が 1 つあります。

N1QL クエリで単一の射影を実行することもできます (フィールドを 1 つだけ選択し、結果を 1 つだけ返します (通常は COUNTAVGMAX などの集計))。このような射影は、longbooleanString のような単純な戻り値の型を持ちます。これは、DTO への射影を目的としたものではありません。

もう一つの例:
 #{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
と同等です
SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1

Spring Security による SpEL の実践

SpEL は、Spring Security などの他の Spring コンポーネントによって挿入されたデータに応じてクエリを実行する場合に役立ちます。SpEL コンテキストを継承して、このような外部データにアクセスするために必要なことは次のとおりです。

まず、EvaluationContextExtension を実装する必要があります (以下のようにサポートクラスを使用します)。

class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {

  @Override
  public String getExtensionId() {
    return "security";
  }

  @Override
  public SecurityExpressionRoot getRootObject() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    return new SecurityExpressionRoot(authentication) {};
  }
}

Spring Data Couchbase が関連する SpEL 値にアクセスできるようにするために必要なのは、構成内で対応する Bean を宣言することだけです。

@Bean
EvaluationContextExtension securityExtension() {
    return new SecurityEvaluationContextExtension();
}

これは、接続されているユーザーのロールに応じてクエリを作成する場合に役立ちます。たとえば、次のようになります。

@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND " +
"role = '?#{hasRole('ROLE_ADMIN') ? 'public_admin' : 'admin'}'")
List<UserInfo> findAllAdmins(); //only ROLE_ADMIN users will see hidden admins

削除クエリの例:

@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND " +
"username = $1 #{#n1ql.returning}")
UserInfo removeUser(String username);

2 番目の方法では、Spring-Data のクエリ導出メカニズムを使用して、メソッド名とパラメーターから N1QL クエリを構築します。これにより、SELECT …​ FROM …​ WHERE firstName = "valueOfFnameAtRuntime" のようなクエリが生成されます。これらの条件を組み合わせて、countByFirstname のような名前でカウントを実行したり、findFirst3ByLastname のような名前で制限を実行したりすることもできます。

実際には、生成された N1QL クエリには、リポジトリのエンティティクラスに一致するドキュメントのみを選択するための追加の N1QL 条件も含まれています。

ほとんどの Spring-Data キーワードがサポートされています: @Query (N1QL) メソッド名内のサポートされるキーワード

キーワード サンプル N1QL WHERE 句のスニペット

And

findByLastnameAndFirstname

lastName = a AND firstName = b

Or

findByLastnameOrFirstname

lastName = a OR firstName = b

Is,Equals

findByField,findByFieldEquals

field = a

IsNot,Not

findByFieldIsNot

field != a

Between

findByFieldBetween

field BETWEEN a AND b

IsLessThan,LessThan,IsBefore,Before

findByFieldIsLessThan,findByFieldBefore

field < a

IsLessThanEqual,LessThanEqual

findByFieldIsLessThanEqual

field ⇐ a

IsGreaterThan,GreaterThan,IsAfter,After

findByFieldIsGreaterThan,findByFieldAfter

field > a

IsGreaterThanEqual,GreaterThanEqual

findByFieldGreaterThanEqual

field >= a

IsNull

findByFieldIsNull

field IS NULL

IsNotNull,NotNull

findByFieldIsNotNull

field IS NOT NULL

IsLike,Like

findByFieldLike

field LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)

IsNotLike,NotLike

findByFieldNotLike

field NOT LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)

IsStartingWith,StartingWith,StartsWith

findByFieldStartingWith

field LIKE "a%" - a should be a String prefix

IsEndingWith,EndingWith,EndsWith

findByFieldEndingWith

field LIKE "%a" - a should be a String suffix

IsContaining,Containing,Contains

findByFieldContains

field LIKE "%a%" - a should be a String

IsNotContaining,NotContaining,NotContains

findByFieldNotContaining

field NOT LIKE "%a%" - a should be a String

IsIn,In

findByFieldIn

field IN array - note that the next parameter value (or its children if a collection/array) should be compatible for storage in a JsonArray)

IsNotIn,NotIn

findByFieldNotIn

field NOT IN array - note that the next parameter value (or its children if a collection/array) should be compatible for storage in a JsonArray)

IsTrue,True

findByFieldIsTrue

field = TRUE

IsFalse,False

findByFieldFalse

field = FALSE

MatchesRegex,Matches,Regex

findByFieldMatches

REGEXP_LIKE(field, "a") - note that the ignoreCase is ignored here, a is a regular expression in String form

Exists

findByFieldExists

field IS NOT MISSING - used to verify that the JSON contains this attribute

OrderBy

findByFieldOrderByLastnameDesc

field = a ORDER BY lastname DESC

IgnoreCase

findByFieldIgnoreCase

LOWER(field) = LOWER("a") - a must be a String

You can use both counting queries and Limiting Query Results features with this approach.

With N1QL, another possible interface for the repository is the PagingAndSortingRepository one (which extends CrudRepository). It adds two methods:

Table 6. Exposed methods on the PagingAndSortingRepository
Method Description

Iterable<T> findAll(Sort sort);

Allows to retrieve all relevant entities while sorting on one of their attributes.

Page<T> findAll(Pageable pageable);

Allows to retrieve your entities in pages. The returned Page allows to easily get the next page’s Pageable as well as the list of items. For the first call, use new PageRequest(0, pageSize) as Pageable.

You can also use Page and Slice as method return types as well with a N1QL backed repository.
If pageable and sort parameters are used with inline queries, there should not be any order by, limit or offset clause in the inline query itself otherwise the server would reject the query as malformed.

6.3.2. Automatic Index Management

By default, it is expected that the user creates and manages optimal indexes for their queries. Especially in the early stages of development, it can come in handy to automatically create indexes to get going quickly.

For N1QL, the following annotations are provided which need to be attached to the entity (either on the class or the field):

  • @QueryIndexed: Placed on a field to signal that this field should be part of the index

  • @CompositeQueryIndex: Placed on the class to signal that an index on more than one field (composite) should be created.

  • @CompositeQueryIndexes: If more than one CompositeQueryIndex should be created, this annotation will take a list of them.

For example, this is how you define a composite index on an entity:

Example 77. Composite index on two fields with ordering
@Document
@CompositeQueryIndex(fields = {"id", "name desc"})
public class Airline {
   @Id
   String id;

	@QueryIndexed
	String name;

	@PersistenceConstructor
	public Airline(String id, String name) {
		this.id = id;
	}

	public String getId() {
		return id;
	}

	public String getName() {
		return name;
	}

}

By default, index creation is disabled. If you want to enable it you need to override it on the configuration:

Example 78. Enable auto index creation
@Override
protected boolean autoIndexCreation() {
 return true;
}

6.3.3. Querying with consistency

By default repository queries that use N1QL use the NOT_BOUNDED scan consistency. This means that results return quickly, but the data from the index may not yet contain data from previously written operations (called eventual consistency). If you need "ready your own write" semantics for a query, you need to use the @ScanConsistency annotation. Here is an example:

Example 79. Using a different scan consistency
@Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {

	@Override
	@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
	Iterable<Airport> findAll();

}

6.3.4. DTO Projections

Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons. In this section, you will learn how to define projections to serve up simplified and reduced views of resources.

Look at the following domain model:

@Entity
public class Person {

  @Id @GeneratedValue
  private Long id;
  private String firstName, lastName;

  @OneToOne
  private Address address;
  …
}

@Entity
public class Address {

  @Id @GeneratedValue
  private Long id;
  private String street, state, country;

  …
}

This Person has several attributes:

  • id is the primary key

  • firstName and lastName are data attributes

  • address is a link to another domain object

Now assume we create a corresponding repository as follows:

interface PersonRepository extends CrudRepository<Person, Long> {

  Person findPersonByFirstName(String firstName);
}

Spring Data will return the domain object including all of its attributes. There are two options just to retrieve the address attribute. One option is to define a repository for Address objects like this:

interface AddressRepository extends CrudRepository<Address, Long> {}

In this situation, using PersonRepository will still return the whole Person object. Using AddressRepository will return just the Address.

However, what if you do not want to expose address details at all? You can offer the consumer of your repository service an alternative by defining one or more projections.

Example 80. Simple Projection
interface NoAddresses {  (1)

  String getFirstName(); (2)

  String getLastName();  (3)
}

This projection has the following details:

1 A plain Java interface making it declarative.
2 Export the firstName.
3 Export the lastName.

The NoAddresses projection only has getters for firstName and lastName meaning that it will not serve up any address information. The query method definition returns in this case NoAdresses instead of Person.

interface PersonRepository extends CrudRepository<Person, Long> {

  NoAddresses findByFirstName(String firstName);
}

Projections declare a contract between the underlying type and the method signatures related to the exposed properties. Hence it is required to name getter methods according to the property name of the underlying type. If the underlying property is named firstName, then the getter method must be named getFirstName otherwise Spring Data is not able to look up the source property.

7. Reactive Couchbase repository

7.1. Introduction

This chapter describes the reactive repository support for couchbase. This builds on the core repository support explained in Couchbase repositories. So make sure you’ve got a sound understanding of the basic concepts explained there.

7.2. Reactive Composition Libraries

The Couchbase Java SDK 3.x moved from RxJava to Reactor, so it blends in very nicely with the reactive spring ecosystem.

Reactive Couchbase repositories provide project Reactor wrapper types and can be used by simply extending from one of the library-specific repository interfaces:

  • ReactiveCrudRepository

  • ReactiveSortingRepository

7.3. Usage

Let’s create a simple entity to start with:

Example 81. Sample Person entity
public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

A corresponding repository implementation may look like this:

Example 82. Basic repository interface to persist Person entities
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {

  Flux<Person> findByFirstname(String firstname);

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

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

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

For JavaConfig use the @EnableReactiveCouchbaseRepositories annotation. The annotation carries the very same attributes like the namespace element. If no base package is configured the infrastructure will scan the package of the annotated configuration class.

Also note that if you are using it in a spring boot setup you likely can omit the annotation since it is autoconfigured for you.

Example 83. JavaConfig for repositories
@Configuration
@EnableReactiveCouchbaseRepositories
class ApplicationConfig extends AbstractCouchbaseConfiguration {
	// ... (see configuration for details)
}

As our domain repository extends ReactiveSortingRepository it provides you with CRUD operations as well as methods for sorted access to the entities. Working with the repository instance is just a matter of dependency injecting it into a client.

Example 84. Sorted access to Person entities
public class PersonRepositoryTests {

    @Autowired
    ReactivePersonRepository repository;

    @Test
    public void sortsElementsCorrectly() {
      Flux<Person> persons = repository.findAll(Sort.by(new Order(ASC, "lastname")));
      assertNotNull(perons);
    }
}

7.4. Repositories and Querying

Spring Data’s Reactive Couchbase comes with full querying support already provided by the blocking Repositories and Querying

8. Template & direct operations

The template provides lower level access to the underlying database and also serves as the foundation for repositories. Any time a repository is too high-level for you needs chances are good that the templates will serve you well. Note that you can always drop into the SDK directly through the beans exposed on the AbstractCouchbaseConfiguration.

8.1. Supported operations

The template can be accessed through the couchbaseTemplate and reactiveCouchbaseTemplate beans out of your context. Once you’ve got a reference to it, you can run all kinds of operations against it. Other than through a repository, in a template you need to always specify the target entity type which you want to get converted.

The templates use a fluent-style API which allows you to chain in optional operators as needed. As an example, here is how you store a user and then find it again by its ID:

Example 85. Fluent template access
// Create an Entity
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");

// Upsert it
couchbaseTemplate.upsertById(User.class).one(user);

// Retrieve it again
User found = couchbaseTemplate.findById(User.class).one(user.getId());

If you wanted to use a custom (by default durability options from the @Document annotation will be used) durability requirement for the upsert operation you can chain it in:

Example 86. Upsert with durability
User modified = couchbaseTemplate
  .upsertById(User.class)
  .withDurability(DurabilityLevel.MAJORITY)
  .one(user);

In a similar fashion, you can perform a N1QL operation:

Example 87. N1QL query on the template
final List<User> foundUsers = couchbaseTemplate
  .findByQuery(User.class)
  .consistentWith(QueryScanConsistency.REQUEST_PLUS)
  .all();

8.2. Sub-Document Operations

Couchbase supports Sub-Document Operations (英語) . This section documents how to use it with Spring Data Couchbase.

Sub-Document operations may be quicker and more network-efficient than full-document operations such as upsert or replace because they only transmit the accessed sections of the document over the network.

Sub-Document operations are also atomic, in that if one Sub-Document mutation fails then all will, allowing safe modifications to documents with built-in concurrency control.

Currently Spring Data Couchbase supports only sub document mutations (remove, upsert, replace and insert).

Mutation operations modify one or more paths in the document. The simplest of these operations is upsert, which, similar to the fulldoc-level upsert, will either modify the value of an existing path or create it if it does not exist:

Following example will upsert the city field on the address of the user, without trasfering any additional user document data.

Example 88. MutateIn upsert on the template
User user = new User();
// id field on the base document id required
user.setId(ID);
user.setAddress(address);
couchbaseTemplate.mutateInById(User.class)
    .withUpsertPaths("address.city")
    .one(user);

8.2.1. Executing Multiple Sub-Document Operations

Multiple Sub-Document operations can be executed at once on the same document, allowing you to modify several Sub-Documents at once. When multiple operations are submitted within the context of a single mutateIn command, the server will execute all the operations with the same version of the document.

To execute several mutation operations the method chaining can be used.

Example 89. MutateIn Multiple Operations
couchbaseTemplate.mutateInById(User.class)
    .withInsertPaths("roles", "subuser.firstname")
    .withRemovePaths("address.city")
    .withUpsertPaths("firstname")
    .withReplacePaths("address.street")
    .one(user);

8.2.2. Concurrent Modifications

Concurrent Sub-Document operations on different parts of a document will not conflict so by default the CAS value will be not be supplied when executing the mutations. If CAS is required then it can be provided like this:

Example 90. MutateIn With CAS
User user = new User();
// id field on the base document id required
user.setId(ID);
// @Version field should have a value for CAS to be supplied
user.setVersion(cas);
user.setAddress(address);
couchbaseTemplate.mutateInById(User.class)
    .withUpsertPaths("address.city")
    .withCasProvided()
    .one(user);

9. Couchbase Transactions

Couchbase supports Distributed Transactions (英語) . This section documents how to use it with Spring Data Couchbase.

9.1. Requirements

  • Couchbase Server 6.6.1 or above.

  • Spring Data Couchbase 5.0.0-M5 or above.

  • NTP should be configured so nodes of the Couchbase cluster are in sync with time. The time being out of sync will not cause incorrect behavior, but can impact metadata cleanup.

  • The entity class must have an @Version Long property to hold the CAS value of the document.

9.2. Overview

The Spring Data Couchbase template operations insert, find, replace and delete and repository methods that use those calls can participate in a Couchbase Transaction. They can be executed in a transaction by using the @Transactional annotation, the CouchbaseTransactionalOperator, or in the lambda of a Couchbase Transaction.

9.3. Getting Started & Configuration

Couchbase Transactions are normally leveraged with a method annotated with @Transactional. The @Transactional operator is implemented with the CouchbaseTransactionManager which is supplied as a bean in the AbstractCouchbaseConfiguration. Couchbase Transactions can be used without defining a service class by using CouchbaseTransactionOperator which is also supplied as a bean in AbtractCouchbaseConfiguration. Couchbase Transactions can also be used directly using Spring Data Couchbase operations within a lambda Using Transactions (英語)

9.4. Transactions with @Transactional

@Transactional defines as transactional a method or all methods on a class.

When this annotation is declared at the class level, it applies as a default to all methods of the declaring class and its subclasses.

9.4.1. Attribute Semantics

In this release, the Couchbase Transactions ignores the rollback attributes. The transaction isolation level is read-committed;

Example 91. Transaction Configuration and Use by @Transactional
The Configuration
@Configuration
@EnableCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
@EnableReactiveCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
@EnableTransactionManagement (1)
static class Config extends AbstractCouchbaseConfiguration {

  // Usual Setup
  @Override public String getConnectionString() { /* ... */ }
  @Override public String getUserName() { /* ... */ }
  @Override public String getPassword() { /* ... */ }
  @Override public String getBucketName() { /* ... */ }

  // Customization of transaction behavior is via the configureEnvironment() method
  @Override protected void configureEnvironment(final Builder builder) {
    builder.transactionsConfig(
      TransactionsConfig.builder().timeout(Duration.ofSeconds(30)));
  }
}
The Transactional Service Class

Note that the body of @Transactional methods can be re-executed if the transaction fails. It is imperative that everthing in the method body be idempotent.

import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

final CouchbaseOperations personOperations;
final ReactiveCouchbaseOperations reactivePersonOperations;

@Service (2)
public class PersonService {

  final CouchbaseOperations operations;
  final ReactiveCouchbaseOperations reactiveOperations;

  public PersonService(CouchbaseOperations ops, ReactiveCouchbaseOperations reactiveOps) {
    operations = ops;
    reactiveOperations = reactiveOps;
  }

  // no annotation results in this method being executed not in a transaction
  public Person save(Person p) {
    return operations.save(p);
  }

  @Transactional
  public Person changeFirstName(String id, String newFirstName) {
    Person p = operations.findById(Person.class).one(id); (3)
    return operations.replaceById(Person.class).one(p.withFirstName(newFirstName);
  }

  @Transactional
  public Mono<Person> reactiveChangeFirstName(String id, String newFirstName) {
    return personOperationsRx.findById(Person.class).one(person.id())
        .flatMap(p -> personOperationsRx.replaceById(Person.class).one(p.withFirstName(newFirstName)));
  }

}
Using the @Transactional Service.
@Autowired PersonService personService; (4)

Person walterWhite = new Person( "Walter", "White");
Person p = personService.save(walterWhite); // this is not a transactional method
...
Person renamedPerson = personService.changeFirstName(walterWhite.getId(), "Ricky"); (5)

Functioning of the @Transactional method annotation requires

  1. the configuration class to be annotated with @EnableTransactionManagement;

  2. the service object with the annotated methods must be annotated with @Service;

  3. the body of the method is executed in a transaction.

  4. the service object with the annotated methods must be obtained via @Autowired.

  5. the call to the method must be made from a different class than service because calling an annotated method from the same class will not invoke the Method Interceptor that does the transaction processing.

9.5. Transactions with CouchbaseTransactionalOperator

CouchbaseTransactionalOperator can be used to construct a transaction in-line without creating a service class that uses @Transactional. CouchbaseTransactionalOperator is available as a bean and can be instantiated with @Autowired. If creating one explicitly, it must be created with CouchbaseTransactionalOperator.create(manager) (NOT TransactionalOperator.create(manager)).

Example 92. Transaction Access Using TransactionalOperator.execute()
@Autowired TransactionalOperator txOperator;
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;

Flux<Person> result = txOperator.execute((ctx) ->
  reactiveCouchbaseTemplate.findById(Person.class).one(person.id())
    .flatMap(p -> reactiveCouchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt")))
 );

9.6. Transactions Directly with the SDK

Spring Data Couchbase works seamlessly with the Couchbase Java SDK for transaction processing. Spring Data Couchbase operations that can be executed in a transaction will work directly within the lambda of a transactions().run() without involving any of the Spring Transactions mechanisms. This is the most straight-forward way to leverage Couchbase Transactions in Spring Data Couchbase.

Example 93. Transaction Access - Blocking
@Autowired CouchbaseTemplate couchbaseTemplate;

TransactionResult result = couchbaseTemplate.getCouchbaseClientFactory().getCluster().transactions().run(ctx -> {
  Person p = couchbaseTemplate.findById(Person.class).one(personId);
  couchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt"));
});
Example 94. Transaction Access - Reactive
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;

Mono<TransactionResult> result = reactiveCouchbaseTemplate.getCouchbaseClientFactory().getCluster().reactive().transactions()
  .run(ctx ->
    reactiveCouchbaseTemplate.findById(Person.class).one(personId)
      .flatMap(p -> reactiveCouchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt")))
  );

10. Collection Support

Couchbase supports Scopes and Collections (英語) . This section documents on how to use it with Spring Data Couchbase.

The try-cb-spring [GitHub] (英語) sample application is a working example of using Scopes and Collections in Spring Data Couchbase.

The 2021 Couchbase Connect presentation on Collections in Spring Data can be found at Presentation Only [YouTube] (英語) and Presentation with Slide Deck (英語)

10.1. Requirements

  • Couchbase Server 7.0 or above.

  • Spring Data Couchbase 4.3.1 or above.

10.2. Getting Started & Configuration

10.2.1. Scope and Collection Specification

There are several mechanisms of specifying scopes and collections, and these may be combined, or one mechanism may override another. First some definitions for scopes and collections. An unspecified scope indicates that the default scope is to be used, likewise, an unspecified collection indicates that the default collection is to be used. There are only three combinations of scopes and collections that are valid. (1) the default scope and the default collection; (2) the default scope and a non-default collection; and (3) a non-default scope and a non-default collection. It is not possible to have a non-default scope and a default collection as non-default scopes do not contain a default collections, neither can one be created.

A scope can be specified in the configuration:

@Configuration
static class Config extends AbstractCouchbaseConfiguration {

    // Usual Setup
    @Override public String getConnectionString() { /* ... */ }

    // optionally specify the scope in the Configuration
    @Override
    protected String getScopeName() {
        return "myScope"; // or a variable etc.;
    }

}

Scopes and Collections can be specified as annotations on entity classes and repositories:

@Document
@Scope("travel")
@Collection("airport")
public class Airport {...
@Scope("travel")
@Collection("airport")
public interface AirportRepository extends CouchbaseRepository<Airport, String> ...

Scopes and Collections can be specified on templates using the inScope(scopeName) and inCollection(collectionName) fluent APIs:

List<Airport> airports = template.findByQuery(Airport.class).inScope("archived").all()

Scopes and Collections can be specified on repositories that extend DynamicProxyable using the withScope(scopeName) and withCollection(collectionName) APIs:

public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository>{...}
...
List<Airport> airports = airportRepository.withScope("archived").findByName(iata);
The order of precedence is:
  1. inScope()/inCollection() of the template fluent api

  2. withScope()/withCollection() of the template/repository object

  3. annotation of the repository method

  4. annotation of the repository interface

  5. annotation of the entity object

  6. getScope() of the configuration

11. Couchbase Field Level Encrytpion

Couchbase supports Field Level Encryption (英語) . This section documents how to use it with Spring Data Couchbase.

11.1. Requirements

  • Spring Data Couchbase 5.0.0-RC1 or above.

11.2. Overview

Fields annotated with com.couchbase.client.java.encryption.annotation.Encrypted (@Encrypted) will be automatically encrypted on write and decrypted on read. Unencrypted fields can be migrated to encrypted by specifying @Encrypted(migration = Encrypted.Migration.FROM_UNENCRYPTED).

11.3. Getting Started & Configuration

11.3.1. Dependencies

Field Level Encryption is available with the dependency ( see Field Level Encryption (英語) )

    <groupId>com.couchbase.client</groupId>
    <artifactId>couchbase-encryption</artifactId>

HashiCorp Vault Transit integration requires Spring Vault

    <groupId>org.springframework.vault</groupId>
    <artifactId>spring-vault-core</artifactId>

11.3.2. Providing a CryptoManager

A CryptoManager needs to be provided by overriding the cryptoManager() method in AbstractCouchbaseConfiguration. This CryptoManager will be used by Spring Data Couchbase and also by Couchbase Java SDK direct calls made from a CouchbaseClientFactory.

@Override
protected CryptoManager cryptoManager() {
  KeyStore javaKeyStore = KeyStore.getInstance("MyKeyStoreType");
  FileInputStream fis = new java.io.FileInputStream("keyStoreName");
  char[] password = { 'a', 'b', 'c' };
  javaKeyStore.load(fis, password);
  Keyring keyring = new KeyStoreKeyring(javaKeyStore, keyName -> "swordfish");

  // AES-256 authenticated with HMAC SHA-512. Requires a 64-byte key.
  AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring).build();

  CryptoManager cryptoManager = DefaultCryptoManager.builder().decrypter(provider.decrypter())
    .defaultEncrypter(provider.encrypterForKey("myKey")).build();
  return cryptoManager;
}

11.3.3. Defining a Field as Encrypted.

  1. @Encrypted defines a field as encrypted.

  2. @Encrypted(migration = Encrypted.Migration.FROM_UNENCRYPTED) defines a field that may or may not be encrypted when read. It will be encrypted when written.

  3. @Encrypted(encrypter = "<encrypterAlias>") specifies the alias of the encrypter to use for encryption. Note this is not the algorithm, but the name specified when adding the encrypter to the CryptoManager.

11.3.4. Example

Example 95. AbstractCouchbaseConfiguration
@Configuration
@EnableCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
@EnableReactiveCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
static class Config extends AbstractCouchbaseConfiguration {

  // Usual Setup
  @Override public String getConnectionString() { /* ... */ }
  @Override public String getUserName() { /* ... */ }
  @Override public String getPassword() { /* ... */ }
  @Override public String getBucketName() { /* ... */ }

  /* provide a cryptoManager */
  @Override
  protected CryptoManager cryptoManager() {
    KeyStore javaKeyStore = KeyStore.getInstance("MyKeyStoreType");
    FileInputStream fis = new java.io.FileInputStream("keyStoreName");
    char[] password = { 'a', 'b', 'c' };
    javaKeyStore.load(fis, password);
    Keyring keyring = new KeyStoreKeyring(javaKeyStore, keyName -> "swordfish");

    // AES-256 authenticated with HMAC SHA-512. Requires a 64-byte key.
    AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring).build();

    CryptoManager cryptoManager = DefaultCryptoManager.builder().decrypter(provider.decrypter())
      .defaultEncrypter(provider.encrypterForKey("myKey")).build();
    return cryptoManager;
  }

}
Example 96. The Annotation in the Document
@Document
public class AddressWithEncStreet extends Address {

    private @Encrypted String encStreet;
    .
    .
Example 97. Usage in Code
AddressWithEncStreet address = new AddressWithEncStreet(); // plaintext address with encrypted street
address.setCity("Santa Clara");
address.setEncStreet("Olcott Street");
addressEncryptedRepository.save(address);
Example 98. Resulting Document
{
  "_class": "AddressWithEncStreet",
   "city": "Santa Clara",
   "encrypted$encStreet": {
     "alg": "AEAD_AES_256_CBC_HMAC_SHA512",
     "ciphertext": "A/tJALmtixTxqj77ZUcUgMklIt3372DKD7l5FvbCzHNJMplbgQEv0RgSbxIfiRNr+uW2H7cokkcCW/F5YnQoXA==",
     "kid": "myKey"
   }
}

12. ANSI Joins

This chapter describes hows ANSI joins can be used across entities. Since 5.5 version, Couchbase server provides support for ANSI joins for joining documents using fields. Previous versions allowed index & lookup joins, which were supported in SDC only by querying directly through the SDK.

Relationships between entities across repositories can be one to one or one to many. By defining such relationships, a synchronized view of associated entities can be fetched.

12.1. Configuration

Associated entities can be fetched by annotating the entity’s property reference with @N1qlJoin. The prefix lks refers to left-hand side key space (current entity) and rks refers to the right-hand side key space (associated entity). The required element for @N1qlJoin annotation is the on clause, a boolean expression representing the join condition between the left-hand side (lks) and the right-hand side (rks), which can be fields, constant expressions or any complex N1QL expression. There could also be an optional where clause specified on the annotation for the join, similarly using lks to refer the current entity and rks to refer the associated entity.

Example 99. Annotation for ANSI Join
@Document
public class Author {
      @Id
      String id;

      String name;

      @N1qlJoin(on = "lks.name=rks.authorName")
      List<Book> books;

      @N1qlJoin(on = "lks.name=rks.name")
      Address address;
     ...
}

12.2. Lazy fetching

Associated entities can be lazily fetched upon the first access of the property, this could save on fetching more data than required when loading the entity. To load the associated entities lazily, @N1qlJoin annotation’s element fetchType has to be set to FetchType.LAZY. The default is FetchType.IMMEDIATE.

Example 100. Configuration for lazy fetch
@N1qlJoin(on = "lks.name=rks.authorName", fetchType = FetchType.LAZY)
List<Book> books;

12.3. ANSI Join Hints

12.3.1. Use Index Hint

index element on the @N1qlJoin can be used to provided the hint for the lks (current entity) index and rightIndex element can be used to provided the rks (associated entity) index.

12.3.2. Hash Join Hint

If the join type is going to be hash join, the hash side can be specified for the rks (associated entity). If the associated entity is on the build side, it can be specified as HashSide.BUILD else HashSide.PROBE.

12.3.3. Use Keys Hint

keys element on the @N1qlJoin annotation can be used to specify unique document keys to restrict the join key space.

13. Caching

This chapter describes additional support for caching and @Cacheable.

13.1. Configuration & Usage

Technically, caching is not part of spring-data, but is implemented directly in the spring core. Most database implementations in the spring-data package can’t support @Cacheable, because it is not possible to store arbitrary data.

Couchbase supports both binary and JSON data, so you can get both out of the same database.

To make it work, you need to add the @EnableCaching annotation and configure the cacheManager bean:

Example 101. AbstractCouchbaseConfiguration for Caching
@Configuration
@EnableCaching
public class Config extends AbstractCouchbaseConfiguration {
    // general methods

  @Bean
  public CouchbaseCacheManager cacheManager(CouchbaseTemplate couchbaseTemplate) throws Exception {
  CouchbaseCacheManager.CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.CouchbaseCacheManagerBuilder
      .fromConnectionFactory(couchbaseTemplate.getCouchbaseClientFactory());
    builder.withCacheConfiguration("mySpringCache", CouchbaseCacheConfiguration.defaultCacheConfig());
    return builder.build();
  }

The persistent identifier can then be used on the @Cacheable annotation to identify the cache manager to use (you can have more than one configured).

Once it is set up, you can annotate every method with the @Cacheable annotation to transparently cache it in your couchbase bucket. You can also customize how the key is generated.

Example 102. Caching example
@Cacheable(value="persistent", key="'longrunsim-'+#time")
public String simulateLongRun(long time) {
    try {
        Thread.sleep(time);
    } catch(Exception ex) {
        System.out.println("This shouldnt happen...");
    }
    return "I've slept " + time + " miliseconds.;
}

If you run the method multiple times, you’ll see a set operation happening first, followed by multiple get operations and no sleep time (which fakes the expensive execution). You can store whatever you want, if it is JSON of course you can access it through views and look at it in the Web UI.

Note that to use cache.clear() or cache.invalidate(), the bucket must have a primary key. :leveloffset: -1

14. Appendix

Appendix A: Namespace reference

The <repositories /> Element

The <repositories /> element triggers the setup of the Spring Data repository infrastructure. The most important attribute is base-package, which defines the package to scan for Spring Data repository interfaces. See “XML Configuration”. The following table describes the attributes of the <repositories /> element:

Table 7. Attributes
Name Description

base-package

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

repository-impl-postfix

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

query-lookup-strategy

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

named-queries-location

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

consider-nested-repositories

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

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

<populator/> 要素

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

表 8: 属性
名前 説明

locations

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

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

サポートされているクエリメソッドの件名キーワード

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

表 9: 件名のキーワードを照会する
キーワード 説明

find…By, read…By, get…By, query…By, search…By, stream…By

通常、リポジトリ型、Collection または Streamable サブ型、PageGeoResults などの結果ラッパーまたはその他のストア固有の結果ラッパーを返す一般的なクエリメソッド。findBy …findMyDomainTypeBy …  として、または追加のキーワードと組み合わせて使用できます。

exists…By

射影が存在し、通常は boolean の結果を返します。

count…By

数値結果を返す射影をカウントします。

delete…By, remove…By

結果なし(void)または削除カウントのいずれかを返すクエリメソッドを削除します。

…First<number>…, …Top<number>…

クエリ結果を結果の最初の <number> に制限します。このキーワードは、find (および他のキーワード)と by の間の件名の任意の場所で使用できます。

…Distinct…

一意のクエリを使用して、一意の結果のみを返します。その機能がサポートされているかどうかは、ストア固有のドキュメントを参照してください。このキーワードは、find (および他のキーワード)と by の間の件名の任意の場所で使用できます。

サポートされているクエリメソッドの述語キーワードと修飾子

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

表 10: 述語キーワードのクエリ
論理キーワード キーワード表現

AND

And

OR

Or

AFTER

After, IsAfter

BEFORE

Before, IsBefore

CONTAINING

Containing, IsContaining, Contains

BETWEEN

Between, IsBetween

ENDING_WITH

EndingWith, IsEndingWith, EndsWith

EXISTS

Exists

FALSE

False, IsFalse

GREATER_THAN

GreaterThan, IsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqual, IsGreaterThanEqual

IN

In, IsIn

IS

Is, Equals, (or no keyword)

IS_EMPTY

IsEmpty, Empty

IS_NOT_EMPTY

IsNotEmpty, NotEmpty

IS_NOT_NULL

NotNull, IsNotNull

IS_NULL

Null, IsNull

LESS_THAN

LessThan, IsLessThan

LESS_THAN_EQUAL

LessThanEqual, IsLessThanEqual

LIKE

Like, IsLike

NEAR

Near, IsNear

NOT

Not, IsNot

NOT_IN

NotIn, IsNotIn

NOT_LIKE

NotLike, IsNotLike

REGEX

Regex, MatchesRegex, Matches

STARTING_WITH

StartingWith, IsStartingWith, StartsWith

TRUE

True, IsTrue

WITHIN

Within, IsWithin

フィルター述語に加えて、次の修飾子のリストがサポートされています。

表 11: 述語修飾子キーワードのクエリ
キーワード 説明

IgnoreCase, IgnoringCase

大文字と小文字を区別しない比較のために、述語キーワードとともに使用されます。

AllIgnoreCase, AllIgnoringCase

すべての適切なプロパティの大文字と小文字を区別しません。クエリメソッド述語のどこかで使用されます。

OrderBy…

静的な並べ替え順序を指定し、その後にプロパティのパスと方向を指定します(例: OrderByFirstnameAscLastnameDesc)。

付録 D: リポジトリクエリの戻り値の型

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

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

地理空間型(GeoResultGeoResultsGeoPage など)は、地理空間クエリをサポートするデータストアでのみ使用できます。一部のストアモジュールは、独自の結果ラッパー型を定義する場合があります。
表 12: クエリの戻り型
戻りの型 説明

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 の非同期メソッド実行機能を有効にする必要があります。

Slice<T>

使用可能なデータがさらにあるかどうかを示すサイズのデータチャンク。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 構成を参照