X to One 방식과 마찬가지로 fetch join을 사용하여 LAZY 로딩 없이 하나의 쿼리로 전부 가져올 수 있다.
@GetMapping("/api/v3/orders")
public List<OrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithItem();
// order를 dto로 변환하여 반환
List<OrderDto> result = orders.stream()
.map(o -> new OrderDto(o))
.collect(Collectors.toList());
return result;
}
@Data
static class OrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemDto> orderItems;
// DTO 안에 엔티티가 있는 것도 잘못된 설계
// 이조차도 이런식으로 전부 DTO로 바꿔줘야 함
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()
.map(orderItem -> new OrderItemDto(orderItem))
.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();
}
}
.
.
.
// OrderRepository 내부
public List<Order> findAllWithItem() {
return em.createQuery(
// JPQL distinct 기능 두 가지
// 1. DB에 날리는 쿼리 수준에서 distinct 수행
// 2. 그래도 걸러지지 않은 것들에 대해서는 o의 참조값을 확인하여 중복 제거
"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();
}
fetch join을 사용하지 않으면 order, orderItem, item 각각의 개수마다 쿼리가 1개씩 나가게 된다.
order를 조회하는 JPQL에서 연관된 모든 엔티티에 fetch join을 걸어주면 한 번의 쿼리만으로 모든 엔티티를 동시에 가져올 수 있다.
이때 distinct의 역할이 중요하다.
1:N 관계에서 join 쿼리를 수행할경우 row의 개수는 N쪽(여기선 orderItems)으로 맞춰진다. 즉, 1쪽(여기선 order)의 데이터가 중복해서 들어가게 된다.
다시말해서 똑같은 o가 여러 개가 나오는 것.
이러한 중복을 막기 위해 distinct를 사욯안다.
distinct는 두 가지 기능을 수행한다.
1. 일반적인 쿼리문의 distinct과 똑같은 기능
-> 하지만 위의 경우에서는 order만 같을 뿐 item 등의 정보는 모두 다르기 때문에 distinct에서 걸러지지 않는다 ( distinct는 row의 모든 column이 같을 때만 중복으로 판단 )
2. 똑같은 참조값을 가진 객체 중복 제거
-> 여기서 o의 중복이 제거된다.
이러한 방식은 성능을 줄여주는 데 큰 도움이 되지만 페이징 기능을 사용할 수 없다는 단점이 있다.
(원인에 대해서는 교재or강의를 참고하자 V3.1 강의의 16:30 ~ 23:30)
결론 : X To One 관계는 페치 조인을 해도 페이징에 아무런 영향이 없다. 따라서 X To One 관계는 페치 조인으로 한 번에 가져와서 쿼리 수를 줄이고, X To Many 관계는 hibernate: default_batch_fetch_size로 해결하자.
'김영한님 스프링 강의 정리 > JPA' 카테고리의 다른 글
X to One 성능 최적화 (fetch join) (0) | 2021.04.01 |
---|---|
API 통신에서 DTO의 필요성 (0) | 2021.03.29 |
JPQL 벌크 연산 (0) | 2021.03.10 |
fetch join의 기능과 한계 (0) | 2021.03.10 |
명시적 조인과 묵시적 조인 (0) | 2021.03.10 |