#05장 리포지터리의 조회기능(JPA 중심)
최범균 저「DDD START!, 2016」를 읽고 정리하였습니다.
이 장에서 다룰 내용
스펙
JPA 스펙구현
정렬과 페이징
동적 인스턴스와 @Subselect
스펙
검색을 위한 스펙
애그리거트를 찾을 때 식별자를 이용하는 것 외에 여러 다양한 조건으로 애그리거트를 찾아야 할 때가 있다.
스펙을 사용해보자.
public interface Speficiation<T> {
public boolean isSatisfiedBy(T agg);
}구현 사례
// 특정 고객의 주문인 지 확인하는 스펙
public class OrdererSpec implements Specification<Order> {
private String ordererld;
public OrdererSpec(String ordererld) {
this.ordererld = ordererld;
}
public boolean isSatisfiedBy(Order agg) {
return agg.getOrdererId().getMemberId().getld().equals(ordererld);
}
}리포지터리가 메모리에 모든 애그리거트를 보관하고 있다면 다음과 같이
원하는 스펙을 생성해서 전달해 주기만하면 된다.
스펙 조합
스펙의 장점 = 조합
AND, OR 이용
전달 예제
JPA를 위한 스펙 구현
앞선 예제들은 모든 애그리거트를 조회한 다음에 스펙을 이용해서 걸러내는 방식을 사용했다. -> 성능 문제
JPA를 위한 스펙은 CriteriaBuilder와 Predicate를 이용해서 검색 조건을 구현할 수 있다.
JPA 스펙 구현
Specification 인터페이스
스펙 구현 예시
응용 서비스 코드 예시
별도 클래스에 스펙 생성 기능을 모을 경우
간결하게 스펙을 생성
AND/OR 스펙 조합을 위한 구현
생성자로 전달받은 Speicification 목록을 Predicate 목록으로 바꾸고 CriteriaBuilder의 and()와 or()를 사 용해서 새로운 Predicate# 생성
쉬운 생성을 위한 팩토리 클래스
조합해서 사용
스펙을 사용하는 JPA 리포지터리 구현
이제 남은 작업은 스펙을 사용하도록 리포지터리를 구현하는 것이다
스펙을 이용해서 검색을 수행하는 JPA 리포지터리 구현
정렬 구현
JPQL 사용시
정렬 순서를 지정하는 가장 쉬운 방법은 다음과 같이 문자열을 사용하는 것
문자열로 전달받은 정렬 값을 Order로 변환하는 코드 구현 예시
문자열을 Order로 변경해 주는 보조 클래스
toJpaOrder 메서드는 문자열이 다음과 같이 주어지면, 정렬된 JPA Order 객체를 생성할 것이다.
name desc -> cb.desc(root.get( "name" ))
customer.name asc -> cb.asc(root.get( "customer" ).get( "name" ))
JPQL의 order by 절을 생성하기 위한 JpaQueryUtils 클래스의 메서드
위 메소드 사용 코드
TIP
정렬 순서를 타입으로 구현하면 오류를 줄인다. 예, ‘des’나 ‘a_ding’과 같이 잘못된 문자열을 사용해도 오름차순으로 정렬한다.
페이징과 개수 구하기 구현
JPA 쿼리는 페이징 처리를 위해 setFirstResult()와 setMaxResults() 메서드 제공한다.
한 페이지에 보여줄 행 개수가 15개이고 보여줄 페이지 번호가 4라면 (46번째 행부터 보여줘야함)
함께 자주 사용되는 기능 = 전체 개수 구하기
Specification과 Criteria를 이용해서 특정 조건을 충족하는 애그리거트 개수 구하기
TIP
스프링 데이터 JPA 모듈을 이용하면 스팩, 정렬 순서, 페이징 을 자동으로 해준다. (인터페이스 작성만으로 끝남)
조회 전용 기능 구현
리포지터리 사용 부적합 예시
여러 애그리거트를 조합해서 한 화면에 보여주는 데이터 제공
각종 통계 데이터 제공
이런 기능은 조회 전용 쿼리로 처리해야 하는 것들이다.
JPA와 Hibernate를 사용하면 동적 인스턴스 생성, (Hibernate의) @Subselect 확장 기능, 네이티브 쿼리를 이용해서 조회 전용 기능을 구현할 수 있다.\
동적 인스턴스생성
JPA는 쿼리 결과에서 임의의 객체를 동적으로 생성할 수 있는 기능을 제공한다.
아래와 같은 조회 전용 모델을 만드는 이유는 표현 영역을 통해 사용자에게 데이터를 보여주기 위함이다.
동적 인스턴스의 장점으로 JPQL을 그대로 사용하므로 객체 기준으로 쿼리를 작성하 면서도 동시에 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터를 조회할 수 있다.
하이버네이트 @Subselect 사용
하이버네이트는 JPA 확장 기능으로 @Subselect를 제공한다. 이것은 쿼리 결과를 @Entity로 매핑할 수 있게 해준다.
@Immutable, @Subselect, ©Synchronize 이용하면 된다.
@Immutable 사용 이유: 뷰를 수정할 수 없듯이 @Subselect로 조회한 @Entity 역시 수정할 수 없다.
어기면, 매핑한 테이블이 없으므로 에러가 난다.
@Synchronize 사용 이유
대게, 트랜잭션을 커밋하는 시점에 변경사항을 DB에 반영한다. Order의 변경 내역을 아직 purchase_order 테 이블에 반영하지 않은 상태 에서 purchase_order 테 이블을 사용하는 OrderSummary를 조회하게 된다. 즉, OrderSummary에는 최신 값이 아닌 이전 값이 담기게 된다.
이런 문제를 해소한다.
아래 코드 참고
Order의 상태를 변경한 뒤에 OrderSummary를 조회
@Subselect를 사용해도 일반 @Entity와 같기 때문에 EntityManager#find(), JPQL, Criteria를 사용해서 조회할 수 있다는 것이 @Subselect의 장점이다.
서브쿼리를 사용하고 싶지 않다면 Native SQL 쿼리를 사용하거나 MyBatis와 같은 별도 매퍼를 사용해서 조회 기능을 구현해야 한다.
Last updated