Specification

JPA 2 は、クエリをプログラムで作成するために使用できる条件 API を導入しています。criteria を記述することにより、ドメインクラスのクエリの where 句を定義します。別のステップに戻ると、これらの条件は、JPA 条件 API 制約によって記述されたエンティティに関する述語と見なすことができます。

Spring Data JPA は、同じセマンティクスに従い、JPA 条件 API を使用してそのような仕様を定義する API を提供する、エリックエバンスの著書 "Domain Driven Design [Amazon] " から仕様の概念を取り入れています。次のように、仕様をサポートするために、JpaSpecificationExecutor インターフェースを使用してリポジトリインターフェースを継承できます。

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
 …
}

追加のインターフェースには、さまざまな方法で仕様を実行できるメソッドがあります。例: 次の例に示すように、findAll メソッドは仕様に一致するすべてのエンティティを返します。

List<T> findAll(Specification<T> spec);

Specification インターフェースは次のように定義されます。

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

次の例に示すように、仕様を簡単に使用して、エンティティの上に拡張可能な一連の述語を構築し、JpaRepository と組み合わせて使用し、必要な組み合わせごとにクエリ(メソッド)を宣言する必要なしに使用できます。

例 1: 顧客の仕様
public class CustomerSpecs {


  public static Specification<Customer> isLongTermCustomer() {
    return (root, query, builder) -> {
      LocalDate date = LocalDate.now().minusYears(2);
      return builder.lessThan(root.get(Customer_.createdAt), date);
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
    return (root, query, builder) -> {
      // build query here
    };
  }
}

Customer_ 型は、JPA メタモデルジェネレーターを使用して生成されたメタモデル型です(Hibernate 実装のドキュメントの例 (英語) を参照)。式 Customer_.createdAt は、Customer が型 Date の createdAt 属性を持っていることを前提としています。さらに、ビジネス要件の抽象化レベルでいくつかの条件を表現し、実行可能な Specifications を作成しました。クライアントは次のように Specification を使用する可能性があります。

例 2: 簡単な仕様を使用する
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

この種のデータアクセスのクエリを作成してみませんか? 単一の Specification を使用しても、単純なクエリ宣言よりも大きな利点は得られません。仕様を組み合わせて新しい Specification オブジェクトを作成すると、仕様の威力が本当に発揮されます。これは、次のような式を作成するために提供されている Specification のデフォルトメソッドを使用して実現できます。

例 3: 組み合わせ仕様
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  isLongTermCustomer().or(hasSalesOfMoreThan(amount)));

Specification は、チェーンにいくつかの「グルーコード」デフォルトメソッドを提供し、Specification インスタンスを結合します。これらのメソッドを使用すると、新しい Specification 実装を作成し、既存の実装と組み合わせることにより、データアクセスレイヤーを継承できます。

また、JPA 2.1 では、CriteriaBuilder API によって CriteriaDelete が導入されました。これは、JpaSpecificationExecutor’s `delete(Specification) API を介して提供されます。

例 4: Specification を使用してエントリを削除します。
Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)

userRepository.delete(ageLessThan18);

Specification は、age フィールド(整数としてキャスト)が 18 よりも小さい条件を構築します。userRepository に渡され、JPA の CriteriaDelete 機能を使用して、適切な DELETE 操作を生成します。次に、削除されたエンティティの数を返します。