Skip to content

Latest commit

 

History

History
518 lines (430 loc) · 26.7 KB

File metadata and controls

518 lines (430 loc) · 26.7 KB

실전! 스프링 데이터 JPA

프로젝트 환경설정

H2 데이터베이스 설치

  • 라이브러리에 맞는 버전을 설치

  • db 파일로 접근

    • url: jdbc:h2:~/datajpa
    • 이 방법은 직접적으로 파일에 접근했기 떄문에 lock이 걸려 한 번에 여러 곳에서 접근할 수 없음
  • 원격으로 접근

    • url: jdbc:h2:tcp://localhost/~/datajpa

스프링 데이터 JPA와 DB 설정, 동작확인

  • application.yml 설정
  • 기본 엔티티 생성 및 테스트 코드 작성
  • jpa vs spring-data-jpa 맛보기 코드비교

예제 도메인 모델

  • Member, Team 생성 및 연관관계 설정
  • 생성자 레벨 Protected로 설정
  • ToString은 연관관계가 설정되어 있지 않은 필드만 출력

공통 인터페이스 기능

순수 JPA 기반 리포지토리 만들기

  • 기본 CRUD

    • 저장
    • 변경 > 변경감지 사용
    • 삭제
    • 전체 조회
    • 단건 조회
    • 카운트
  • 참고

    • JPA에서 수정은 변경감지 기능을 사용
    • 트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경감지 기능이 작동해서 변경된 엔티티를 감지하고 UPDATE SQL을 실행
  • 확인사항

    • JPA 코드로 기본 CRUD 작성 및 테스트
    • 지연로딩을 이용한 수정 테스트

스프링 데이터 JPA 공통 인터페이스 설정

  • 스프링부트 사용시 @SpringBootApplication 위치를 지정(해당패키지와 하위 패키지 인식)

  • 만약 위치가 달라지면 @EnableJpaRepositories 필요

  • org.springframework.data.repository.Repository 를 구현한 클래스는 스캔 대상

    • MemberRepository 인터페이스가 동작한 이유
    • 실제 출력해보기(Proxy)
    • memberRepository.getClass() class com.sun.proxy.$ProxyXXX
  • @Repository 애노테이션 생략 가능

    • 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리
    • JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리

스프링 데이터 JPA 공통 인터페이스 적용

  • 위 JPA 코드로 작성한 내용을 Spring-data-jpa에서 제공하는 JpaRepository를 사용하여 구현
  • JpaRepository<T, ID>
    • T: 엔티티 타입
    • ID: 식별자 타입(PK)

스프링 데이터 JPA 공통 인터페이스 분석

  • JpaRepository

    • data.jpa 패키지 하위에 존재하는 인터페이스
    • 대부분의 공통 메서드를 제공
  • PagingAndSortingRepository

    • data 패키지 하위에 존재하는 인터페이스
  • 수정 사항 확인

    • T findOne(ID) -> Optional<T> findById(ID) 변경
  • 제네릭 타입 확인

    • T: 엔티티
    • ID: 엔티티의 식별자 타입
    • S: 엔티티와 그 자식 타입
  • 주요 메서드

    메서드명 설명
    save(S) 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
    delete(T) 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
    findById(ID) 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
    getOne(ID) 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출
    findAll(...) 모든 엔티티를 조회한다. 정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공할수 있다.

쿼리 메서드 기능

  • 여러 기능

    • 메소드 이름으로 쿼리 생성
    • NamedQuery
    • @Query - 리파지토리 메소드에 쿼리 정의 파라미터 바인딩
    • 반환 타입
    • 페이징과 정렬
    • 벌크성 수정 쿼리
    • @EntityGraph
  • 쿼리 메소드 기능 3가지

    • 메소드 이름으로 쿼리 생성
    • 메소드 이름으로 JPA NamedQuery 호출
    • @Query 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의

메소드 이름으로 쿼리 생성

JPA NamedQuery

  • @NamedQuery 어노테이션으로 Named 쿼리 정의

  • JPA기반 쿼리 테스트

  • Spring Data JPA 기반 쿼리 테스트

    • @Query를 생략하고 메서드 이름만으로 Named쿼리를 호출할 수 있다.
  • 스프링 데이터 JPA는 선언한 도메인 클래스 + .(점) + 메서드 이름으로 Named 쿼리를 찾아서 실행

  • 만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.

  • 필요하면 전략을 변경할 수 있지만 권장하지 않는다.

  • 주의

    • 스프링 데이터 JPA를 사용하면 실무에서 NamedQuery를 직접 등록해서 사용하는 일은 드물다.
    • 대신 @Query를 사용해서 리파지토리 메소드에 쿼리를 직접 정의한다.

