[Spring Boot2][3] 2. API 개발 고급 - 지연 로딩과 조회 성능 최적화(1)
🏷 조회용 샘플 데이터 입력
API 개발 고급 설명을 위해 샘플 데이터를 입력하자😃
- userA
- JPA1 BOOK
- JPA2 BOOK
- userB
- SPRING1 BOOK
- SPRING2 BOOK
✔️ InitDB
클래스 생성
package jpabook.jpashop;
import jpabook.jpashop.domain.*;
import jpabook.jpashop.domain.item.Book;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
/**
* 총 주문 2개
* * userA
* * JPA1 BOOK
* * JPA2 BOOK
* * userB
* * SPRING1 BOOK
* * SPRING2 BOOK
*/
@Component
@RequiredArgsConstructor
public class InitDb {
private final InitService initService;
@PostConstruct
public void init() {
initService.dbInit1();
initService.dbInit2();
}
@Component
@Transactional
@RequiredArgsConstructor
static class InitService {
private final EntityManager em;
public void dbInit1() {
System.out.println("Init1" + this.getClass());
Member member = createMember("userA", "서울", "1", "1111");
em.persist(member);
Book book1 = createBook("JPA1 BOOK", 10000, 100);
em.persist(book1);
Book book2 = createBook("JPA2 BOOK", 20000, 100);
em.persist(book2);
OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, 1);
OrderItem orderItem2 = OrderItem.createOrderItem(book2, 20000, 2);
Delivery delivery = createDelivery(member);
Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);
em.persist(order);
}
public void dbInit2() {
Member member = createMember("userB", "진주", "2", "2222");
em.persist(member);
Book book1 = createBook("SPRING1 BOOK", 20000, 200);
em.persist(book1);
Book book2 = createBook("SPRING2 BOOK", 40000, 300);
em.persist(book2);
OrderItem orderItem1 = OrderItem.createOrderItem(book1, 20000, 3);
OrderItem orderItem2 = OrderItem.createOrderItem(book2, 40000, 4);
Delivery delivery = createDelivery(member);
Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);
em.persist(order);
}
private Member createMember(String name, String city, String street, String zipcode) {
Member member = new Member();
member.setName(name);
member.setAddress(new Address(city, street, zipcode));
return member;
}
private Book createBook(String name, int price, int stockQuantity) {
Book book1 = new Book();
book1.setName(name);
book1.setPrice(price);
book1.setStockQuantity(stockQuantity);
return book1;
}
private Delivery createDelivery(Member member) {
Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress());
return delivery;
}
}
}
🏷 간단한 주문 조회 V1 : 엔티티를 직접 노출
주문 + 배송정보 + 회원을 조회하는 API를 만들어 보자☺️
지연 로딩 때문에 발생하는 성능 문제를 단계적으로 해결해 볼 것이다!
✔️ OrderSimpleApiController
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {
private final OrderRepository orderRepository;
/**
* V1. 엔티티 직접 노출
* - Hibernate5Module 모듈 등록, LAZY=null 처리 * - 양방향 관계 문제 발생 -> @JsonIgnore
*/
@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
List<Order> all = orderRepository.findAllByString(new OrderSearch());
for (Order order : all) {
order.getMember().getName(); // Lazy 강제 초기화
order.getDelivery().getAddress(); // Lazy 강제 초기화
}
return all;
}
}
🤚🏻 양방향 연관관계가 있을 땐, 한 쪽에
@JsonIgnore
추가
안 그러면 양쪽을 서로 호출하면서 무한 루프가 걸리게 된다! 조심!
- 엔티티를 직접 노출하는 것은 좋지 않다! (이미 많이 설명함^_^)
order
➡️member
와order
➡️address
는 지연 로딩이다.
따라서 실제 엔티티 대신에 프록시 존재jackson
라이브러리는 기본적으로 이 프록시 객체를json
으로 어떻게 생성해야 하는지 모름 ➡️Hibernate5Module
을 스프링 빈으로 등록하면 해결!!(스프링 부트 사용중)
📌
build.gradle
에 다음 라이브러리 추가implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'
📌 Hibernate5Module 등록
JpashopApplication 에
다음 코드를 추가!@Bean Hibernate5Module hibernate5Module() { return new Hibernate5Module(); }
➡️ 이렇게 설정하면, 기본적으로 초기화 된 프록시 객체만 노출하고 초기화 되지 않은 프록시 객체는 노출 안함!
🤚🏻 주의할 점 정리
1️⃣ 엔티티를 직접 노출할 때는 양방향 연관관계가 걸린 곳은 꼭! 한곳을 @JsonIgnore
처리 해야 한다.
2️⃣ 앞에서 계속 강조했듯이 정말 간단한 애플리케이션이 아니면 엔티티를 API 응답으로 외부로 노출하는 것은 좋지 않다🙁
➡️ 따라서 Hibernate5Module
를 사용하기 보다는 DTO
로 변환해서 반환하는 것이 더 좋은 방법이다!
3️⃣ 지연 로딩(LAZY)을 피하기 위해 즉시 로딩(EARGR)으로 설정하면 안된다!
➡️ 항상 지연 로딩을 기본으로 하고, 성능 최적화가 필요한 경우에는 페치 조인(fetch join
)을 사용하자!
🏷 간단한 주문 조회 V2 : 엔티티를 DTO로 변환
✔️ OrderSimpleApiController - 추가
/**
* V2. 엔티티를 조회해서 DTO로 변환(fetch join 사용X) * - 단점: 지연로딩으로 쿼리 N번 호출
*/
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() {
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<SimpleOrderDto> result = orders.stream()
// order를 SimpleOrderDto로 바꿔치기
.map(o -> new SimpleOrderDto(o))
.collect(toList());
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과 쿼리수 결과는 같음)
order
조회 1번(order
조회 결과 수가 N이 됨)order ➡️ member
지연 로딩 조회 N번order ➡️ delivery
지연 로딩 조회 N번- 예)
order
의 결과가 4개면 최악의 경우 1 + 4 + 4번 실행된다.(최악의 경우)- 지연로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우 쿼리를 생략한다.
초쿰,,, 많이,,,, 어렵네요,,,,,
Author And Source
이 문제에 관하여([Spring Boot2][3] 2. API 개발 고급 - 지연 로딩과 조회 성능 최적화(1)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sorzzzzy/Spring-Boot23-2.-API-개발-고급-지연-로딩과-조회-성능-최적화저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)