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