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

주문 + 배송정보 + 회원을 조회하는 API
지연로딩으로 발생하는 문제 해결을 위한 것

엔티티를 직접 노출


api폴더에 OrderSimpleApiController클래스 생성

package jpabook.jpashop.api;

import jpabook.jpashop.domain.Order;
import jpabook.jpashop.repository.OrderRepository;
import jpabook.jpashop.repository.OrderSearch;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/*
* xToOne (ManyToOne, OneToOne)
* Order
* Order -> Member
* Order -> Delivery
* */
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

    @GetMapping("/api/v1/simle-orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        return all;
    }
}


객체를 무한루프 돌면서 뽑아나기 떄문에 계속 돌아간다 양방향 관계 문제 발생 -> @JsonIgnore
양방향 걸리는 곳 모두 Ignore로 해서 반대쪽으로는 안한다.

엔티티를 직접 노출할 때는 양방향 연관관계가 걸린 곳은 꼭! 한곳을 @JsonIgnore 처리 해야 한다.
안그러면 양쪽을 서로 호출하면서 무한 루프가 걸린다.

그러고 나면

2번째 에러가 나는데

"error": "Internal Server Error",
"trace": "org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:

List< Order>all 로 가져온게
order member 와 order address 는 지연 로딩이다. 따라서 실제 엔티티 대신에 프록시 존재
jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 하는지 모름 예외 발생해서 에러가 발생한 것이다.

Hibernate한테 아무것도 뿌리지 말라고 하면된다.
Hibernate5Module 을 스프링 빈으로 등록하면 해결된다.

build.gradle 에 다음 라이브러리를 추가하자
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'


빈 등록하고 나면

주문 2개가 출력된다.
member가 null인데 이건 지연로딩이어서 그렇다

강제로 지연 로딩도 가능 한데

@Bean
Hibernate5Module hibernate5Module() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
//강제 지연 로딩 설정
hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING,
true);
return hibernate5Module;
}

강제로 Lazy 로딩해서 OrderItems관련된거 가져옴

저번 글에서 썼던 것처럼 이건 엔티티를 그대로 노출하는 것인데, 성능상에도 문제도 있고 외부로 노출되는 엔티티는 좋지않다. 불필요한 api스펙 노출도 되고, 사용하지 않는 것까지 쿼리가 나가기 때문에 다 가져와서 성능이 저하된다.


이런 방법도 있는데 order.getMember() 까지는 프록시 객체 (db쿼리 전, 진짜아님)
.getName()하면 Lazy가 강제 초기화된다.
Member에 쿼리를 날려서 JPA가 끌고온다.

암튼 엔티티 직접 노출했을 때 많은 문제있었고
Hibernate5Module 모듈 등록해서 어느정도 해결했지만 그래도 api스펙에 엔티티가 그대로 노출되면 너무많은 문제가 발생한다.

엔티티를 API 응답으로 외부로 노출하는 것은 좋지 않다. 따라서 Hibernate5Module 를 사용하기 보다는 DTO로 변환해서 반환하는 것이 더 좋은 방법

엔티티를 DTO로 변환


@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() {
List<Order> orders = orderRepository.findAll();
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
@Data
static class SimpleOrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate; //주문시간
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
}
}

v2 결과

v1과 비교

v2는 api 스펙에 딱 맞춰서 최적화 해서 개발한 것
근데 Lazy 로딩으로 인한 쿼리가 많이 호출되는 문제가 있다.

현재 주문수 2개 이므로 쿼리 5번 나감

엔티티를 DTO로 변환 - 페치 조인 최적화


  @GetMapping("/api/v3/simple-orders")
    public List<SimpleOrderDto> ordersV3() {
        List<Order> orders = orderRepository.findAllWithMemberDelivery();
        List<SimpleOrderDto> result = orders.stream()
                .map(o -> new SimpleOrderDto(o))
                .collect(Collectors.toList());
        return result;
    }

패치조인은 Lazy 무시하고 객체에 다 값을 채워서 가져온다 .

SQL에는 패치라는 문법없고, jpa만 있는 문법

엔티티를 페치 조인(fetch join)을 사용해서 쿼리 1번에 조회

OrderRepository - 추가

public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.getResultList();
}


결과는 v2와 동일하지만 쿼리 보내는걸 보면 한번만 나간다.

fetch라는 명령어로 쿼리 1번에 조회
페치 조인으로 order -> member , order -> delivery 는 이미 조회 된 상태 이므로 지연로딩X

실무에서 자주 사용하는 기법이다.

좋은 웹페이지 즐겨찾기