Order -> Delivery를 OneToOne로 연관되어 있으며, Order -> Member는 ManyToOne으로 연관되어 있을때, Order를 통해 연관된 Delivery와 Member도 같이 DB로 부터 값을 가져와서 Response로 보내는 작업입니다.
@GetMapping("/api/v3/simple-orders")
public Result ordersV3() {
List<Order> findOrders = orderRepository.findALlWithOrderMember();
List<OrderDTO> orderDTOS = findOrders.stream()
.map(o -> new OrderDTO(o))
.collect(Collectors.toList());
return new Result(orderDTOS);
}
public List<Order> findALlWithOrderMember() {
String query = "select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d";
return em.createQuery(query, Order.class)
.getResultList();
}
Response를 보내는 것은 V2와 동일한 DTO로 반환하며, 반환 값도 같습니다. 하지만, findOrders를 가져올때 JOIN FETCH를 이용해서 단 한번의 쿼리문으로 연관관계의 엔티티 값들도 가져오게 됩니다. 그렇기 때문에, Order -> OrderDTO 변환 과정에서 Member와 Delivery 값들을 DB를 추가로 접근하지 않아도 가져올 수 있습니다.
실제로 처리되는 쿼리문들을 확인해 보면, findOrders를 가져올때의 쿼리문만 처리되는 것을 확인 할 수 있습니다./** * 결과: * (쿼리문:) * 1. select order0_.order_id as order_id1_9_0_, member1_.member_id as member_i1_6_1_, delivery2_.delivery_id as delivery1_4_2_, order0_.member_id as member_i4_9_0_, order0_.order_date as order_da2_9_0_, order0_.status as status3_9_0_, member1_.city as city2_6_1_, member1_.street as street3_6_1_, member1_.zipcode as zipcode4_6_1_, member1_.username as username5_6_1_, delivery2_.city as city2_4_2_, delivery2_.street as street3_4_2_, delivery2_.zipcode as zipcode4_4_2_, delivery2_.order_id as order_id6_4_2_, delivery2_.status as status5_4_2_ from orders order0_ * inner join member member1_ on order0_.member_id=member1_.member_id * inner join delivery delivery2_ on order0_.order_id=delivery2_.order_id * => 총 1번의 쿼리문이 처리됨 */
하지만, 필요없는 Column들도 모두 가져오기 때문에 이러한 부분에서는 최적화의 여지가 있으며, V4에서 이 부분을 다뤄보겠습니다.
Response로 값을 보낼때도 이전과 동일하게 DTO로 반환하지만, DB를 접근할때도 DTO를 사용해서 필요한 값들을 가져옵니다.(더 자세한 내용은 https://velog.io/@k_ms1998/JPA-JPQL-%ED%94%84%EB%A1%9C%EC%A0%9D%EC%85%98)
DB를 접근할때 JOIN FETCH와 DTO를 동시에 사용하는 것은 불가능하기 때문에 JOIN(INNER, OUTER)과 DTO를 사용합니다.
@GetMapping("/api/v4/simple-orders")
public Result ordersV4() {
List<OrderQueryDTO> orderDTOS = orderRepository.findALlWithOrderMemberDTO();
return new Result(orderDTOS);
/**
* 결과:
* (쿼리문:)
* 1. select order0_.order_id as col_0_0_, member1_.username as col_1_0_, order0_.order_date as col_2_0_, order0_.status as col_3_0_, delivery2_.city as col_4_0_, delivery2_.street as col_4_1_, delivery2_.zipcode as col_4_2_ from orders order0_
* inner join member member1_ on order0_.member_id=member1_.member_id
* inner join delivery delivery2_ on order0_.order_id=delivery2_.order_id
* => 총 1번의 쿼리문이 처리됨 & V3이랑 비교 해봤을때 가져오는 Column이 다름
*
* 반환되는 Response는 V2&V3와 동일
*/
}
public List<OrderQueryDTO> findALlWithOrderMemberDTO() {
String query = "select new jpabook.jpashop.Repository.DTO.OrderQueryDTO(o.id, m.username, o.orderDate, o.status, d.address) from Order o" +
" join o.member m" +
" join o.delivery d";
return em.createQuery(query, OrderQueryDTO.class)
.getResultList();
}
@Data
public class OrderQueryDTO {
private Long orderId;
private String username; //주문자 이름
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address; //배송지
public OrderQueryDTO(Long orderId, String username, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
this.orderId = orderId;
this.username = username;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
}
}
Response 값도 동일하며, 쿼리문도 1번 만으로 원하는 값들을 가져올 수 있는데, 실행된 쿼리문을 살펴보면 가져오는 Column들이 V3와 다른 것을 볼 수 있습니다.
V3과 V4중에서 어떤것이 무조건적으로 더 좋다고 말하기는 어렵습니다. 물론 V4가 더 최적화 된 것은 맞지만, 쿼리문을 최적화 할때 Column을 더 가져오는 것은 큰 성능저하가 발생하지 않으며, V4는 딱 한가지 스펙에서만 사용 가능하기 때문에 재사용이 불가능 합니다. 반면에, V3는 여러 스펙에서 재사용이 가능하다는 장점이 있습니다.
그렇기 때문에, 상황에 따라서 V3 또는 V4 방식을 사용하는 것이 맞다고 생각합니다.