@Query, 리포지토리 메소드에 쿼리 정의하기

  • @org.springframework.data.jpa.repository.Query 어노테이션을 사용

  • 실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음

  • JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)

  • 실무 환경

    • 메서드 이름으로 쿼리 생성 기능은 파라미터가 증가하면 메서드 이름이 지저분해진다.
    • 따라서 @Query 기능을 더 선호한다.
  • 단순 값 하나 조회

    • JPA 값 타입 @Embedded도 이 방식으로 조회가 가능
  • DTO로 직접 조회

    • DTO로 직접 조회 하려면 JPA의 new 명령어를 사용
    • 생성자가 맞는 DTO가 필요
  • 파라미터 바인딩

    • 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용
  • 컬렉션 파라미터 바인딩

    • Collection 타입으로 in절 지원

[반환타입](https://docs.spring.io/spring-data/jpa/docs/current/reference/ html/#repository-query-return-types)

  • 조회 결과 값에 따른 방식

    • 컬렉션
      • 결과 없음 -> 빈 컬렉션
    • 단건 조회
      • 결과 없음 -> null
      • 결과 2건 이상: javax.persistence.NonUniqueResultException 예외 발생
  • 참고

    • 단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출한다.
    • 이 메서드를 호출했을 때 조회 결과가 없으면 javax.persistence.NoResultException 예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편하다.
    • 스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null 을 반환한다.

순수 JPA 페이징과 정렬

  • JPA 페이징

    • 페이징에 대한 개념을 가지고 설계 해야하는 부담이 있다.
  • 스프링 데이터 JPA

    • Page, Slice와 같은 인터페이스를 제공

      • 페이징과 정렬 파라미터
        • Sort: 정렬기능
        • Pageable: 페이징 기능 (내부에 Sort 포함)
      • 특별한 반환 타입
        • Page: 추가 count 쿼리 결과를 포함하는 페이징
        • Slice: 추가 count 쿼리 없이 다음 페이지만 확인 가능(내부적으로 limit + 1 조회)
        • List: 추가 count쿼리 없이 결과만 반환
    • PageRequest 클래스를 활용하여 조건을 만들어 Repository로 전달하여 사용

      • Jpa가 두 번째 파라미터로 Pageable 인터페이스를 제공
      • Page는 0부터 시작하기때문에 주의
    • 페이징에 성능상 문제가 되는경우 @Query(value = "{...}", countQuery = "{...}")를 사용하여 분리가 가능하다.

      • 페이징에는 Join이 필요없음에도 기본 쿼리가 Join을 하여 페이징에 부하가 있는 경우
      • 복잡한 sql에서 사용, 데이터는 join으로 가져와야하나, count는 join이 필요없는 경우
    • 페이징에서도 Entity는 API 로 바로 노출하면 문제가 발생하기 때문에 DTO로 변환하는 작업은 필수

  • 정리

    • 사용자의 요청 쿼리를 PageRequest로 만들어 JPA로 전달
    • 기본은 Page를 사용, 더보기와 같은 버튼이 있는 페이지의 경우 Slice를 사용하도록한다.
    • Page를 사용하던 Slice를 사용하던 dto로 변환하는 것을 잊지 말 것
    • JPA의 페이징 인터페이스를 사용하여 domain 개발에 집중

벌크성 수정 쿼리

  • Jpa 기반 bulk 수정 쿼리

    • executeUpdate()으로 호출
    • 영속성 컨텍스트를 초기화 EntityManager.clear()
  • 스프링 데이터 JPA 기반 bulk 수정 쿼리

    • bulk성 쿼리 설정

      • @Modifying을 사용하여 bulk 성 수정 및 삭제 쿼리를 실행하게 하는 어노테이션
        • 해당 어노테이션이 설정되어야 executeUpdate() 메서드로 실행이 됨
        • 해당 어노테이션이 없으면 예외를 발생시킴
      • @Modifying(clearAutomatically = true) 벌크성 쿼리를 실행한 뒤 영속성 컨텍스트 초기화를 하도록 한다.
    • bulk성 쿼리의 주의사항

      • bulk 연산은 영속성 컨텍스트를 무시하고 DB에 직접 실행하기 때문에, 영속성 컨텍스트에 있는 엔티티의 상태와 DB 엔티티 상태가 달라질 수 있다.
      • @Modifying를 사용하지 않는 경우 QueryExecutionRequestException 예외 발생
      • 또한 영속성 컨텍스트를 초기화하지 않는 경우 해당 데이터를 다시 조회하면 영속성 컨텍스트에 값이 남아 있어 문제가 될 수 있다.
      • 조회가 필요한 경우 영속성 컨텍스트를 필히 초기화 해야한다.
    • bulk 연산 권장 방법

      • 영속성 컨텍스트에 엔티티가 없는 상태에서 bulk 연산을 먼저 실행
      • 영속성 컨텍스트에 엔티티가 있는 경우 bulk 연산 직후 영속성 컨텍스트를 초기화

@EntityGraph (Fetch Join의 간소화 버전)

  • 연관된 엔티티들을 SQL 한 번에 조회하는 방법

    • Fetch Join과 같은 방식?
  • 기존 방법

    • Hibernate5Module에 기반하여 Proxy객체를 초기화하여 값을 세팅
    • 1:N 쿼리 조회 시 1을 먼저 조회 후 N에 대해서 지연로딩을 수행
  • 가장 기본적인 방식 hibernate 모듈을 통한 지연로딩 대처 방법 (강의에 없는 내용)

    • 지연로딩 여부를 확인
      • hibernate 메서드 사용
        • Hibernate.isInitialized(member.getTeam())
      • JPA 표준으로 확인하는 방법
        • em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(member.getTeam())
    • 결국 엔티티를 한번에 조회하기 위해서는 Fetch Join이 필요함
  • Fetch Join으로 지연로딩에 대처하는방법

    • @Query("select ... from ... join fetch ...")
    • 연관된 엔티티를 한 번에 조회할 수 있도록 Fetch Join을 사용
    • 하지만 이는 outer join으로 조회되기 때문에 중복 데이터가 존재
  • @EntityGraph를 사용하여 지연로딩에 대처하는 방법

    • Fetch JoinAnnotation 버전
    • left outer join 실행
  • @NamedEntityGraph 설정을 통한 지연로딩 대처방법

    • Entity 설정
      • @NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
    • Repository 설정
      • @EntityGraph("Member.all")
  • 정리

    • 간단한 fetch join이 필요한 부분에서는 @EntityGraph
    • 조금 복잡해지면 JPQL fetch join을 활용

JPA Hint & Lock (사용하려는 경우 깊은 이해가 필요)

  • database의 hint가 아님
  • 데이터를 오직 조회하는 기능에 dirty check 기능을 비활성화 하도록 하는 설정
  • 기본적으로 데이터를 저장하고 영속성 컨텍스트를 비운 뒤, 데이터를 새로 조회했을 때 db의 데이터를 조회 해 1차 캐시에 데이터를 저장하고 그 상태를 감지하게 되는데 단순히 조회만 하는 기능에서는 해당 작업이 비효율적이게 된다. 해당 기능을 통해 dirty checking을 하지 않도록하여 효율적으로 관리 하는 방법
  • 반환 타입을 Page 인터페이스로 사용하는 경우에도 페이징을 위한 count 쿼리에도 힌트 적용 가능

확장 기능

사용자 정의 인터페이스 (실무에서 많이 씀)

  • 기본 프로세스

    • 스프링 데이터 JPA 리포지토리는 인터페이스만 정의하고 구현체는 스프링이 자동 생성
  • Custom 인터페이스 구현 선택 사항

    • JPA 직접 사용( EntityManager )
    • 스프링 JDBC Template 사용
    • MyBatis 사용
    • 데이터베이스 커넥션 직접 사용 등등...
    • Querydsl 사용
  • 실무에서는 주로 QueryDSL이나 SpringJdbcTemplate을 함께 사용할 때 사용자 정의 리포지토리 기능을 사용한다.

  • MemberQueryRepository 를 인터페이스가 아닌 클래스로 만들고 스프링 빈으로 등록하여 직접 사용해도 된다. (이때 스프링 데이터 JPA와는 관계 없이 별도로 동작한다.)

  • 스프링 데이터 2.x 버전 사용방식

    • 사용자 정의 구현 클래스에 리포지토리 인터페이스 이름 + Impl 을 적용하는 대신 사용자 정의 인터페이스 명 + Impl 방식도 지원
    • MemberRepositoryImpl -> MemberRepositoryCustomImpl으로도 적용 가능
    /* 이전 방식 */
    class MemberRepositoryImpl implements MemberRepositoryCustom {
      // ...
    }
    /* spring data 2.x 이상 방식 */
    class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
      // ...
    }
  • 정리

    • Custom으로 추가되는 기능들이 핵심 비즈니스인지 화면에 출력되는 내용인지 확실히 분류하여 클래스별로 설계할 수 있는 능력이 필요

