Specification
JPA の Criteria API を使用すると、プログラムでクエリを作成できます。Spring Data JPA Specification は、エンティティに対する述語を表現し、リポジトリ間で再利用するための、小規模で集中的な API を提供します。Eric Evans の著書「ドメイン駆動設計 [Amazon] 」の仕様コンセプトに基づいて、これらの仕様は同じセマンティクスに従い、JPA を使用して条件を定義するための API を提供します。仕様をサポートするには、次のように JpaSpecificationExecutor インターフェースを使用してリポジトリインターフェースを継承できます。
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}仕様とは、Criteria API で表現されたエンティティに対する述語です。Spring Data JPA は 2 つのエントリポイントを提供します。
PredicateSpecification: Spring Data JPA 4.0 で導入された、クエリ型に依存しない柔軟なインターフェース。Specification(およびUpdateSpecification、DeleteSpecification): クエリバインドバリアント。
PredicateSpecification
PredicateSpecification インターフェースは、幅広い機能構成を可能にする最小限の依存関係セットで定義されています。
public interface PredicateSpecification<T> {
Predicate toPredicate(From<?, T> from, CriteriaBuilder builder);
} 仕様を使用すると、拡張可能な述語のセットを簡単に構築でき、次の例に示すように、必要な組み合わせごとにクエリ (メソッド) を宣言する必要がなくなり、JpaRepository と併用できます。
class CustomerSpecs {
static PredicateSpecification<Customer> isLongTermCustomer() {
return (from, builder) -> {
LocalDate date = LocalDate.now().minusYears(2);
return builder.lessThan(from.get(Customer_.createdAt), date);
};
}
static PredicateSpecification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
return (from, builder) -> {
// build predicate for sales > value
};
}
}Customer_ 型は、JPA メタモデルジェネレーター(Hibernate 実装のドキュメントの例 (英語) を参照)を用いて生成されたメタモデル型です。式 Customer_.createdAt は、Customer が LocalDate 型の createdAt 属性を持つことを前提としています。さらに、ビジネス要件の抽象化レベルでいくつかの条件を規定し、実行可能な Specifications を作成しました。
リポジトリで仕様を直接使用します。
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());仕様は次のように構成されたときに最も価値が高まります。
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount))
);Specification、UpdateSpecification、DeleteSpecification
Specification (Javadoc) インターフェースは以前から利用可能であり、Criteria API の制限に従って特定のクエリ型(選択、更新、削除)に関連付けられています。3 つの仕様インターフェースは以下のように定義されています。
仕様
UpdateSpecification
DeleteSpecification
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
public interface UpdateSpecification<T> {
Predicate toPredicate(Root<T> root, CriteriaUpdate<T> update,
CriteriaBuilder builder);
}
public interface DeleteSpecification<T> {
Predicate toPredicate(Root<T> root, CriteriaDelete<T> delete,
CriteriaBuilder builder);
}
Specification オブジェクトは、次の例に示すように、直接構築することも、PredicateSpecification インスタンスを再利用して構築することもできます。
public class CustomerSpecs {
public static UpdateSpecification<Customer> updateLastnameByFirstnameAndLastname(String newLastName, String currentFirstname, String currentLastname) {
return UpdateSpecification.<Customer>update((root, update, criteriaBuilder) -> {
update.set("lastname", newLastName);
}).where(hasFirstname(currentFirstname).and(hasLastname(currentLastname)));
}
public static PredicateSpecification<Customer> hasFirstname(String firstname) {
return (root, builder) -> {
return builder.equal(root.get("firstname"), firstname);
};
}
public static PredicateSpecification<Customer> hasLastname(String lastname) {
return (root, builder) -> {
// build query here
};
}
}Fluent API
JpaSpecificationExecutor は、Specification インスタンスに基づいてクエリを柔軟に実行するための流れるようなクエリメソッドを定義します。
PredicateSpecificationの場合:findBy(PredicateSpecification<T> spec, Function<? super SpecificationFluentQuery<S>, R> queryFunction)Specificationの場合:findBy(Specification<T> spec, Function<? super SpecificationFluentQuery<S>, R> queryFunction)
他のメソッドと同様に、Specification から派生したクエリを実行します。ただし、クエリ関数を使用すると、他の方法では動的に制御できないクエリ実行の側面を制御できます。これは、SpecificationFluentQuery のさまざまな中間メソッドと終端メソッドを呼び出すことによって実現されます。
中間的な方法
sortBy: 結果に順序付けを適用します。メソッドを繰り返し呼び出すと、各Sortが追加されます(ソート済みのPageableを使用するpage(Pageable)は、以前のソート順序を上書きすることに注意してください)。limit: 結果数を制限します。as: 読み取るまたは投影する型を指定します。project: クエリのプロパティを制限します。
ターミナル方式
first,firstValue: 最初の値を返します。クエリが結果を返さなかった場合、firstはOptional<T>またはOptional.empty()を返します。firstValueは、Optionalを使用する必要がない、null 値可能なバリアントです。one,oneValue: 1 つの値を返します。クエリが結果を返さなかった場合、oneはOptional<T>またはOptional.empty()を返します。oneValueは、Optionalを使用する必要がない、NULL 値許容型のバリアントです。複数の一致が見つかった場合は、IncorrectResultSizeDataAccessExceptionがスローされます。all: すべての結果をList<T>として返します。page(Pageable): すべての結果をPage<T>として返します。slice(Pageable): すべての結果をSlice<T>として返します。scroll(ScrollPosition): スクロール (オフセット、キーセット) を使用して、結果をWindow<T>として取得します。stream(): 結果を具体化されたCollectionではなくストリームとして処理するには、Stream<T>を返します (Streamのセマンティクスを参照)。ストリームは状態を持つため、使用後は閉じる必要があります。countおよびexists: 一致するエンティティの数、または一致するものが存在するかどうかを返します。
| 中間メソッドとターミナルメソッドは、クエリ関数内で呼び出す必要があります。 |
lastname で順序付けられた投影された Page を取得します。Page<CustomerProjection> page = repository.findBy(spec,
q -> q.as(CustomerProjection.class)
.page(PageRequest.of(0, 20, Sort.by("lastname")))
);lastname 順に並べられた多数の結果の最後を取得します。Optional<Customer> match = repository.findBy(spec,
q -> q.sortBy(Sort.by("lastname").descending())
.first()
);