[도메인 주도 개발 시작하기] 05. 스프링 데이터 JPA를 이용한 조회 기능

ASk·2022년 10월 24일
0

DDD

목록 보기
5/11
post-thumbnail

5.1 시작에 앞서

시작하기 앞서 언급할것이 있다. 바로 CQRS 이다.

CQRS 는 명령(Command) 모델과 조회(Query) 모델을 분리하는 패턴이다.

명령 모델은 상태 변경 기능 구현, 조회 모델은 데이터 조회 기능 구현에 사용한다.

명령 모델은 주로 도메인 모델에 사용하며 정렬, 페이징, 검색등과 같은 기능은 조회모델로 사용한다.

5.2 검색을 위한 스펙

검색 조건이 고정되어 있다면 기능을 특정 조건 조회 기능을 추가하면 되지만
다양한 검색조건을 조합해야 할때 사용할 수 있는것이 스펙(Specification)이다.

애그리거트가 특정 조건을 충족하는지를 검사할 때 사용하는 인터페이스이다.

public interface Specification<T> {
  boolean isSatisfiedBy(T agg);
}

agg 파라미터는 검사 대상이 되는 객체이며 리포지터리에서 사용하면 애그리거트 루트가 되고
스펙을 DAO 에 사용하면 검색 결과로 리턴할 데이터 객체가 된다.

public class OrdererSpec implements Specification<Order> {

  private String ordererId;

  public boolean isSatisfiedBy(Order agg) {
    return agg.getOrdererId().getMemberId().getId().equals(ordererId);
  }

}

리포지터리나 DAO 는 검색 대상을 걸러내는 용도로 스펙을 사용한다.

하지만 모든 애그리거트 객체를 메모리에 보관하는것이 아니기 때문에 이렇게 구현하지 않고
사용하는 기술에 맞춰 구현하게 된다. 이장에서는 스프링 데이터 JPA 스펙 구현에 대해 알아본다.

5.3 스프링 데이터 JPA를 이용한 스펙 구현

스프링 데이터 JPA 는 검색 조건을 표현하기 위한 인터페이스인 Specification 을 제공한다.

public interface Specification<T> extends Serializable {
  
  @Nullable
  Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
  
}

JPA Criteria API 를 사용한 방법이다.

추가) 실무에선 QueryDSL 사용하므로 생략, 필요하면 하단글 확인

5.4 리포지터리/DAO에서 스펙 사용하기

스펙을 충족하는 엔티티를 검색하고 싶다면 findAll() 메서드를 사용하면 된다.

추가) 스프링 데이터 JPA 사용시 JpaSpecificationExecutor 를 상속받아 사용

public interface JpaSpecificationExecutor<T> {
  // 생략...
  
  List<T> findAll(@Nullable Specification<T> spec);

  // 생략...
}

5.5 스펙 조합

스프링 데이터 JPA 가 제공하는 스펙 인터페이스는 스펙을 조합할 수 있는 메서드를 제공한다.

추가로 not, where 등의 메서드도 제공한다.

public interface Specification<T> extends Serializable {

  static <T> Specification<T> not(@Nullable Specification<T> spec) { ... }
  static <T> Specification<T> where(@Nullable Specification<T> spec) { ... }
  default Specification<T> and(@Nullable Specification<T> other) { ... }
  default Specification<T> or(@Nullable Specification<T> other) { ... }
  
  @Nullable
  Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
  
}

5.6 정렬 지정하기

스프링 데이터 JPA 는 두가지 방법을 사용해서 정렬을 지정 할 수 있다.

  • 메서드 이름에 OrderBy 를 사용해서 정렬 기준 지정
  • Sort 를 인자로 전달
public interface OrderSummaryRepository extends JpaRepository<OrderSummary, Integer> {
  
  // 메서드 이름
  List<OrderSummary> findAllByOrderByNumberDesc(String ordererId);
  
  // Sort
  List<OrderSummary> findAllByOrdererId(String ordererId, Sort sort);
}
Sort sort = Sort.by("number").ascending();

5.7 페이징 처리하기

스프링 데이터 JPA 는 페이징 처리를 위해 Pageable 타입을 이용한다.

Sort 타입과 마찬가지로 find 에 Pageable 타입 파라미터를
사용하면 페이징을 자동으로 처리 해준다.

public interface OrderSummaryRepository extends JpaRepository<OrderSummary, Integer> {
  
  List<OrderSummary> findByOrderByNumberDesc(String ordererId, Pagable pagable);

  // 목록뿐 아니라 조건에 해당하는 전체 개수 및 페이징 처리에 필요한 데이터도 함께 제공
  Page<OrderSummary> findByOrderByNumberDesc(String ordererId, Pagable pagable);
  
}
Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending());

처음부터 N개의 데이터가 필요하면 findFirstN 형식의 메서드를 사용할 수도 있다.
Fisrt 대신 Top 도 가능

List<MemberData> findFisrt3ByNameLikePOrderByName(String name);

5.8 스펙 조합을 위한 스펙 빌더 클래스

스펙을 사용하다보면 조건에 따라 스펙을 조합해야 할 때가 있다.
이때 스펙 빌더를 사용해 작성할 수 있다.

Specification<MeberData> spec = SpecBuilder.builder(MemberData.class)
    .ifTrue(...)
    .ifHasText(...)
    .toSpec();

List<MemberData> result = memberDataDao.findAll(spec, PageRequest.of(0, 5));

5.9 동적 인스턴스 생성

JPA 는 쿼리 결과에서 임의의 객체를 동적으로 생성할 수 있는 기능을 제공하고 있다.
추가) Projection 을 의미한다.

조회 전용 모델을 만드는 이유는 표현 영역을 통해 사용자에게
데이터를 보여주기 위함이다.

동적 인스턴스의 장점은 JPQL 을 그대로 사용하므로 객체 기준 쿼리를 사용하면서도
지연/즉시 로딩과 같은 고민이 필요없이 데이터를 조회할 수 있다는 점이다.

5.10 하이버네이트 @Subselect 사용

하이버네이트는 JPA 확장 기능으로 @Subselect 를 제공한다.

@Subselect 는 쿼리 결과를 @Entity 로 매핑할 수 있는 유용한 기능이다.

@Subselect 로 조회한 @Entity 는 수정할 수 없다.

@Entity 매핑 필드를 수정하면 update 쿼리가 발생하므로 이런 문제를
방지하기위해 @Immutable 을 사용한다.

@Subselect 는 이름처럼 @Subselect 의 값으로 지정한 쿼리를
from 절의 서브 쿼리로 사용한다.

0개의 댓글