ManyToOne, OneToOne 연관관계 조회 최적화 V3&V4(Best)

KMS·2022년 4월 19일
0

SpringBoot + JPA

목록 보기
11/14

Order -> Delivery를 OneToOne로 연관되어 있으며, Order -> Member는 ManyToOne으로 연관되어 있을때, Order를 통해 연관된 Delivery와 Member도 같이 DB로 부터 값을 가져와서 Response로 보내는 작업입니다.

V3: JOIN FETCH + DTO

API

	@GetMapping("/api/v3/simple-orders")
    public Result ordersV3() {
        List<Order> findOrders = orderRepository.findALlWithOrderMember();
        List<OrderDTO> orderDTOS = findOrders.stream()
                .map(o -> new OrderDTO(o))
                .collect(Collectors.toList());

        return new Result(orderDTOS);
}

findAllWithOrderMember()

public List<Order> findALlWithOrderMember() {
        String query = "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d";

        return em.createQuery(query, Order.class)
                .getResultList();
    }

Response를 보내는 것은 V2와 동일한 DTO로 반환하며, 반환 값도 같습니다. 하지만, findOrders를 가져올때 JOIN FETCH를 이용해서 단 한번의 쿼리문으로 연관관계의 엔티티 값들도 가져오게 됩니다. 그렇기 때문에, Order -> OrderDTO 변환 과정에서 Member와 Delivery 값들을 DB를 추가로 접근하지 않아도 가져올 수 있습니다.
실제로 처리되는 쿼리문들을 확인해 보면, findOrders를 가져올때의 쿼리문만 처리되는 것을 확인 할 수 있습니다.

		/**
         * 결과:
         * (쿼리문:)
         * 1. select order0_.order_id as order_id1_9_0_, member1_.member_id as member_i1_6_1_, delivery2_.delivery_id as delivery1_4_2_, order0_.member_id as member_i4_9_0_, order0_.order_date as order_da2_9_0_, order0_.status as status3_9_0_, member1_.city as city2_6_1_, member1_.street as street3_6_1_, member1_.zipcode as zipcode4_6_1_, member1_.username as username5_6_1_, delivery2_.city as city2_4_2_, delivery2_.street as street3_4_2_, delivery2_.zipcode as zipcode4_4_2_, delivery2_.order_id as order_id6_4_2_, delivery2_.status as status5_4_2_ from orders order0_
         * inner join member member1_ on order0_.member_id=member1_.member_id
         * inner join delivery delivery2_ on order0_.order_id=delivery2_.order_id
         * => 총 1번의 쿼리문이 처리됨
         */

하지만, 필요없는 Column들도 모두 가져오기 때문에 이러한 부분에서는 최적화의 여지가 있으며, V4에서 이 부분을 다뤄보겠습니다.

V4: JOIN(DTO) + DTO

Response로 값을 보낼때도 이전과 동일하게 DTO로 반환하지만, DB를 접근할때도 DTO를 사용해서 필요한 값들을 가져옵니다.(더 자세한 내용은 https://velog.io/@k_ms1998/JPA-JPQL-%ED%94%84%EB%A1%9C%EC%A0%9D%EC%85%98)
DB를 접근할때 JOIN FETCH와 DTO를 동시에 사용하는 것은 불가능하기 때문에 JOIN(INNER, OUTER)과 DTO를 사용합니다.

API

	@GetMapping("/api/v4/simple-orders")
    public Result ordersV4() {
        List<OrderQueryDTO> orderDTOS = orderRepository.findALlWithOrderMemberDTO();

        return new Result(orderDTOS);
        /**
         * 결과:
         * (쿼리문:)
         * 1. select order0_.order_id as col_0_0_, member1_.username as col_1_0_, order0_.order_date as col_2_0_, order0_.status as col_3_0_, delivery2_.city as col_4_0_, delivery2_.street as col_4_1_, delivery2_.zipcode as col_4_2_ from orders order0_
         * inner join member member1_ on order0_.member_id=member1_.member_id
         * inner join delivery delivery2_ on order0_.order_id=delivery2_.order_id
         * => 총 1번의 쿼리문이 처리됨 & V3이랑 비교 해봤을때 가져오는 Column이 다름
         *
         * 반환되는 Response는 V2&V3와 동일
         */
    }

findALlWithOrderMemberDTO()

public List<OrderQueryDTO> findALlWithOrderMemberDTO() {
        String query = "select new jpabook.jpashop.Repository.DTO.OrderQueryDTO(o.id, m.username, o.orderDate, o.status, d.address) from Order o" +
                " join o.member m" +
                " join o.delivery d";

        return em.createQuery(query, OrderQueryDTO.class)
                .getResultList();
    }

OrderQueryDTO

@Data
public class OrderQueryDTO {
    private Long orderId;
    private String username; //주문자 이름
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address; //배송지

    public OrderQueryDTO(Long orderId, String username, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.username = username;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }
}

Response 값도 동일하며, 쿼리문도 1번 만으로 원하는 값들을 가져올 수 있는데, 실행된 쿼리문을 살펴보면 가져오는 Column들이 V3와 다른 것을 볼 수 있습니다.

V3 vs. V4

V3과 V4중에서 어떤것이 무조건적으로 더 좋다고 말하기는 어렵습니다. 물론 V4가 더 최적화 된 것은 맞지만, 쿼리문을 최적화 할때 Column을 더 가져오는 것은 큰 성능저하가 발생하지 않으며, V4는 딱 한가지 스펙에서만 사용 가능하기 때문에 재사용이 불가능 합니다. 반면에, V3는 여러 스펙에서 재사용이 가능하다는 장점이 있습니다.
그렇기 때문에, 상황에 따라서 V3 또는 V4 방식을 사용하는 것이 맞다고 생각합니다.

profile
Student at Sejong University Department of Software

0개의 댓글