Auditing

  • 엔티티 생성, 변경의 작성자 및 시간을 관리하는 방식

    • JPA 기반 관리 방법

      • @MappedSuperclass
      • @PrePersist, @PostPersist
      • @PreUpdate, @PostUpdate
    • 스프링 데이터 JPA 기반 관리 방법

      • @EnableJpaAuditing 스프링 부트 설정 클래스에 적용
      • @EntityListeners(AuditingEntityListener.class) 엔티티에 적용
      • @CreatedDate 엔티티 최초 등록일
      • @LastModifiedDate 엔티티 최종 수정일
      • @CreatedBy 엔티티 최초 등록자
      • @LastModifiedBy 엔티티 최종 수정자
  • 실무에서 사용하는 방법

    • 기본적으로 Entity의 이력 정보를 확인하기 위해서는 무조건 필요한 필드
    • 다만 등록일, 수정일은 어떤 엔티티든 필수인데 등록자, 수정자의 경우 필요하지 않은 경우가 존재하기 때문에 분리하여 사용
    • BaseEntity, BaseTimeEntity 클래스로 구분하여 필요한 엔티티에서 사용
    • JPA의 이벤트를 스프링 데이터가 제공해주기 때문에 도메인 코드를 줄일 수 있다.
  • @EntityListeners(AuditingEntityListener.class)를 설정하지 않고 글로벌로 설정하는 방법

    • META-INF/orm.xml
    <?xml version=“1.0” encoding="UTF-8”?>
        <entity-mappings xmlns=“http://xmlns.jcp.org/xml/ns/persistence/orm”
                         xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
                         xsi:schemaLocation=“http://xmlns.jcp.org/xml/ns/persistence/
        orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd”
                         version=“2.2">
            <persistence-unit-metadata>
                <persistence-unit-defaults>
                    <entity-listeners>
                        <entity-listener
        class="org.springframework.data.jpa.domain.support.AuditingEntityListener”/>
                    </entity-listeners>
                </persistence-unit-defaults>
            </persistence-unit-metadata>
        </entity-mappings>

