[JPA] lazyLoading 최적화 ( XToOne )

DevHwan·2022년 12월 3일
0

XtoOne 관계에 있는 데이터 조회

데이터 간 연관관계에서 XtoMany에 해당하는 데이터는 없는 상태에서 조회하는 것이다. 이번에도 마찬가지로 인터넷 쇼핑몰 서비스를 가정하고 생각해보자. 단, 일반 쇼핑몰과는 다르게 한 주문에서는 한 가지 품목만 주문이 가능하다.

해당 서비스에서 주문 조회 API 설계에 대해 생각해보자. 한 주문에서 한 가지 상품만 주문이 가능하기 때문에 주문과 물건과의 관계는 OneToOne이 된다. 공동 구매 기능이 별도로 없기 때문에 주문과 회원의 관계 역시 OneToOne 상태를 갖게 된다.

Step 1. 단순 전체 조회

@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> orderV2() {
    List<Order> orders = orderRepository.findAll();
    List<SimpleOrderDto> result = orders.stream()
            .map(o -> new SimpleOrderDto(o))
            .collect(Collectors.toList());

    return result;
}

가장 단순한 방식의 Order를 모두 찾아온 후, 이를 Dto로 변환하는 방식이다.

select
        order0_.order_id as order_id1_6_,
        order0_.delivery_id as delivery4_6_,
        order0_.member_id as member_i5_6_,
        order0_.order_date as order_da2_6_,
        order0_.status as status3_6_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id limit ?

select
        member0_.member_id as member_i1_4_0_,
        member0_.city as city2_4_0_,
        member0_.street as street3_4_0_,
        member0_.zipcode as zipcode4_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id in (
            ?, ?
        )

주문 조회를 햇을 뿐인데 총 2번의 쿼리가 발생했다. JPA를 이용하여 구현하다보면 가장 많이 발생하는 N+1 문제이다.

해당 API에서는 적은 양의 쿼리가 발생한 것 처럼 보이지만, 데이터 구조 설계에 따라 여러 관계가 얽혀있다면 더 많은 양의 쿼리가 발생할 수 있다. 이런 일이 발생하는 이유는 데이터 간의 관계를 각각 참조하면서 지연 로딩이 발생하기 때문이다. 이를 Fetch Join을 통해서 해결한다.

Step 2. Fetch join 조회 with 지연로딩 최적화

@GetMapping("/api/v3.1/simple-orders")
public List<SimpleOrderDto> orderV3_1() {
    List<Order> orders = orderRepositoryIn.findAllWithMemberDelivery();
    List<SimpleOrderDto> result = orders.stream()
            .map(SimpleOrderDto::new)
            .collect(Collectors.toList());
    return result;
}
@Repository
public interface OrderRepositoryIn extends JpaRepository<Order, Long> {
    @Query("select o from Order o join fetch o.member m join fetch o.delivery d")
    List<Order> findAllWithMemberDelivery();
}

이번에는 Order를 조회하면서 Order와 연관관계가 있는 테이블들을 모두 JPQL의 Fetch Join을 통해서 한 번에 데이터를 가져오도록 했다.

select
        order0_.order_id as order_id1_6_0_,
        member1_.member_id as member_i1_4_1_,
        delivery2_.delivery_id as delivery1_2_2_,
        order0_.delivery_id as delivery4_6_0_,
        order0_.member_id as member_i5_6_0_,
        order0_.order_date as order_da2_6_0_,
        order0_.status as status3_6_0_,
        member1_.city as city2_4_1_,
        member1_.street as street3_4_1_,
        member1_.zipcode as zipcode4_4_1_,
        member1_.name as name5_4_1_,
        delivery2_.city as city2_2_2_,
        delivery2_.street as street3_2_2_,
        delivery2_.zipcode as zipcode4_2_2_,
        delivery2_.status as status5_2_2_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id 
    inner join
        delivery delivery2_ 
            on order0_.delivery_id=delivery2_.delivery_id

기존에 있던 쿼리들보다 길이가 상당히 길어진 것을 알 수 있다. 그 대신 단 한 번의 쿼리 수행으로 모든 데이터를 가져올 수 있다. 이는 DB 커넥션의 사용을 줄이기 때문에 더욱 효율적인 조회 API 수행으로 이어질 수 있다.

물론 반환되는 데이터는 모두 동일한 값이다!

profile
달리기 시작한 치타

0개의 댓글