Chapter 5: 스프링 데이터 JPA를 이용한 조회 기능

💡 책에서 기억하고 싶은 내용

검색을 위한 스펙

  • 스펙 (Specification)

    • 검색 조건을 다양항게 조합해야 할 때 사용할 수 있는 것

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

    • ex)

      public interface Specification<T> {
        public boolean isSatisfiedBy(T agg);
      }
      • isSatisfiedBy() method의 agg 파라미터는 검사 대상 이 되는 객체다

      • 스펙을 리포지터리 에 사용하면 agg는 애그리거트 루트가 되고,

      • 스펙을 DAO 에 사용하면 agg는 검색 결과로 리턴할 데이터 객체가 된다

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

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

  • 스펙 인터페이스는 함수형 인터페이스 이므로 람다식을 이용해서 객체를 생성할 수 있다

JPA 정적 메타 모델

  • 정적 메타 모델은 @StaticMetamodel annotation을 이용해서 관련 모델을 지정한다

  • 메타 모델 클래스는 모델 클래스의 이름 뒤에 ‘_’ 을 붙인 이름을 갖는다

  • 정적 메타 모델 클래스는 대상 모델의 각 프로퍼티와 동일한 이름을 갖는 정적 필드 를 정의한다

    • 이 정적 필드는 프로퍼티에 대한 메타 모델로서, 프로퍼티 타입에 따라 SingularAttribute , ListAttribute 등의 타입을 사용해서 메타 모델을 정의한다

  • 정적 메타 모델 클래스를 직접 작성할 수 있지만, Hibernate와 같은 JPA Provider는 정적 메타 모델을 생성하는 도구를 제공하고 있으므로 이들 도구를 사용하면 편리하다

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

  • 스펙을 충족하는 엔티티들 검색하고 싶다면, findAll() method를 사용하면 된다

    1. 스펙 객체를 생성하고

    2. findAll() method를 이용해서 검색

      • findAll() method는 스펙 인터페이스 를 파라미터로 갖는다

스펙 조합

  • 스프링 데이터 JPA가 제공하는 스펙 인터페이스는 스펙을 조합할 수 있는 두 method를 제공하고 있다

    • and()or()

  • and()or() method는 기본 구현을 가진 default method 이다

    • and() method는 두 스펙을 모두 충족하는 조건을 표현하는 스펙을 생성하고,

    • or() method는 두 스펙 중 하나 이상 충족하는 조건을 표현하는 스펙을 생성한다

정렬 지정하기

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

    1. method 이름에 OrderBy 를 사용해서 정렬 기준 지정

    2. Sort를 인자로 전달

페이징 처리하기

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

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

  • Pageable 타입은 인터페이스로, 실제 Pageable 타입 객체는 PageRequest class를 이용해서 생성한다

    • ex)

      import org.springframework.data.domain.PageRequest
      
      PageRequest pageReq = PageRequest.of(1, 10);
      List<MemberData> user = memberDataDao.findByNameLike("chloe%", pageReq);
      • PageRequest.of() method의 첫 번째 인자는 페이지 번호를, 두 번째 인자는 한 페이지의 개수 를 의미한다

      • 페이지 번호는 0번부터 시작하므로, 위 코드는 한 페이지에 10개씩 표시한다고 했을 때 두번째 페이지를 조회한다.

        • 즉, 11번째부터 20번째까지 데이터를 조회한다

  • PageRequestSort 를 사용하면 정렬 순서를 지정할 수 있다

    • ex)

      Sort sort = Sort.by("name").descending();
      PageRequest pageReq = PageRequest.of(1, 2, sort);
      List<MemberData> user = memberDataDao.findByNameLike("chloe%", pageReq);
  • Pageable 을 사용하는 method의 return type이 Page 일 경우, 스프링 데이터 JPA는 목록 조회 쿼리와 함께 COUNT 쿼리도 실행해서 조건에 해당하는 데이터 개수를 구한다

  • Page 는 전체 개수, 페이지 개수 등 페이징 처리에 필요한 데이터도 함께 제공한다

    • Page가 제공하는 method 다수 있음!

  • 프로퍼티를 비교하는 findBy프로퍼티() 형식의 method는 Pagable 타입을 사용하더라도 리턴 타입이 List 면 COUNT 쿼리를 실행하지 않는다

    • 페이징 처리와 관련된 정보가 필요 없다면, Page 리턴 타입이 아닌 List를 사용해서 불필요한 COUNT 쿼리를 실행하지 않도록 한다

  • 반면, 스펙 을 사용하는 findAll() method에 Pageable 타입을 사용하면, 리턴 타입이 Page가 아니어도 COUNT 쿼리를 실행한다

    • 즉, 페이지 관련 정보가 필요 없더라도 COUNT 쿼리를 실행한다

  • 만약 처음부터 N개의 데이터가 필요하다면, Pageable 을 사용하지 않고 findFirstN() 형식의 method를 사용할 수도 있다

    • First 대신 Top 을 사용해도 된다

    • FirstTop 뒤에 숫자가 없으면 한 개 결과만 리턴한다

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

  • 스펙을 사용하다보면 조건에 따라 스펙을 조합해야 할 때가 있다

    • 그 때 스팩 빌더 를 만들어 사용한다

  • 스펙 빌더에는 and() , ifHasText() , ifTrue() method가 있는데, 이 외에 필요한 method를 추가해서 사용하면 된다

