일대다 조회가 되면, 조인할때 데이터 뻥튀기가 되어버린다.
ToOne인 경우는 모두 fetch join을 하거나 inner join을 하면 최적화가 가능했었다
컬렉션 조회인 경우는 다쪽에 데이터가 많아져서 최적화가 힘들다. 따라서 컬렉션인 일대다 관계를 조회하고 최적화하는 방법을 알아본다.
@GetMapping("/api/v1/orders")
public List<Order> orderV1(){
List<Order> all = orderRepository.findAllByString(new OrderSearch());
//객체 그래프 초기화
for (Order order : all) {
order.getMember().getName();
order.getDelivery().getAddress();
//리스트 강제 초기화
List<OrderItem> orderItems = order.getOrderItems();
for (OrderItem orderItem : orderItems) {
orderItem.getItem().getName();
}
}
//엔티티가 직접 노출되는 문제가 있다. -> DTO로 바꿔서 응답해보자(v2)
return all;
}
orderItem과 Item 관계를 직접 초기화하면 Hibernate5Module
설정에 의해서 엔티티를 JSON으로 생성한다.
프록시인 상태로 두지 않는다는 의미, 다시말해 강제 초기화로 실제 값이 들어간다. 따라서 프록시 객체인 상태로 넘어가는 값들은 null로 설정되고 초기화가 된 값들은 실제값이 들어간다.
양방향 연관관계라면 무한루프에 걸리지 않도록 한 곳에는 @JsonIgnore
을 추가한다.
이 방법은 엔티티를 노출시키는 방법으로 권장되지 않는다.
@GetMapping("/api/v2/orders")
public List<OrderDto> orderV2(){
List<Order> orders = orderRepository.findAllByString(new OrderSearch());//select order all(1)
List<OrderDto> collect = orders.stream().map(OrderDto::new).collect(Collectors.toList());
return collect;
}
@Data
static class OrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;//value object는 노출해도 된다.
// private List<OrderItem> orderItems; //엔티티에 대한 의존을 완전히 끊어내야 한다.(굉장히 많은 실수가 여기에 있다)
private List<OrderItemDto> orderItemDtos;
public OrderDto(Order o) {
this.orderId = o.getId();
this.name = o.getMember().getName();//select member(1) select member(1)
this.orderDate = o.getOrderDate();
this.orderStatus = o.getStatus();
this.address = o.getMember().getAddress();
o.getOrderItems().forEach(item->item.getItem().getName()); //select item all (2) select item all(2)
this.orderItemDtos = o.getOrderItems().stream().map(OrderItemDto::new).collect(Collectors.toList());
}
}
@Getter
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이 실행된다.
api controller
@GetMapping("/api/v3/orders")
public List<OrderDto> orderV3(){
List<Order> orders = orderRepository.findAllWithItem();
List<OrderDto> collect = orders.stream().map(OrderDto::new).collect(Collectors.toList());
return collect;
}
repository
public List<Order> findAllWithItem(){
return em.createQuery(
"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();
//distinct는 스프링에서 엔티티의 id값이 같은 데이터가 있으면 중복을 제거해준다(DB는 완전히 데이터가 같아야 중복 제거해주지만)
}
(참고: 컬렉션이 포함된 쿼리에 페치조인은 1개만 사용할 수 있다. 컬렉션 둘 이상에 페치조인을 사용하면 안된다.)