JPA 2에는 프로그래밍 방식으로 쿼리를 작성하는 데 사용할 수 있는 기준 API가 도입되었습니다. criteria
을 작성하여 도메인 클래스에 대한 쿼리의 where 절을 정의합니다. 한 걸음 더 물러서면 이러한 기준은 JPA 기준 API 제약 조건에 의해 설명되는 엔터티에 대한 조건자로 간주될 수 있습니다.
Spring Data JPA는 Eric Evans의 저서 "Domain Driven Design"에서 사양 개념을 가져와 동일한 의미론을 따르고 JPA 기준 API를 사용하여 이러한 사양을 정의하는 API를 제공합니다. 사양을 지원하려면 다음과 같이 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
와 결합하여 사용할 수 있는 엔터티 위에 확장 가능한 predicate 세트를 구축하는 데 쉽게 사용할 수 있습니다.
Example 1. Specifications for a Customer
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 Metamodel 생성기를 사용하여 생성된 메타모델 유형입니다(예제는 Hibernate 구현 문서 참조). 따라서 Customer_.createdAt
표현식은 Customer
이 Date
유형의 createAt
속성을 가지고 있다고 가정합니다. 그 외에도 비즈니스 요구 사항 추상화 수준에 대한 몇 가지 기준을 표현하고 실행 가능한 Specifications
을 만들었습니다. 따라서 클라이언트는 다음과 같이 Specification
을 사용할 수 있습니다.
Example 2. Using a simple Specification
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
이런 종류의 데이터 액세스에 대한 쿼리를 만들어 보는 것은 어떨까요? 단일 Specification
을 사용하면 일반 쿼리 선언에 비해 많은 이점을 얻지 못합니다. Specification
의 힘은 사양을 결합하여 새로운 Specification
개체를 만들 때 실제로 빛납니다. 다음과 유사한 표현식을 작성하기 위해 우리가 제공하는 Specification
의 기본 method을 통해 이를 달성할 수 있습니다.
Example 3. Combined Specifications
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
의 delete(Specification)
API를 통해 제공됩니다.
Example 4. Using a Specification to delete entries.
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
작업을 생성합니다. 그런 다음 삭제된 엔터티 수를 반환합니다.