프로그래밍/spring boot

[스프링부트] 실전! 스프링 부트와 JPA 활용2 컬렉션 조회 최적화 #2 엔티티를 DTO로 변환

aSpring 2023. 11. 26. 12:45
728x90
728x90
※ 본 포스팅은 김영한 강사님의 인프런 '실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화' 강의를 들으며 작성한 수강일지 입니다.

 
 

 

 

| API 개발 고급 - 컬렉션 조회 최적화

1. 주문 조회 V1: 엔티티 직접 노출
2. 주문 조회 V2: 엔티티를 DTO로 변환
3. 주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화
4. 주문 조회 V3.1: 엔티티를 DTO로 변환 - 페이징과 한계 돌파
5. 주문 조회 V4: JPA에서 DTO 직접 조회
6. 주문 조회 V5: JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화 
7. 주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화
8. API 개발 고급 정리

 

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

 

OrderApiController에 추가

package jpabook.jpashop.api;

import jpabook.jpashop.domain.Address;
import jpabook.jpashop.domain.Order;
import jpabook.jpashop.domain.OrderItem;
import jpabook.jpashop.domain.OrderStatus;
import jpabook.jpashop.repository.OrderRepository;
import jpabook.jpashop.repository.OrderSearch;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

@RestController
@RequiredArgsConstructor
public class OrderApiController {

    private final OrderRepository orderRepository;

    // 섹션 4 엔티티 직접 노출
    // 원래 이렇게 entity 직접 노출하면 안됨(예제니까 하는 것)
    @GetMapping("/api/v1/orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        for(Order order : all) {
            order.getMember().getName();
            order.getDelivery().getAddress();

            // 프록시 강제 초기화
            List<OrderItem> orderItems = order.getOrderItems();
            orderItems.stream().forEach(o -> o.getItem().getName());
        }
        return all;
    }

    @GetMapping("/api/v2/orders")
    public List<OrderDto> ordersV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch()); // orders를 DTO로 변환해야 하ㅏㅁ
        List<OrderDto> collect = orders.stream()
                .map(o -> new OrderDto(o)) // 값을 생성자에 넘겨서 dto로 반환
                .collect(toList());

        return collect;
    }

    //    @Data
    @Getter
    static class OrderDto {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate; //주문시간
        private OrderStatus orderStatus;
        private Address address; // Entity를 다 노출하면 안됨, but Address 같은 value object는 상관없음
        private List<OrderItemDto> orderItems;

        // 생성자
        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            orderItems = order.getOrderItems().stream() // loop로 돌리면서
//                    .map(OrderItemDto::new)
                    .map(orderItem -> new OrderItemDto(orderItem)) // 생성자를 통해 dto로 변환
                    .collect(Collectors.toList());
        }
    }

    @Data
    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번 -> 2개의 (주문) 결과
    • member , address N번(order 조회 수 만큼)
    • orderItem N번(order 조회 수 만큼) // 2개 가져옴 -> 각각 Lazy
    • item N번(orderItem 조회 수 만큼)

 

참고: 지연 로딩은 영속성 컨텍스트에 있으면 영속성 컨텍스트에 있는 엔티티를 사용하고 없으면 SQL을 실 행한다. 따라서 같은 영속성 컨텍스트에서 이미 로딩한 회원 엔티티를 추가로 조회하면 SQL을 실행하지 않는다
728x90
728x90