Web 확장 - 도메인 클래스 컨버터 (잘 안씀)

  • HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩

  • Http 기본 동작

    • view에서 controller로 넘어온 파라미터 값을 통해 repository에 파라미터 값을 넘겨 데이터를 조회
  • 도메인 클래스 컨버트를 사용하는 경우 동작

    • 파라미터로 엔티티를 받되 PathVariable로 설정된 파라미터를 해당 엔티티에서 가져와 엔티티의 Repository를 통해 데이터를 조회
    • Repository 로직을 skip할 수 있으나 트랜잭션이 없는 범위에서 엔티티를 조회 하므로 엔티티를 변경했을 때 DB에 반영이 되지 않는다.
  • 두 방식의 차이점

    • HTTP 요청은 회원 id를 받지만 도메인 클래스 컨버터가 중간에 동작해서 회원 엔티티 객체를 반환
    • 도메인 클래스 컨버터도 리파지토리를 사용해서 엔티티를 찾음
  • 주의사항

    • 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 단순 조회용으로만 사용해야 한다.
    • (트랜잭션이 없는 범위에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다.)

Web 확장 - 페이징과 정렬

  • 페이징관련 인터페이스 & 클래스

    • Pageable 인터페이스
    • PageRequest 실제 사용 객체
  • 페이징 사용법

    /members?page=0&size=3&sort=id,desc&sort=username,desc
    • page: 현재 페이지, 0부터 시작한다.
    • size: 한 페이지에 노출할 데이터 건수
    • sort: 정렬 조건을 정의한다.
      • 예) 정렬 속성, 정렬 속성...(ASC | DESC), 정렬 방향을 변경하고 싶으면 sort 파라미터 추가 ( asc 생략 가능)
  • 페이징 설정

    • 글로벌 설정

      spring.data.web.pageable.default-page-size=20 /# 기본 페이지 사이즈/ 
      spring.data.web.pageable.max-page-size=2000 /# 최대 페이지 사이즈/
    • 개별 설정

      • @PageableDefault 어노테이션을 사용
          @PageableDefault(size = 12, sort = “username”, direction = Sort.Direction.DESC) Pageable pageable
    • 페이징 정보가 둘 이상인 경우 설정 방법

      • 페이징 정보가 둘 이상이면 접두사로 구분
      • @Qualifier 에 접두사명 추가 "{접두사명}_xxx”
      • 요청 쿼리스트링
        /members?member_page=0&order_page=1
      • 컨트롤러 파라미터 설정
        class MemberController {
            public String list(
                    @Qualifier("member") Pageable memberPageable,
                    @Qualifier("order") Pageable orderPageable, ...) {
                    // ...
            }
        }
    • Paging 커스텀

      • Page의 기본 값이 0인 것을 1부터 시작하는 방법
        • Pageable, Page를 파리미터와 응답 값으로 사용히자 않고, 직접 클래스를 만들어서 처리
        • spring.data.web.pageable.one-indexed-parameters 를 true 로 설정
          • 이 방법은 web에서 page 파라미터를 -1 처리 할 뿐, 응답 값 Page에는 0으로 되어 있음
          • 반환하는 pageable 객체의 응답값이 의도한 값으로 반환되지 않음
  • 페이징 결과값 반환

    • 엔티티를 API로 노출하면 다양한 문제가 발생
    • 엔티티를 꼭 DTO로 변환해서 반환해야 한다.
    • Page는 map() 을 지원해서 내부 데이터를 다른 것으로 변경할 수 있다.

