본문 바로가기

Advance I/Springboot & JPA

22.06.21

API 개발 고급

등록이나 수정은 거의 성능문제가 발생하지 않는다.

(단순하게 한 건 등록, 한 건 수정)

조회 API 최적화를 중점적으로

조회용 샘플 데이터 입력

PostConstruct 내부에는 Transaction 작업이 어렵다.

따라서 Transaction 작업을 하는 별도의 Bean(@Component)을 생성한다.

 

<단축키 정리>

Extract Method: Ctrl + Alt + M

Extract Parameter: Ctrl + Alt + P

Rename: Shift + F6

Search everywhere: Double Shift

Go to declaration: Ctrl + B, Ctrl + Click

참고: IntelliJIDEA_ReferenceCard.pdf (jetbrains.com)

지연 로딩과 조회 성능 최적화

주문(Order) + 배송정보(Delivery) + 회원(Member)을 조회하는 API

지연 로딩 때문에 발생하는 n+1 문제 해결

 

매우 중요!! 100% 이해할 것

 

간단한 주문 조회 V1: 엔티티를 직접 노출

절대 X

양방향 연관관계에 의한 무한루프

-> 둘 중 하나를 @JsonIgnore

-> 지연 로딩으로 인한 오류

    jackson 라이브러리는 기본적으로 프록시 객체(ByteBuddy)를 json 처리 불가

-> implementation Hibernate5Module(jackson-datatype-hibernate5) 

    Hibernate5Module을 스프링 빈으로 등록. 초기화 된 프록시 객체만 노출시킴

    order.getMember().getName() //Lazy 강제 초기화

    order.getDelivery().getAddress() //Lazy 강제 초기화

-> API 스펙에 불필요한 엔티티 정보 노출 

 

참고: 앞에서 계속 강조했듯이 정말 간단한 애플리케이션이 아니면 엔티티를 API 응답으로 외부로 노출하는 것은 좋지 않다. 따라서 Hibernate5Module 를 사용하기 보다는 DTO로 변환해서 반환하는 것이 더 좋은 방법이다.

 

주의: 지연 로딩(LAZY)을 피하기 위해 즉시 로딩(EARGR)으로 설정하면 안된다! 즉시 로딩 때문에 연관관계가 필요 없는 경우에도 데이터를 항상 조회해서 성능 문제가 발생할 수 있다. 즉시 로딩으로 설정하면 성능 튜닝이 매우 어려워 진다.

항상 지연 로딩을 기본으로 하고, 성능 최적화가 필요한 경우에는 페치 조인(fetch join)을 사용해라!(V3 에서 설명)

간단한 주문 조회 V2: 엔티티를 DTO로 변환 

V1, V2 모두 지연 로딩으로 인한 쿼리 N+1 문제 발생

주문 조회 + 연관된 회원, 배송정보 조회를 위한 추가 쿼리

주문 N개(1) + [ 각 주문에 대한 회원(N) + 배송정보(N) ]

현재 예제에서 N=2, 총 5번의 쿼리

*지연로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우 쿼리를 생략한다.

 

API 스펙에 따라 DTO 최적화

원하는 결과는 얻었지만 성능이 안 나옴

 

*DTO에서 엔티티를 파라미터로 받는 것은 크게 문제가 되지 않는다. DTO 자체가 임시로 데이터를 저장하는 용도일 뿐 큰 기능이 있는 것이 아니기 때문에

간단한 주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화

join fetch: JPA에만 있는 문법. 쿼리 1번으로 객체 그래프 탐색

order, member, delivery 한 줄로 동시 join

order를 조회할 때 order -> member , order -> delivery 는 이미 조회 된 상태이므로 지연 로딩 자체가 일어나지 않는다.

 

select o from Order o

join fetch o.member m

join fetch o.delivery d

 

엔티티를 반환 타입으로 fetch join을 걸었기 때문에 API 스펙에 불필요한 엔티티 정보까지 조회한 후 DTO로 변환

간단한 주문 조회 V4: JPA에서 DTO로 바로 조회

일반적인 SQL을 사용할 때 처럼 원하는 값을 선택해서 조회 (fetch join X)

JPA는 기본적으로 엔티티나 임베디드 타입으로만 반환할 수 있기 때문에 new 명령어를 사용해서 JPQL의 결과를 DTO로 즉시 변환

 

JPA에서 new 명령어는 생성자의 파라미터로 엔티티를 직접 넘기는 것이 불가능하기 때문에

별칭 참조를 통해 DTO에 필요한 필드만을 초기화

 

select new jpabook.jpashop.repository.order.simplequery.OrderSimpleQueryDto

(o.id, m.name, o.orderDate, o.status, d.address)

from Order o

join o.member m

join o.delivery d

 

SELECT 절에서 원하는 데이터를 직접 선택하므로 DB 애플리케이션 네트웍 용량 최적화 (생각보다 미비)

리포지토리는 엔티티의 객체 그래프 조회가 목적. API 스펙(프레젠테이션)을 위한 코드가 리포지토리에 들어가는 단점

리포지토리 재사용성 저하 -> API만을 위한 별개의 리포지토리 패키지로 분리하는 것을 권장!

정리

엔티티를 DTO로 변환하거나, DTO로 바로 조회하는 두가지 방법은 각각의 장단점이 있다.

상황에 따라서 더 나은 방법을 선택한다.

엔티티로 조회하면 리포지토리 재사용성도 좋고, 개발도 단순해진다.

 

* 쿼리 방식 선택 권장 순서 *

1. 우선 엔티티를 DTO로 변환하는 방법(V2)을 선택한다.

2. 필요하면 페치 조인으로 성능을 최적화(V3) 한다. -> 대부분의 성능 이슈가 해결된다.

3. 그래도 안되면 DTO로 직접 조회하는 방법(V4)을 사용한다.

4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.

'Advance I > Springboot & JPA' 카테고리의 다른 글

22.06.24  (0) 2022.06.24
22.06.23  (0) 2022.06.23
22.06.20 :: 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화  (0) 2022.06.20
22.06.13  (0) 2022.06.13
22.06.10  (0) 2022.06.10