스펙 인터페이스에서 제네릭 타입 파라미터 <T>는 JPA 엔티티 타입을 의미한다. toPredicate() 메서드는 JPA Criteria API에서 조건을 표현하는 Predicate을 생성한다.
public class OrdererIdSpec implements Specification<OrderSummary> {
private String ordererId;
public OrdererIdSpec(String ordererId){
this.ordererId = ordererId;
}
@Override
public Predicate toPredicate(Root<OrderSummary> root,
CriteriaQuery<?> query,
CriteriaBuilder cb)
return cb.equal(root.get(OrderSummary_.ordererId), ordererId);
}
}
해당 클래스는 OrderSummary 엔티티에 대한 검색 조건을 표현한다. toPredicate() 메서드를 구현한 코드는 "ordererId" 프로퍼티 값이 생성자로 전달받은 ordererId와 동일한지 비교하는 Predicate을 생성한다.
NOTE : JPA 정적 메타 모델 정적 메타 모델은 @StaticMetamodel 애너테이션을 이용해서 관련 모델을 지정한다. 메타 모델 클래스는 모델 클래스의 이름 뒤에 '_'를 붙인 이름을 갖는다.
정적 메타 모델 클래스는 대상 모델의 각 프로퍼티와 동일한 이름을 갖는 정적 필드를 정의한다. 이 정적 필드는 프로퍼티에 대한 메타 모델로서 프로퍼티 타입에 따라 SingularAttribute, ListAttribute 등의 타입을 사용해서 메타 모델을 정의한다.
정적 메타 모델을 사용하는 대신 문자열로 프로퍼티를 지정할 수도 있지만, 오타가능성과 코드 자동 완성 기능을 사용할 수 없어 입력할 코드가 많아진다는 단점이 있다.
정적 메타 모델 클래스를 직접 작성할 수 있지만 하이버네이트와 같은 JPA 프로파이더는 정적 메타 모델을 생성하는 도구를 제공하고 있으므로 이들 도구를 사용하면 편리하다.
스펙 구현 클래스를 개별적으로 만들지 않고 별도 클래스에 스펙 생성 기능을 모아도 된다. 예를 들어 OrderSummary와 관련된 스펙 생성 기능을 한 클래스에 모을 수 있다.
findAll() 메서드는 OrderSummary에 대한 검색 조건을 표현하는 스펙 인터페이스를 파라미터로 갖는다. 이 메서드와 앞서 작성한 스펙 구현체를 사용하면 특정 조건을 충족하는 엔티티를 검색할 수 있다.
// 스펙 객체 생성
Specficification<OrderSummary> spec = new OrdererIdSpec("user1");
// findAll() 메서드를 이용해서 검색
List<OrderSummary> results = orderSummaryDao.findAll(spec);
보통 프로젝트에서 쿼리나 검색 조건을 만들 때는 @NamedQuery나 QueryDSL을 주로 사용해 왔다. Specification은 사용 방식이 복잡하고 러닝커브도 있어 선뜻 도입하기 어려운 느낌이 있었다. 그렇다면 굳이 Specification을 사용하는 이유가 있을까 궁금해져 이를 찾아보았다.
짧은 시간 동안 살펴본 결과, Specification을 사용하는 데 있어 뚜렷한 메리트는 크지 않다고 판단했다. 따라서 실무에서는 QueryDSL을 기본으로 활용하되, Specification은 학습 목적으로 익혀두는 것이 좋겠다고 생각했다