스프링 데이터 JPA 분석

  • 기본 개념

    • @Repository

      • JPA 예외를 스프링이 추상화한 예외로 변환
    • @Transactional

      • JPA의 모든 변경트랜잭션 안에서 동작
      • 스프링 데이터 JPA는 변경(등록, 수정, 삭제) 메서드를 트랜잭션 처리
      • 서비스 계층에서 트랜잭션을 시작하지 않으면 리파지토리에서 트랜잭션 시작 -> 기본적으로 Repository@Transactional이 걸려있음
      • 서비스 계층에서 트랜잭션을 시작하면 리파지토리는 해당 트랜잭션을 전파 받아서 사용
      • 그래서 스프링 데이터 JPA를 사용할 때 트랜잭션이 없어도 데이터 등록, 변경이 가능했음 (사실은 트랜 잭션이 리포지토리 계층에 걸려있는 것임)
    • @Transactional(readOnly = true)

      • 데이터를 단순히 조회만하고 변경하지 않는 트랜잭션에서 readOnly = true 옵션을 사용하면 플러 시를 생략해서 약간의 성능 향상을 얻을 수 있음
  • JPA를 사용하여 데이터를 저장할 때 주의할 점

    • persist를 사용해야함
    • merge는 db에 데이터가 존재하는 지 select를 한번 조회하기 때문에 비효율적임
  • 임의의 Id를 사용하려는 경우

    • 기본적으로 식별자(@Id)객체인경우 null로 판단, 기본 타입인 경우 0으로 판단
    • 임의로 식별자를 채번하여 임의로 생성해야 하는 경우 -> Persistable 인터페이스를 사용하여 엔티티가 신규인지 여부를 판단
    • 실무에서는 사용하는 방법
      • 생성일자(createDate)가 존재하는 경우를 기준으로 신규 데이터인지 여부를 판단
  • Persistable

    • JPA 식별자 생성 전략이 @GenerateValue 면 save() 호출 시점에 식별자가 없으므로 새로운 엔티 티로 인식해서 정상 동작
    • JPA 식별자 생성 전략이 @Id 만 사용해서 직접 할당이면 이미 식별자 값이 있는 상태로 save() 를 호출
    • 이 경우 merge() 가 호출된다. merge()는 우선 DB를 호 출해서 값을 확인하고, DB에 값이 없으면 새로운 엔티티로 인지하므로 매우 비효율적임
    • Persistable 를 사용해서 새로운 엔티티 확인 여부를 직접 구현하게는 효과적이다.
    • 참고
      • 등록시간(@CreatedDate)을 조합해서 사용하면 이 필드로 새로운 엔티티 여부를 편리하게 확인할 수 있다. (@CreatedDate에 값이 없으면 새로운 엔티티로 판단)

나머지 기능들

