Chapter 05. 스프링 데이터 JPA를 이용한 조회 기능

beanii·2023년 3월 26일
0

DDD Study

목록 보기
5/11
post-thumbnail

5.1 시작에 앞서

  • CQRS

    • 명령(Command) 모델과 조회(Query) 모델을 분리하는 패턴
  • 명령 모델

    • 상태를 변경하는 기능 구현할 때 사용
      ex) 회원 가입, 암호 변경, 주문 취소
  • 조회 모델

    • 데이터를 조회하는 기능 구현할 때 사용
      ex) 주문 목록, 주문 상세
  • 도메인 모델 -> 명령 모델

  • 정렬, 페이징, 검색 조건 지정 등 -> 조회 모델



5.2 검색을 위한 스펙

  • 스펙(Specification)
    • 검색 조건을 다양하게 조합해야 할 때 사용
    • 애그리거트가 특정 조건을 충족하는지 검사
    • 필요한 조합마다 find 메서드 정의하는 것은 좋은 방법이 아님
public interface Specification<T> {
	public boolean isSatisfiedBy(T agg);
}
  • isSatisfiedBy 메서드
    • agg 파라미터는 검사 대상이 되는 객체
      • 리포지터리에서는 agg = 애그리거트 루트
      • DAO에서는 agg = 검색 결과로 리턴할 데이터 객체

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

  • 스프링 데이터 JPA는 검색 조건을 표현하기 위한 인터페이스인 Specification을 다음과 같이 정의하여 제공
package org.springframework.data.jpa.domain;

public interface Specification<T> extends Serializable { // T -> JPA 엔티티 타입
	//not, where, and, or 메서드 생략
    
    @Nullable
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

  • 사용 예시
    • 엔티티 타입이 OrderSummary
    • orderId 프로퍼티 값이 지정한 값과 동일
public class OrderIdSpec implements Specification<OrderSummary> {
	private String orderId;
    
    public OrdererIdSpec(String orderId) {
    	this.ordererId = ordererId;
    }

	@Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    	return cb.equal(root.get(OrderSummary_.ordererId), ordererId);
    }
}

  • 스펙 구현 클래스를 개별적으로 만들지 않고 별도 클래스에 스펙 생성 기능 모아도 됨


5.4 리포지터리/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);


5.5 스펙 조합

  • andor 메서드
    • 기본 구현을 가진 디폴트 메서드
    • and 메서드 : 두 스펙을 모두 충족하는 조건을 표현하는 스펙 생성
    • or 메서드 : 두 스펙 중 하나 이상 충족하는 조건을 표현하는 스펙 생성
Specification<OrderSummary> spec = OrderSummarySpecs.ordererId("user1")
	.and(ORderSummarySpecs.orderDateBetween(from, to));

  • not 메서드
    • 조건을 반대로 적용할 때 사용하는 정적 메서드
Specification<OrderSummary> spec =
	Specification.not(OrderSummarySpecs.ordererId("user1"));

  • where 메서드
    • 스펙 인터페이스의 정적 메서드로 null 전달하면 아무 조건도 생성하지 않는 스펙 객체 리턴
    • null이 아니면 인자로 받은 스펙 객체 그대로 리턴
Specification<OrderSummary> spec =
	Specification.where(createNullableSpec()).and(createOtherSpec());


5.6 정렬 지정하기

  1. 메서드 이름에 OrderBy 사용해서 정렬 기준 지정
    • 단점 : 메서드 이름 길어짐, 상황에 따라 정렬 순서 변경할 수 없음
      ex) findByOrdererIdOrderByNumberDesc(),
      findByOrdererIdOrderByOrderDateDescNumberAsc() ->두 개 이상의 프로퍼티에 대한 정렬

  2. Sort를 인자로 전달
  • find 메서드에 마지막 파라미터로 Sort 추가
  • 두 개 이상의 정렬 순서 지정하고 싶을 때는 and 이용해서 두 Sort 객체 연결
Sort sort = Sort.by("number").ascending();
List<OrderSummary> results = orderSummaryDao.findByOrdererId("user1", sort);


5.7 페이징 처리하기

  • Pageable 타입
    • 스프링 데이터 JPA에서 페이징 처리를 위해 사용하는 인터페이스
    • find 메서드에 파라미터로 전달하면 페이징 자동으로 처리
Sort sort = Sort.by("name").descending(); //정렬 순서 지정
PageRequest pageReq = PageRequest.of(1, 2, sort);
List<MemberData> user = memberDataDao.findByNameLike("사용자%", pageReq);
  • Page 타입 이용해서 조건에 해당하는 전체 개수도 구할 수 있음
    • Peageable 사용하는 메서드의 리턴 타입이 Page일 경우 목록 조회 쿼리와 함께 COUNT 쿼리도 실행
    • 전체 개수, 페이지 개수 등 페이징 처리에 필요한 데이터 제공

  • 처음부터 N개의 데이터 필요하면 findFirstN 형식의 메서드 사용
    • First 대신 Top 사용 가능
    • First나 Top 뒤에 숫자 없으면 한 개 결과 리턴
      ex) findFirst3ByNameLikeOrderByName()


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

  • 스펙 빌더 사용
    • if 문 사용에 비해 코드 가독성 높이고 단순한 구조


  • 사용 예시
Specification<MemberData> spec = SpecBuilder.builder(MemberData.class)
	.ifTrue(searchRequest.isOnlyNotBlocked(),
    () -> MemberDataSpecs.nonBlocked())
    .ifHasText(searchRequest.getName(),
    	name -> MemberDataSpecs.nameLike(searchRequest.getName())
    .toSpec();
List<MemberData> result = memberDataDao.findAll(spec, Pagerequest.of(0,5));


5.9 동적 인스턴스 생성

  • 동적 인스턴스
    • JPA는 쿼리 결과에서 임의의 객체를 동적으로 생성할 수 있는 기능 제공
    • 조회 전용 모델 만드는 이유 : 표현 영역을 통해 사용자에게 데이터 보여주기 위함
      장점 : JPQL 그대로 사용하므로 객체 기준으로 쿼리 작성하면서 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터 조회 가능
public interface OrderSummaryDao
extends Repository<OrderSummary, String> {
	@Query(""
			select new com.myshop.order.query.dto.OrderView(
        	o.number, o.state, m.name, m.id, p.name
			)
			from Order o join o.orderLines ol. Member m, Product p
			where o.orderer.memberId = :ordererId
			and o.orderer.memberId.id = m.id
			and index(ol) = 0
			and ol.productId.id = p.id
			order by o.number.number desc
			"")
    List<OrderView> findOrderView(String ordererId);
}


5.10 하이버네이트 @Subselect 사용

  • @Subselect
    • 쿼리 결과를 @Entity로 매칭할 수 있는 유용한 기능
    • 조회 쿼리를 값으로 가짐
    • select 쿼리의 결과를 매핑할 테이블처럼 사용
    • @Subselect로 조회한 @Entity 수정 불가
    • 장점 : 일반 @Entity와 같기 때문에 EntityManager#find(), JPQL, Criteria를 사용해서 조회 가능
    • @Subselect의 값으로 지정한 쿼리를 from 절의 서브 쿼리로 사용

  • @Immutable
    • 해당 엔티티의 매핑 필드/프로퍼티가 변경돼도 DB에 반영하지 않고 무시

  • @Synchronize
    • 해당 엔티티와 관련된 테이블 목록 명시

0개의 댓글