고딩왕 코범석
5장 - 스프링 데이터 JPA를 이용한 조회 기능 본문
Index
CQRS
- 앞에서 봤던 애그리거트, 리포지터리, 엔티티와 같은 모델은 상태를 변경할 때 주로 사용한다.
- 조회용 모델과 상태 변경의 모델은 다르기 때문에 만약 조회를 위해 엔티티에 연관 관계와 같은 설정이 변경될 수 있다면 가급적 조회 모델을 따로 만드는게 좋다.
검색을 위한 스펙
검색 조건을 조합해야 할 경우 조건별
find..()
를 만들기 보다 애그리거트가 특정 조건을 충족하는지 검사할 때 사용하는 Specification 인터페이스를 만들어서 사용하는게 좋다.public interface Specification<T> { boolean isSatisfiedBy(T agg); }
isSatisfiedBy()
의 파라미터는 검사 대상이 되는 객체다. 만약 이 스펙을 Repository에 사용하면 애그리거트 루트가 되고, DAO(조회를 위한 데이터 접근 객체)에 적용한다면 검색 결과로 사용할 데이터 객체가 된다.위 Specification 인터페이스를 구현해 XXXSpec 클래스를 만들어 조건에 맞는 엔티티 혹은 조회 모델을 찾는 Spec 클래스를 정의한다고 이해하면 된다.
그리고 Repository 계층에서는 매개변수를 Specification 인터페이스를 받아 filter 조건으로 사용한다.
스프링 데이터 JPA를 이용한 스펙 구현
스프링 데이터 JPA에서는 검색 조건을 표현하기 위한 Specification 인터페이스를 제공한다.
package org.springframework.data.jpa.domain; public interface Specification<T> extends Serializable { long serialVersionUID = 1L; static <T> Specification<T> not(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> { return null; } : (root, query, builder) -> { return builder.not(spec.toPredicate(root, query, builder)); }; } static <T> Specification<T> where(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> { return null; } : spec; } default Specification<T> and(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, CriteriaBuilder::and); } default Specification<T> or(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, CriteriaBuilder::or); } @Nullable Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder); }
책에서는 Criteria가 나와있어 별도로 살펴보지는 않았다. 나도 그렇고 요즘은 기업에서 JPA를 사용한다면 Querydsl도 사용하기 때문에 이번 포스팅에서는 이런게 있다 정도로만 이해하고 넘어갔다.
정렬 지정
- 스프링 데이터 JPA는 다음과 같은 두 가지 방법을 사용해 정렬을 지정할 수 있다.
- 메서드 명에 OrderBy를 사용해 정렬 기준 지정
- Sort를 인자로 전달
- 메서드 명을 통해 정렬하는 방법이 간단하지만 정렬 조건이나 다른 조건이 붙어버리면 다음과 같은 단점이 있다.
- 메서드명이 너무 길어져서 가독성 저하
- 메서드 이름으로 정렬 순서가 정해지기 때문에 상황에 따라 정렬 순서가 변경될텐데 이 부분에 있어 유연하지 못하다.
- 이럴 때는
Sort
타입을 이용해 정렬한다.
페이징 처리하기
- JPA에서 페이징 처리는
Pageable
을 통해 처리한다. Pageable
을 사용하는 메서드의 리턴 타입이Page
일 경우 JPA에서는 count 쿼리도 발생한다. 무한 스크롤의 경우 전체 컨텐츠에 대한 count 쿼리를 발생시킬 이유는 없으니 Slice 타입으로 리턴하는 것도 좋은 방법이다.
@Subselect
@Subselect
는 쿼리 결과를@Entity
로 매핑할 수 있는 유용한 기능이다.@Subselect
를 통해 조회 쿼리 자체를 값으로 가지며 하이버네이트는 select 쿼리의 결과를 매핑할 테이블처럼 사용한다.하지만,
@Entity
는 변경 감지 기능이 동작하기 때문에 해당 모델의 수정을 막기위해@Immutable
을 사용한다.@Entity @Immutable @Subselect( """ select o.order_number as number, o.version, o.orderer_id, o.orderer_name, o.total_amounts, o.receiver_name, o.state, o.order_date, p.product_id, p.name as product_name from purchase_order o inner join order_line ol on o.order_number = ol.order_number cross join product p where ol.line_idx = 0 and ol.product_id = p.product_id""" ) @Synchronize({"purchase_order", "order_line", "product"}) public class OrderSummary { @Id private String number; private long version; @Column(name = "orderer_id") private String ordererId; @Column(name = "orderer_name") private String ordererName; @Column(name = "total_amounts") private int totalAmounts; @Column(name = "receiver_name") private String receiverName; private String state; @Column(name = "order_date") private LocalDateTime orderDate; @Column(name = "product_id") private String productId; @Column(name = "product_name") private String productName; // constructor, getter }
트랜잭션 문제와 관련한 아래 코드를 살펴보자.
// Order 엔티티를 찾아 무언가 변경 Order order = orderRepository.findById(orderId).orElseThrow(); order.change(); // 한 트랜잭션에 있어서 변경내역이 저장되지 않은 상태에서 조회 모델을 통해 조회 List<OrderDataDto> datas = orderDataRepository.findByUserId(userId);
위 코드에서는 order 엔티티에 대한 변경이 저장되지 않은 상태에서 조회 모델을 통해 조회한다.
이렇게 되면 변경한 order 정보가 조회 전용 모델에 적용되지 않는 이슈가 발생하는데, 이 이슈를
@Synchronize
로 해결한다.@Synchronize
는 해당 엔티티와 관련한 테이블을 기재하고 조회 모델을 가져오기 전 기재한 테이블에 변경이 발생하면 관련 내역을 먼저flush()
후 조회한다.
주의사항
- @Subselect는 명시된 쿼리를 from절의 서브쿼리로 실행시킨다. 이러한 형태의 쿼리가 발생된다는 점을 유의하고 사용하자.
'Book Lounge > 도메인 주도 개발 시작하기' 카테고리의 다른 글
7장 - 도메인 서비스 (0) | 2022.07.16 |
---|---|
6장 - 응용 서비스와 표현 영역 (0) | 2022.07.16 |
4장 리포지터리와 모델 구현 (0) | 2022.07.09 |
3장 애그리거트 (0) | 2022.07.02 |
2장 아키텍처 개요 (0) | 2022.07.01 |