Specifications (명세) - 유지보수가 힘든 코드

  • 실무에서는 JPA Criteria를 거의 안쓴다! 대신에 QueryDSL을 사용하자.

  • 스프링 데이터 JPA는 JPA Criteria를 활용하여 해당 개념을 사용할 수 있도록 Specification클래스를 지원

  • 동적 쿼리를 대처하기 위한 방법으로 사용

  • 술어(predicate)

    • 참 또는 거짓으로 평가
    • AND OR 같은 연산자로 조합해서 다양한 검색조건을 쉽게 생성(컴포지트 패턴)
    • 예) 검색 조건 하나하나
    • 스프링 데이터 JPA는 org.springframework.data.jpa.domain.Specification 클래스로 정의
  • 명시 기능 사용방법

    • RepositoryJpaSpecificationExecutor<Member> 상속
    • 조회 쿼리 메서드에 Specification 파라미터로 받아 검색 조건으로 사용
    • 검색 조건에 대한 Spec을 미리 정의 해두어야 함

QueryBy

  • 사용 방식

    • Probe: 필드에 데이터가 있는 실제 도메인 객체
    • ExampleMatcher: 특정 필드를 일치시키는 상세한 정보 제공, 재사용 가능
    • Example: ProbeExampleMatcher로 구성, 쿼리를 생성하는데 사용
  • 장점

    • 동적 쿼리를 편하게 처리하는 방법
    • 도메인 객체를 그대로 사용하는 것이 큰 장점
    • 데이터 저장소를 RDB에서 NOSQL로 변경해도 코드 변경이 없게 추상화 되어 있음 (spring data 패키지 내에 존재하기 때문)
    • 스프링 데이터 JPA JpaRepository 인터페이스에 이미 포함
  • 단점

    • 조인은 가능하나 내부 조인만 가능 ( outer join에서 제약 )
    • 중첩 제약조건 쿼리에 제한
    • 매칭 조건이 단순한 것만 가능 (정확하게 매칭되는 것만 가능)
  • 정리

    • 좋은 기술처럼 보이지만 쿼리의 중요한 기능 들을 제공하지 않아 사용을 해야하는 선택을 해야할지에 대해서 생각해야 한다.

Projections

  • 복잡한 쿼리를 해결하기에는 한계가 있다. 실무에서는 단순할 때만 사용하고, 조금만 복잡해지면 QueryDSL을 사용하자

  • 사용 용도

    • 엔티티 대신에 DTO를 편리하게 조회할 때 사용
    • 전체 엔티티가 아니라 만약 회원 이름만 딱 조회하고 싶은경우
  • 주의사항

    • 프로젝션 대상이 root 엔티티인 경우, JPQL SELECT 절 최적화가 가능하다.
    • 프로젝션 대상이 root가 아닌경우
      • left outer join 처리하기 때문에 모든 필드를 select 해서 엔티티로 조회한 다음에 계산
      • 최적화가 하기 힘듬
  • 정리

    • 프로젝션 대상이 ROOT 엔티티인 경우 유용
    • 프로젝션 대상이 ROOT 엔티티를 넘어가면 JPQL SELECT 최적화기 되지 않는다.
    • 실무의 복잡한 쿼리를 해결하기에 한계가 있다.

Native Query

  • 최후의 수단으로 사용하는 수단중 하나

    • Native Query 또는 Projections 이라고는 하나 QueryDSL로 못할 수가 없다.
    • 한번에 못가져올거면 차라리 두 세번에 걸쳐서 가져올것..
  • 스프링 데이터 JPA Native Query

    • 페이징 지원
    • 반환 타입
      • Object[]
      • Tuple
      • DTO (스프링 데이터 인터페이스 Projections 지원)
    • 제약 사항
      • sort 파라미터를 통한 정렬이 정상 동작하지 않을 수도 있다.
      • JPQL처럼 애플리케이션 로딩 시점에 문법 확인 불가
      • 동적 쿼리 불가
  • 위치기반 파라미터

    • JPQL의 경우 1부터 시작, Native Query의 경우 0부터 시작
  • DTO 변환 시

    • DTO 대신 JPA TUPLE 조회
    • DTO 대신 MAP 조회
    • @SqlResultSetMapping 복잡
    • Hibernate ResultTransformer를 사용해야함 복잡
    • 프로젝션으로 DTO 쿼리 만드는 방법
    • 네이티브 SQL을 DTO로 조회할 때는 JdbcTemplate or MyBatis 권장

참고