728x90
728x90
※ 본 포스팅은 김영한 강사님의 인프런 '실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화' 강의를 들으며 작성한 수강일지 입니다.
| API 개발 고급 - 지연 로딩과 조회 성능 최적화
1. 간단한 주문 조회 V1: 엔티티를 직접 노출
2. 간단한 주문 조회 V2: 엔티티를 DTO로 변환
3. 간단한 주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화
4. 간단한 주문 조회 V4: JPA에서 DTO로 바로 조회
주문을 기반으로, 주문 + 배송정보 + 회원을 조회하는 API를 만들자
지연 로딩 때문에 발생하는 성능 문제를 단계적으로 해결해보자.
참고: 지금부터 설명하는 내용은 정말 중요합니다. 실무에서 JPA를 사용하려면 100% 이해해야 합니다.
안그러면 엄청난 시간을 날리고 강사를 원망하면서 인생을 허비하게 됩니다.
2. 간단한 주문 조회 V2: 엔티티를 DTO로 변환
OrderSimpleApiController - 추가
// V2 : 엔티티를 DTO로 변환
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() { // List로 반환하면 안됨, result로 한번 감싸주어야 함
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<SimpleOrderDto> result = orders.stream() // for문 돌려도 됨
.map(o -> new SimpleOrderDto(o))
.collect(Collectors.toList());
// 위 코드를 이렇게 줄여도 됨 1
// List<SimpleOrderDto> result = orderRepository.findAllByString(new OrderSearch()).stream()
// .map(o -> new SimpleOrderDto(o))
// .collect(Collectors.toList());
// 위 코드를 이렇게 줄여도 됨 2
// return orderRepository.findAllByString(new OrderSearch()).stream()
// .map(o -> new SimpleOrderDto(o))
// .collect(Collectors.toList());
// 위 코드를 이렇게 줄여도 됨 3
// return orderRepository.findAllByString(new OrderSearch()).stream()
// .map(SimpleOrderDto::new)
// .collect(Collectors.toList());
// 위 코드를 이렇게 줄여도 됨 4
// return orderRepository.findAllByString(new OrderSearch()).stream()
// .map(SimpleOrderDto::new)
// .collect(toList()); // Collectors 블록 지정 Option + Enter -> static import ...
return result;
}
@Data
static class SimpleOrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Order order) { // 생성자
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
}
}
- 엔티티를 DTO로 변환하는 일반적인 방법이다.
- 쿼리가 총 1 + N + N번 실행된다. (v1과 쿼리수 결과는 같다.) : 이번에는 1 + 2 + 2 => 쿼리 5번 나감
- order 조회 1번(order 조회 결과 수가 N이 된다.) : 결과 주문 수가 2개이므로 2번 loop를 돔
- order -> member 지연 로딩 조회 N 번 order -> delivery 지연 로딩 조회 N 번
- 예) order의 결과가 4개면 최악의 경우 1 + 4 + 4번 실행된다.(최악의 경우)
- 지연로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우 쿼리를 생략한다.
-> 여기서 address는 Entity가 아닌 Value Object
그냥 address라는 데이터 타입을 하나 정의했다고 보면 됨
이제 Entity가 바뀌어도 api 스펙이 바뀔 일이 없음
-> 원래 name인데 username으로 변경 시, getName이 빨간색으로 표시되는 것처럼! 변경하면 문제가 된다는 것을 알 수 있음
Entity가 아니라 절대적으로 DTO로 다 바꿔서 보내기!!!
V1, V2가 모두 가지고 있는 문제점
- lazy loading으로 인한 DB query가 너무 많이 호출되는 문제
- order, member, delivery 3개의 table을 조회해야 하는 상황
-> 만약 같은 유저가 여러개 주문을 한 것이라면
1 + 1 + 2 => 쿼리 4번 나감
order -> 2개가 조회됨 -> loop를 member를 가져옴 -> 2번째 loop를 돌 때, member가 이미 영속성 컨텍스트에 있었으므로 member는 다시 조회하지 않음 -> 각각 다른 유저 2명이 주문했을 때보다 쿼리 1번 덜 조회함
다음 시간
- N + 1 문제를 페치 조인으로 해결할 것
728x90
728x90
'프로그래밍 > spring boot' 카테고리의 다른 글
[Annotations] Spring Boot Annotation 정리 (1) | 2023.11.23 |
---|---|
[STS] STS4 설치 / 기본 설정 (0) | 2023.11.20 |
[스프링부트] 실전! 스프링 부트와 JPA 활용2 지연 로딩과 조회 성능 최적화 #1 간단한 주문 조회 V1: 엔티티를 직접 노출 (0) | 2023.11.15 |
[스프링부트] 실전! 스프링 부트와 JPA 활용2 #2 API 개발 고급 - 준비 (0) | 2023.11.15 |
[스프링부트] 실전! 스프링 부트와 JPA 활용2 #1 API 개발(3) 회원 조회 (2) | 2023.11.14 |