동적 인스턴스 생성

  • JPA는 쿼리 결과에서 임의의 객체를 동적으로 생성 할 수 있는 기능을 제공하고 있다

  • JPQL의 select 절에 new 키워드를 사용하고, new 키워드 뒤에 생성할 인스턴스의 완전한 클래스 이름을 지정하고 괄호 안에 생성자에 인자로 전달할 값을 지정한다

  • 동적 인스턴스의 장점

    • JPQL을 그대로 사용하므로 객체 기준 으로 쿼리를 작성하면서도, 동시에 지연/즉시 로딩 과 같은 고민 없이 원하는 모습으로 데이터를 조회할 수 있다

하이버네이트 @Subselect 사용

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

    • @Subselect 쿼리 결과를 @Entity 로 매핑해준다

  • @Immutable , @Subselect , @Synchronize 는 하이버네이트 전용 annotation인데 이 태그를 사용하면 테이블이 아닌 쿼리 결과@Entity 로 매핑할 수 있다

  • @Subselect 는 SELECT 쿼리를 값으로 갖는다

    • 하이버네이트는 이 SELECT 쿼리의 결과를 매핑할 테이블처럼 사용한다

    • DBMS가 여러 테이블을 조인해서 조회한 결과를 한 테이블처럼 보여주기 위한 용도로 뷰를 사용하는 것처럼, @Subselect 를 사용하면 쿼리 실행 결과매핑할 테이블처럼 사용한다

  • @Immutable

    • 뷰를 수정할 수 없듯이 @Subselect 로 조회한 @Entity 역시 수정할 수 없다

      • 실수로 @Subselect 를 이용한 @Entity 의 매핑 필드를 수정하면, 하이버네이트는 변경 내역을 반영하는 UPDATE 쿼리를 실행한다

        • But, 매핑 한 테이블이 없으므로 에러가 발생한다

    • 이런 문제를 방지하기 위해 @Immutable 을 사용한다

      • @Immutable 을 사용하면 하이버네이트는 해당 엔티티의 매핑 필드 / 프로퍼티가 변경되어도 DB에 반영하지 않고 무시한다

  • @Synchronize

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

    • 하이버네이트는 엔티티를 로딩하기 전에 지정한 테이블과 관련된 변경이 발생하면 FLUSH를 먼저 한다

    • @Synchronize 가 지정하고 있는 테이블에 변경이 발생하면, 관련 내역을 먼저 FLUSH 한다

    • 따라서 해당 class를 로딩하는 시점에서는 변경된 내역이 반영된다

  • @Subselect

    • @Subselect 를 사용해도 일반 @Entity 와 같기 때문에 EntityManager#find() , JPQL , Criteria 를 사용해서 조회할 수 있다는 것이 @Subselect 의 장점이다

      • 스펙도 사용할 수 있다

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

    • 서브 쿼리를 사용하고 싶지 않다면, 네이티브 SQL 쿼리를 사용하거나 MyBatis 와 같은 별도 mapper 를 사용해서 조회 기능을 구현해야 한다

Last updated