5장 : 스프링 데이터 JPA를 이용한 조회 기능
시작에 앞서
CQRS는 명령(Command) 모델과 조회(Query) 모델을 분리하는 패턴이다.
검색을 위한 스펙
검색 조건을 다양하게 조합해야 할 때 사용할 수 있는 것이 스펙이다. 스펙은 애그리거트가 특정 조건을 충족하는지를 검사할 때 사용하는 인터페이스다.
스프링 데이터 JPA를 이용한 스펙 구현
스프링 데이터 JPA는 검색 조건을 표현하기 위한 인터페이스인 Specification을 제공한다.
public interface Specification<T> extends Serializable {
// not, where, and, or 메서드 생략
@Nullable
Predicate toPredicate(Root<T> root,
CriteriaQuery<?> query,
CriterialBuilder cb);
}
public class OrderIdSpec implements Specification<OrderSummary> {
private String orderId;
public OrdererIdSpec(String orderId) {
this.ordererId = oderrerId;
}
@Override
public Predicate toPredicate(Root<OrderSmmary> root,
CriterialQuery<?> query,
CriteriaBuilder cb) {
return cb.equal(root.get(OrderSummary_.ordererId), ordererId);
}
}
스펙 구현 클래스를 개별적으로 만들지 않고 별도 클래스에 스펙 생성 기능을 모아도 된다.
public class OrderSummarySpec {
public static Specification<OrderSummary> orderId(String ordererId) {
return (Root<OrderSummary> root, CriteriaQuery<?> query,
CriteriaBuilder cb) ->
cb.equal(root.<String>get("ordererId"), ordererId);
}
public static Specification<OrderSummary> orderDateBetween (
LocalDateTime from, LocalDateTime to) {
return (Root<OrderSummary> root, CriteriaQuery<?> query,
CriteriaBuilder cb) ->
cb.between(root.get(OrderSummary_.orderDate), from, to);
}
}
Specification<OrderSummary> betweenSpec =
OrderSummarySpecs.orderDateBetween(from, to);
리포지터리/DAO에서 스펙 사용하기
스펙을 충족하는 엔티티를 검색하고 싶다면 findAll()메서드를 사용하면 된다.
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
List<OrderSummary> findAll(Specification<OrderSummary> spec);
}
// 스펙 객체를 생성하고
Specification<OrderSummary> spec new OrdererIdSpec("user1");
// findAll() 메서드를 이용해서 검색
List<OrderSummary> results = orderSummaryDao.findAll(spec);
스펙 조합
public interface Specification<T> extends Serializable {
default Specification<T> and(@Nullable Specification<T> other) { ... }
default Specification<T> or(@Nullable Specification<T> other) { ... }
@Nullable
Predicate toPredicate(Root<T> root,
CriteriaQuery<?> query,
CriterialBuilder cb);
}
정렬 지정하기
스프링 데이터 JPA는 두 가지 방법을 사용해서 정렬을 지정할 수 있다.
1 . 메서드 이름에 OrderBy를 사용해서 정렬 기준 지정
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
List<OrderSummary> findByOrdererIdOrderByNumberDesc(String ordererId);
}
findByOrdererIdOrderByNumberDesc 메서드는 다음 조회 쿼리를 생성한다.
ordererId 프로퍼티 값을 기준으로 검색 조건 지정
number 프로퍼티 값 역순으로 정렬
2 . Sort를 인자로 전달
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
List<OrderSummary> findByOrdererId(String ordererId, Sort sort);
List<OrderSummary> findAll(SPecification<OrderSummary> spec, Sort sort);
}
Sort sort = SOrt.by("name").ascending();
List<OrderSummary> results = orderSummaryDaofindByOrdererId("user1", sort)l;
아래와 같이 두 개 이상의 정렬 순서를 짧게도 표현 가능
Sort sort = Sort.by("number").ascending().and(Sort.by("orderDate").descending());
페이징 처리하기
public interface MemberDataDao extends Repository<MemberData, String> {
List<MemeberData> findByNameLike(String name, Pageable pageable);
Page<MemberData> findByBlocked(boolean blocked, Pageable pageable);
Page<MemberData> findAll(Specification<MemberData> spec, Pageable pageable);
}
PageRequest pageReq = PageRequest.of(1, 10);
List<MemberData> user = memberDataDao.findByNameLike("사용자%", pageReq);
스펙 조합을 위한 스펙 빌더 클래스
Specification<MemberData> spec = Specification.where(null);
if (searchRequest.isOnlyNotBlocked()) {
spec = spec.and(MemberDataSpecs.nonBlocked());
}
if (StringUtils.hasText(searchRequest.getName())) {
spec = spec.and(MemberDataSpecs.nameLike(searchRequest.getName()));
}
List<MemberData> results = memberDataDao.findAll(spec, PageRequest.of(0, 5));
동적 인스턴스 생성
JPA는 쿼리 결과에서 임의의 객체를 동적으로 생성할 수 있는기능을 제공하고 있다.
동적 인스턴스는 JPQL을 그대로 사용하므로 객체 기준으로 쿼리를 작성하면서도 동시에 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터를 조회할 수 있다.
하이버네이트 @Subselect 사용
하이버네이트는 JPA 확장 기능으로 @Subselect를 제공하는데, @Subselect는 쿼리 결과를 @Entity로 매핑할 수 있는 유용한 기능이다.
서브 쿼리를 사용하고 싶지 않다면 네이티브 SQL 쿼리를 사용하거나 마이바티스와 같은 별도 매퍼를 사용해서 조회 기능을 구현해야 한다.
Last updated