코딩과 결혼합니다

[JPA] 지연 로딩과 조회 성능 최적화(2) 본문

2세/JPA

[JPA] 지연 로딩과 조회 성능 최적화(2)

코딩러버 2024. 2. 20. 12:43
728x90

https://coding-s2-chaewon.tistory.com/252

 

[JPA] 지연 로딩과 조회 성능 최적화(1)

등록이나 수정 등은 데이터 한 건을 다루는 것이기 때문에 거의 성능 문제가 발생하지 않는다. 주로 조회하는 데에 문제가 발생하는데 이번에는 여러 방법으로 데이터를 조회해 보면서 JPA로 성

coding-s2-chaewon.tistory.com


@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2(){
    return orderRepository.findAllByString(new OrderSearch()).stream()
            .map(SimpleOrderDto::new)
            .collect(toList());
}

Entity를 조회해서 Dto로 변환하는 방법이다.

 

N+1 문제

위의 방법은 N+1 문제를 일으킨다.

N+1 문제란 쿼리가 1 + N 만큼 실행되는 것이다.
내 코드에서는 order가 총 2(N) 개이며 쿼리가 1+ N + N번 실행된다. [1 + 2 + 2 = 5]
@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();
    }
}
  • 처음 1은 order 조회(order 조회 결과 수가 N이 된다.)
  • order에서 member로 가며 지연로딩 조회 2(N) 번
  • order에서 dlivery로 가며 지연로딩 조회 2(N) 번

만약 주문한 user가 같은 사람이라면 지연로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우에는 쿼리를 생략한다. 하지만 대부분의 경우 다른 user가 주문한 것을 조회할 것이고, 만일 order의 결과가 2개 이상이라면 더욱 많은 쿼리가 나가게 될 것이다.


 

🤔 원래대로라면 쿼리가 5개가 나가는 게 맞지만 나는 계속해서 7개의 쿼리가 나온다.

 

나와 같은 사례를 찾았다.

 

학습 페이지

 

www.inflearn.com

 

이유 : 하이버네이트 6 버전 OneToOne 관계 버그

 

- 하이버네이트 5 까지는 정상 작동하고 해당 쿼리도 추가로 호출되지 않는다.

- 최신버전에서 OneToOne 관계일 때 발생하는 버그로 추측. 하이버네이트 6이 나오면서 내부 엔진이 많이 개선되었는데, 그러면서 연관관계가 복잡한 경우에 아직 해결이 안 되는 것 같다는 김영한 개발자님의 답이었다.

- 문제가 해결되기 전까지는 OneToOne 관계에서 최적화가 필요한 경우 fetch join을 적극적으로 사용하자!


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

위와 매우 유사한데 여기서는 fetch join을 사용해 주었다.

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();
}

이를 사용하여 쿼리 1번에 조회가 가능하다.

더보기
select
    o1_0.order_id,
    d1_0.delivery_id,
    d1_0.city,
    d1_0.street,
    d1_0.zipcode,
    d1_0.status,
    m1_0.member_id,
    m1_0.city,
    m1_0.street,
    m1_0.zipcode,
    m1_0.name,
    o1_0.order_date,
    o1_0.status 
from
    orders o1_0 
join
    member m1_0 
        on m1_0.member_id=o1_0.member_id 
join
    delivery d1_0 
        on d1_0.delivery_id=o1_0.delivery_id
  • order와 member조인 - order와 delivery 조인
  • 이렇게 조인한 것을 하나의 select 절에 풀어서 반환한다.
  • 페치 조인으로 order, member, delivery는 이미 조회된 상태 이므로 지연로딩에 대해 고민하지 않아도 된다.