Search
Duplicate

API 개발 고급 - 지연 로딩과 조회 성능 최적화

사전 준비 - 조회용 샘플 데이터 입력

jpabook::jpashop::InitDb

Code Content

Tips

IntelliJ shortcut
cmd + opt + m : duplicate code refactor(중복된 코드들을 하나의 메서드로 추출해준다.)
cmd + opt + v : method 반환 값에 따라 변수 선언을 해준다.
주문 + 배송정보 + 회원을 조회하는 API를 여러 방법으로 만들어 볼 것입니다. 그러면서 지연로딩 때문에 발생하는 성능 문제를 단계적으로 해결할 것입니다. (Impotant) 실무에서 중요한 내용들을 다루기에 이 챕터는 모두 복습 철저히 하도록 하자.

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

Code Content
우선 엔티티를 직접 노출(반환) 하는것은 좋지 않다.(저번 챕터에서도 언급)
해당 API를 호출하면 Order Member와 Order Delivery간에 서로의 엔티티를 조회하는것이 무한순회를 하게되어 에러가 발생하고 제대로된 Response가 오지 못한다.
Order Delivery 간 무한참조 결과

@JsonIgnore

양방향 연관관계부분에 @JsonIgnore Annotation을 추가해주면 무한 루프 에러는 해결된다. 하지만, 다음 에러가 발생하는데 보도록 하자.
ByteBuddyInterceptor에서 Type defnition error가 발생한것을 볼 수 있다. 어째서 해당 에러가 났을까?
→ 현재 코드의 로딩 전략은 지연로딩(LAZY)전략을 사용한다. 그 말은 Member, Delivery등의 엔티티들을 조회하는 시점에서는 실제 객체가 아닌 프록시 객체를 가지고 있다. 그렇기 때문에 jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 하는지 모르기 때문에 예외가 발생하는 것이다.
해결책
→ Hibernate5Module 을 스프링 Bean으로 등록하면 해결된다.
/*build.gradle*/ implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5' /*JpashopApplication.java*/ @Bean Hibernate5Module hibernate5Module(){ return new Hibernate5Module(); }
Java
복사
gradle에 implentation 할 때 version을 따로 입력해주지 않으면 Gradle에서 자동으로 현재 버전과 잘 맞는 버전을 import해서 관리해준다.
아래와 같이 hibernate5Module설정을 통해 강제로 지연 로딩도 사용 가능하다
Code Content
(Important) 계속 언급했지만 엔티티를 API에 직접 노출하는 것은 좋지 않다. 그래서 Hibernate5Module을 이용하는 방법은 알아만 두고 실무에서는 DTO로 변환해서 반환하는것이 더 좋은 방법이다.
주의점: 지연로딩(LAZY)의 문제를 피하기 위해 즉시 로딩(EAGER)으로 설정하는 것은 안된다. 즉시 로딩 때문에 연관관계가 필요 없는 경우에도 데이터를 조회하기 때문에 성능 문제가 발생할 수 있다. 지연로딩을 기본으로 하고 필요하다면 페치 조인(fetch join)을 이용하자.

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

Code Content

V2버전으로 API호출했을 때 실행되는 쿼리

Code Content
쿼리가 1 + N + N 번 실행된 것을 볼 수 있다. → 물론, 여기서 member가 동일할 경우 처음 조회 이후 다시 조회 하지 않기 때문에 1번의 쿼리수행이 더 줄어들 수 있지만, 흔한 케이스가 아니기에 고려하지 않는다.
Order 조회가 많아질수록 기하급수적으로 쿼리수행이 많아지고 성능저하가 일어날 수 있다.

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

Code Content

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

Code Content::Controller
방법에 따라서 원하는 값을 선택해서 조회하는 경우 엔티티를 조회하는 리포지토리(OrderRepository)가 아니라 따로 쿼리용 리포지토리를 만들면 구분이 쉽다.
Code Content::OrderSimpleQueryRepository 조회 전용 리포지토리
Code Content::OrderSimpleQueryDto

정리

→엔티티를 DTO로 변환하거나, DTO로 바로 조회하는 두가지 방법은 각각 장단점이 있다. 둘 중 상황에 따라서 더 나은 방법을 선택하면 된다.
엔티티로 조회하면 리포지토리 재사용성도 좋고, 개발도 단순해진다. 따라서 권장하는 방법은 다음과 같다.

쿼리 방식 선택 권장 순서

1.
우선 엔티티를 DTO로 변환하는 방법을 선택한다.
2.
필요하면 fetch join으로 성능을 최적화 한다. → 대부분의 성능 이슈 해결
3.
그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
4.
최후의 방법으로 JPA가 제공하는 Native SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.