[DDD START] 5장 스프링 데이터 JPA를 이용한 조회 기능

David Lee·2023년 12월 6일
0

DDD Start

목록 보기
5/12
post-thumbnail

이 포스트는 📔 도메인 주도 개발 시작하기 책을 읽고 공부한 내용을 정리한 포스트입니다.

5장. 스프링 데이터 JPA를 이용한 조회 기능

5.2 검색을 위한 스펙

검색 조건이 고정되고, 단순하담녀 특정 조건을 활용하여 조회하는 기능을 사용하면 되지만 다양한 검색 조건을 조합해야 하는 경우에는 어떻게 해야 할까?

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

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

위 코드를 리포지터리에 사용하면 agg는 애그리거트 루트가 되고, DAO에 사용하면 agg는 검색 결과로 리턴할 데이터 객체가 된다.

즉, 스펙은 검색 대상을 걸러내는 용도로써 활용되는데 실제 구현은 사용하는 기술에 맞춰 구현이 된다.

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

Spring Data 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) {}
	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 criteriaBuilder);

	static <T> Specification<T> allOf(Iterable<Specification<T>> specifications) {}
	@SafeVarargs
	static <T> Specification<T> allOf(Specification<T>... specifications) {}
	static <T> Specification<T> anyOf(Iterable<Specification<T>> specifications) {}
	@SafeVarargs
	static <T> Specification<T> anyOf(Specification<T>... specifications) {}
}

Specification<T>를 구현하는 클래스에서 toPredicate()를 활용하여 JPA에서 조건을 표현하는 Predicate를 생성한다.

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

스펙을 충족하는 엔티티를 검색하기 위해서는 리포지터리나 DAO를 구현하는 과정에서 아래와 같은 인터페이스를 상속해야 한다.

// source from Spring Data JPA Reference Docs.
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {}

이렇게 구현된 리포지터리나 DAO는 List<T> findAll(Specification<T> spec);을 활용하여 스펙을 충족하는 엔티티를 찾을 수 있다.

5.5 스펙 조합

스펙을 조합할 수 있는 메서드는 위의 인터페이스의 메소드 중 and(), or(), allOf(), anyOf() 4가지가 존재한다.

  • and()
    두 스펙을 모두 충족하는 조건을 표현하는 스펙을 생성한다.

  • or()
    두 스펙 중 하나 이상 충족하는 스펙을 생성한다.

allOf()anyOf()의 경우 Spring Data JPA 3.0 이상 버전에서 추가된 내용이다.

  • allOf()
    전달된 모든 스펙을 모두 충족하는 조건을 표현하는 스펙을 생성한다.

  • anyOf()
    전달된 모든 스펙 중 하나 이상 충족하는 스펙을 생성한다.

이외 Specification<T> 에서 제공하는 not() 메서드의 경우는 조건을 반대로 적용할 때, where() 메서드의 경우 null 여부 검증을 진행하는 메서드로서 사용한다.

5.6 정렬 지정하기

Spring Data JPA에서 제공하는 정렬 방식은 아래와 같다

  • 메서드 이름에 OrderBy를 사용해 정렬 기준 지정
    예를 들어 아래와 같은 UserRepository 인터페이스가 있다면

    public interface UserRepository extends JPARepository<User, Long> {
        List<User> findByAddressOredrByUsernameDesc(String address);
    }

    findByUserIdOredrByUsernameDesc 메서드는 다음과 같은 조회 쿼리를 날린다.

    • address 프로퍼티 기준 검색 조건 지정
    • username 프로퍼티 값 역순(DESC)으로 정렬
  • Sort를 인자로 전달.
    Spring 에서 제공하는 Sort 타입을 활용하여 메서드를 전달한다.

    public interface UserRepository extends JPARepository<User, Long> {
        List<User> findByAddress(String address, Sort sort);
    }

    파라미터로 전달된 sort 객체의 값에 따라 정렬 쿼리를 생성해 준다.

5.7 페이징 처리하기

Pagination과 관련된 내용을 Spring에서 Pagination을 적용해보자! 포스트에 조금 더 자세히 정리해두었다.

목록을 보여줄 때 전체 데이터 중 일부만 보여주는 페이징을 Spring Data JPA에서는 Pageable 타입을 활용하여 지원해준다.

Pageable을 사용하는 메서드의 리턴 타입이 Page인 경우 JPA는 목록 조회 쿼리와 함깨 COUNT 쿼리도 실행해서 조건에 해당하는 데이터 개수를 구하지만 이러한 많은 양에 데이터에 대한 COUNT 쿼리는 성능 저하의 원인이 될 수 있다.
리턴 타입이 List인 경우는 페이징 처리와 관련된 정보를 제공하지 않음으로 COUNT 쿼리를 실행하지 않는다.

스펙을 사용하는 findAll 메서드는 리턴 타입에 상관 없이 COUNT 쿼리를 실행한다. 이를 해결하기 위해서는 커스텀 리포지터리 기능을 직접 구현해야 한다.

이외에 메서드를 생성하는 과정에서 N개의 데이터만 사용하는 메서드를 만들고자 한다면 find(First|Top)N 형식의 메서드를 사용하면 된다.

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

스펙에 조건을 활용해야 하는 경우 SpecBuilder 클래스를 활용해서 조건과 스펙을 조합해 사용할 수 있다.

5.9 동적 인스턴스 생성

JPQL을 활용해서 임의의 객체를 쿼리의 결과에서 동적으로 생성할 수 있다.
이와 같은 동적 인스턴스의 장점은 JPQL을 그대로 사용하므로 객체 기준으로 쿼리를 작성하면서도 동시에 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터를 조회할 수 있다는 점 이다.

5.10 하이버네이트 @Subselect 사용

하이버네이트의 @Subselect 기능을 활용해서 쿼리 결과를 @Entity로 매핑할 수 있다.

@Subselect은 조회 쿼리(SELECT Query)를 값으로 갖기 때문에 이를 활용하여 조회한 @Entity는 수정할 수 없다.
하지만 @Entity 매핑 필드를 수정한다면 변경 내역 반영을 위한 update 쿼리가 전송되고 매핑 테이블이 존재하지 않아 에러가 발생한다. 이를 해결하기 위해 @Immutable 어노테이션을 활용하는데, 위 어노테이션을 사용하면 하이버네이트는 변경 내역이 발생해도 DB반영을 하지 않고 무시한다.
이 외에도 @Synchronize 어노테이션을 활용하여 영속성 반영 내역에 대한 최신화 이후 데이터를 불러오게 하는 방식도 사용한다.

profile
쌓아가기

0개의 댓글

관련 채용 정보