Couchbase リポジトリ
Spring Data リポジトリの抽象化のゴールは、さまざまな永続ストアのデータアクセス層を実装するために必要なボイラープレートコードの量を大幅に削減することです。
デフォルトでは、操作が単一ドキュメント操作であり、ID がわかっている場合、操作はキー / 値によってサポートされます。他のすべての操作では、デフォルトで N1QL クエリが生成されるため、データアクセスのパフォーマンスを高めるために適切なインデックスを作成する必要があります。
クエリに必要な一貫性を調整したり ( 一貫性のあるクエリを参照)、異なるバケットでバックアップされた異なるリポジトリを使用したりできることに注意してください。([couchbase.repository.multibucket] を参照)
構成
リポジトリのサポートは常に存在しますが、一般的に、または特定の名前空間に対して有効にする必要があります。AbstractCouchbaseConfiguration
を継承する場合は、@EnableCouchbaseRepositories
アノテーションを使用するだけです。検索パスを絞り込んだりカスタマイズしたりするためのオプションが多数用意されており、最も一般的なオプションの 1 つは basePackages
です。
また、spring boot 内で実行している場合は、autoconfig サポートによってすでにアノテーションが設定されているため、デフォルトをオーバーライドする場合にのみアノテーションを使用する必要があることに注意してください。
@Configuration
@EnableCouchbaseRepositories(basePackages = {"com.couchbase.example.repos"})
public class Config extends AbstractCouchbaseConfiguration {
//...
}
高度な使用箇所については、[couchbase.repository.multibucket] で説明されています。
QueryDSL 構成
Spring Data Couchbase は、型安全なクエリを構築するための QueryDSL をサポートしています。コード生成を有効にするには、CouchbaseAnnotationProcessor
をアノテーションプロセッサーとして設定する必要があります。さらに、ランタイムでは、リポジトリで QueryDSL を有効にするために querydsl-apt が必要です。
. existing depdendencies including those required for spring-data-couchbase
.
.
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydslVersion}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>annotation-processing</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<proc>only</proc>
<annotationProcessors>
<annotationProcessor>org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor</annotationProcessor>
</annotationProcessors>
<generatedTestSourcesDirectory>target/generated-sources</generatedTestSourcesDirectory>
<compilerArgs>
<arg>-Aquerydsl.logInfo=true</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
dependencies {
annotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}'
annotationProcessor 'org.springframework.data:spring-data-couchbase'
testAnnotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}'
testAnnotationProcessor 'org.springframework.data:spring-data-couchbase'
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += [
"-processor",
"org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor"]
}
使用方法
最も単純なケースでは、リポジトリは CrudRepository<T, String>
を継承します。ここで、T は公開するエンティティです。UserInfo のリポジトリを見てみましょう。
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<UserInfo, String> {
}
これは単なるインターフェースであり、実際のクラスではないことに注意してください。コンテキストが初期化されると、バックグラウンドでリポジトリ記述の実際の実装が作成され、通常の Bean を介してそれらにアクセスできるようになります。これは、完全な CRUD セマンティクスをサービス層とアプリケーションに公開しながら、多くの定型コードを節約できることを意味します。
ここで、@Autowire
を 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 機能が得られます。
公開されたメソッドは多種多様なアクセスパターンを提供しますが、多くの場合、カスタムパターンを定義する必要があります。これを行うには、インターフェースにメソッド宣言を追加します。次のセクションで説明するように、メソッド宣言はバックグラウンドでリクエストに対して自動的に解決されます。
リポジトリとクエリ
N1QL ベースのクエリ
前提条件は、エンティティが保存されるバケットに PRIMARY INDEX を作成していることです。
次に例を示します。
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 プレースホルダーでは引き続きすべてのメソッドパラメーターが考慮されるため、次の例のように正しいインデックスを使用してください。
@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 つだけ返します (通常は COUNT
、AVG
、MAX
などの集計))。このような射影は、long
、boolean
、String
のような単純な戻り値の型を持ちます。これは、DTO への射影を目的としたものではありません。
もう一つの例:
#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
と同等です SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1
2 番目の方法では、Spring-Data のクエリ導出メカニズムを使用して、メソッド名とパラメーターから N1QL クエリを構築します。これにより、SELECT … FROM … WHERE firstName = "valueOfFnameAtRuntime"
のようなクエリが生成されます。これらの条件を組み合わせて、countByFirstname
のような名前でカウントを実行したり、findFirst3ByLastname
のような名前で制限を実行したりすることもできます。
実際には、生成された N1QL クエリには、リポジトリのエンティティクラスに一致するドキュメントのみを選択するための追加の N1QL 条件も含まれています。 |
ほとんどの Spring-Data キーワードがサポートされています: @Query (N1QL) メソッド名内のサポートされるキーワード
キーワード | サンプル | N1QL WHERE 句のスニペット |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
このアプローチでは、カウントクエリと [ リポジトリ .limit- クエリ結果 ] 機能の両方を使用できます。
N1QL では、リポジトリに使用できるもう 1 つのインターフェースは PagingAndSortingRepository
( CrudRepository
を継承したもの) です。2 つのメソッドが追加されます。
メソッド | 説明 |
---|---|
Iterable<T> findAll(ソートソート); | いずれかの属性に基づいて並べ替えながら、関連するすべてのエンティティを取得できます。 |
Page<T> findAll(ページング可能、ページング可能); | ページ内のエンティティを取得できるようにします。返された |
Page および Slice をメソッドの戻り値の型として使用したり、N1QL でサポートされたリポジトリを使用したりすることもできます。 |
インラインクエリでページング可能パラメーターと並べ替えパラメーターを使用する場合、インラインクエリ自体に order by、limit、offset 句を含めるべきではありません。そうしないと、サーバーはクエリを不正な形式として拒否します。 |
自動インデックス管理
デフォルトでは、ユーザーはクエリに最適なインデックスを作成および管理することが期待されています。特に開発の初期段階では、迅速に作業を進めるためにインデックスを自動的に作成すると便利です。
N1QL の場合、エンティティ (クラスまたはフィールドのいずれか) に添付する必要がある次のアノテーションが提供されます。
@QueryIndexed
: このフィールドがインデックスの一部であることを示すためにフィールドに配置されます@CompositeQueryIndex
: 複数のフィールド (複合) のインデックスを作成する必要があることを通知するためにクラスに配置されます。@CompositeQueryIndexes
: 複数のCompositeQueryIndex
を作成する必要がある場合、このアノテーションはそれらのリストを取得します。
例: これは、エンティティに複合インデックスを定義する方法です。
@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;
}
}
デフォルトでは、インデックスの作成は無効になっています。これを有効にしたい場合は、設定でそれをオーバーライドする必要があります。
@Override
protected boolean autoIndexCreation() {
return true;
}
一貫性のあるクエリ
デフォルトでは、N1QL を使用するリポジトリクエリは NOT_BOUNDED
スキャン整合性を使用します。これは、結果はすぐに返されますが、インデックスのデータには、以前に書き込まれた操作のデータがまだ含まれていない可能性があることを意味します (結果整合性と呼ばれます)。クエリに「独自の書き込みを準備する」セマンティクスが必要な場合は、@ScanConsistency
アノテーションを使用する必要があります。以下に例を示します。
@Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Iterable<Airport> findAll();
}
DTO の射影
Spring Data リポジトリは通常、クエリメソッドを使用するときにドメインモデルを返します。ただし、さまざまな理由から、そのモデルのビューを変更する必要がある場合があります。このセクションでは、リソースの簡略化された縮小ビューを提供するための射影を定義する方法を学習します。
次のドメインモデルを参照してください。
@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;
…
}
この Person
にはいくつかの属性があります:
id
は主キーですfirstName
とlastName
はデータ属性ですaddress
は別のドメインオブジェクトへのリンクです
ここで、次のように対応するリポジトリを作成するとします。
interface PersonRepository extends CrudRepository<Person, Long> {
Person findPersonByFirstName(String firstName);
}
Spring Data は、すべての属性を含むドメインオブジェクトを返します。address
属性を取得するには、2 つのオプションがあります。1 つのオプションは、次のように Address
オブジェクトのリポジトリを定義することです。
interface AddressRepository extends CrudRepository<Address, Long> {}
この状況では、PersonRepository
を使用すると、Person
オブジェクト全体が返されます。AddressRepository
を使用すると、Address
のみが返されます。
しかし、address
の詳細をまったく公開したくない場合はどうすればよいでしょうか ? 1 つ以上の射影を定義することで、リポジトリサービスのコンシューマーに代替手段を提供できます。
interface NoAddresses { (1)
String getFirstName(); (2)
String getLastName(); (3)
}
この射影には次の詳細があります。
1 | 宣言型にする単純な Java インターフェース。 |
2 | firstName をエクスポートします。 |
3 | lastName をエクスポートします。 |
NoAddresses
射影には、firstName
および lastName
に対して getter のみがあるため、アドレス情報は提供されません。この場合、クエリメソッド定義は Person
ではなく NoAdresses
を返します。
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}
射影は、基礎となる型と公開されたプロパティに関連するメソッドシグネチャー間の契約を宣言します。基礎となる型のプロパティ名に従って getter メソッドに名前を付ける必要があります。基礎となるプロパティの名前が firstName
の場合、getter メソッドの名前は getFirstName
にする必要があります。そうしないと、Spring Data はソースプロパティを検索できません。