[스프링과 JPA활용 2편] 요약 정리4

sonnng·2023년 11월 3일
0

Spring

목록 보기
26/41

API 개발 고급

(2) 컬렉션 조회 최적화

일대다 조회가 되면, 조인할때 데이터 뻥튀기가 되어버린다.
ToOne인 경우는 모두 fetch join을 하거나 inner join을 하면 최적화가 가능했었다

@XToOne 조회 최적화 확인하러 가기

컬렉션 조회인 경우는 다쪽에 데이터가 많아져서 최적화가 힘들다. 따라서 컬렉션인 일대다 관계를 조회하고 최적화하는 방법을 알아본다.


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

	@GetMapping("/api/v1/orders")
    public List<Order> orderV1(){
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        //객체 그래프 초기화
        for (Order order : all) {
            order.getMember().getName();
            order.getDelivery().getAddress();

            //리스트 강제 초기화
            List<OrderItem> orderItems = order.getOrderItems();
            for (OrderItem orderItem : orderItems) {
                orderItem.getItem().getName();
            }
        }
        //엔티티가 직접 노출되는 문제가 있다. -> DTO로 바꿔서 응답해보자(v2)
        return all;
    }
  • orderItem과 Item 관계를 직접 초기화하면 Hibernate5Module설정에 의해서 엔티티를 JSON으로 생성한다.
    프록시인 상태로 두지 않는다는 의미, 다시말해 강제 초기화로 실제 값이 들어간다. 따라서 프록시 객체인 상태로 넘어가는 값들은 null로 설정되고 초기화가 된 값들은 실제값이 들어간다.

  • 양방향 연관관계라면 무한루프에 걸리지 않도록 한 곳에는 @JsonIgnore을 추가한다.

  • 이 방법은 엔티티를 노출시키는 방법으로 권장되지 않는다.



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

	@GetMapping("/api/v2/orders")
    public List<OrderDto> orderV2(){
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());//select order all(1)
        List<OrderDto> collect = orders.stream().map(OrderDto::new).collect(Collectors.toList());
        return collect;
    }
    @Data
    static class OrderDto{
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;//value object는 노출해도 된다.
//        private List<OrderItem> orderItems; //엔티티에 대한 의존을 완전히 끊어내야 한다.(굉장히 많은 실수가 여기에 있다)
        private List<OrderItemDto> orderItemDtos;

        public OrderDto(Order o) {
            this.orderId = o.getId();
            this.name = o.getMember().getName();//select member(1) select member(1)
            this.orderDate = o.getOrderDate();
            this.orderStatus = o.getStatus();
            this.address = o.getMember().getAddress();
            o.getOrderItems().forEach(item->item.getItem().getName()); //select item all (2) select item all(2)
            this.orderItemDtos = o.getOrderItems().stream().map(OrderItemDto::new).collect(Collectors.toList());
        }

    }

    @Getter
    static class OrderItemDto{
        private String itemName; //상품명
        private int orderPrice; //상품가격
        private int count; //상품 수량

        public OrderItemDto(OrderItem orderItem){
            itemName = orderItem.getItem().getName();
            orderPrice = orderItem.getOrderPrice();
            count = orderItem.getCount();
        }
    }

지연로딩으로 인해서 많은 SQL이 실행된다.

  • sql 실행횟수
    order 1번, member, address 각각 N번(order 조회수 만큼), orderItem N번, item N번(orderItem 조회수만큼)



주문 조회 V3 : 엔티티를 DTO로 변환 - 페치조인 최적화

api controller

 	@GetMapping("/api/v3/orders")
    public List<OrderDto> orderV3(){
        List<Order> orders = orderRepository.findAllWithItem();
        List<OrderDto> collect = orders.stream().map(OrderDto::new).collect(Collectors.toList());
        return collect;
    }

repository

	public List<Order> findAllWithItem(){
        return em.createQuery(
                "select distinct o from Order o"+
                " join fetch o.member m"+
                " join fetch o.delivery d"+
                " join fetch o.orderItems oi"+
                " join fetch oi.item i", Order.class)
                .getResultList();
        //distinct는 스프링에서 엔티티의 id값이 같은 데이터가 있으면 중복을 제거해준다(DB는 완전히 데이터가 같아야 중복 제거해주지만)
    }

😀 컬렉션이 포함된 쿼리 페치조인 적용시 장점

  • 컬렉션 조회에서 페치조인을 사용하면, SQL이 한번만 실행된다.
  • distinct를 사용하여 DB에서 중복된 엔티티 id값에 대해 중복제거를 해준다.
    JPA의 distinct는 sql에 대해 distinct를 추가하고, 이에 추가하여 같은 엔티티(id값이 일치하는 경우)가 조회되면 중복을 제거해서 반환한다.

😱 컬렉션이 포함된 쿼리 페치조인 적용시 단점

  • 페이징이 안된다.
    컬렉션이 포함된 쿼리에 페치조인을 사용하면 페이징이 불가능하다. 데이터를 DB에서 읽고 메모리에서 페이징을 해버리기 때문에 매우 위험하다. 또한 부정합하게 데이터가 조회되어 페이징이 안된다.

(참고: 컬렉션이 포함된 쿼리에 페치조인은 1개만 사용할 수 있다. 컬렉션 둘 이상에 페치조인을 사용하면 안된다.)

0개의 댓글