DTO 직접 조회
Last updated
Last updated
화면이나 API에 fit한 용도로만 사용하는 쿼리는 패키지를 따로 파서 구현한다.
화면과 관련된 로직과 중요 핵심 비즈니스 로직을 분리할 수 있다.
@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
private final EntityManager em;
public List<OrderQueryDto> findOrderQueryDtos() {
List<OrderQueryDto> result = findOrders();
result.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return result;
}
// 컬렉션은 별도의 메서드로 조회한다.
private List<OrderItemQueryDto> findOrderItems(Long orderId) {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id = : orderId", OrderItemQueryDto.class)
.setParameter("orderId", orderId)
.getResultList();
}
private List<OrderQueryDto> findOrders() {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" +
" from Order o" +
" join o.member m" +
" join o.delivery d", OrderQueryDto.class)
.getResultList();
}
}
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderRepository orderRepository;
private final OrderQueryRepository orderQueryRepository;
@GetMapping("/api/v4/orders")
public List<OrderQueryDto> ordersV4() {
return orderQueryRepository.findOrderQueryDtos();
}
}
@Data
public class OrderQueryDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemQueryDto> orderItems;
// 컬렉션은 생성자에서 제외한다.
public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
}
}
@Data
public class OrderItemQueryDto {
private Long orderId;
private String itemName;
private int orderPrice;
private int count;
public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int count) {
this.orderId = orderId;
this.itemName = itemName;
this.orderPrice = orderPrice;
this.count = count;
}
}
쿼리는 루트(order)에서 1번, 컬렉션(order_item)에서 N번 실행되었다.
ToOne 관계를 먼저 조회하고 ToMany 관계는 별도로 처리한다.
ToOne은 join해도 데이터 row 수가 증가하지 않는다.
row가 증가하지 않기 때문에 join으로 최적화하기 쉬워 한 번에 조회한다.
ToMany는 join 하면 row 수가 증가한다.
최적화하기 어려우므로 findOrderItems()같은 별도의 메서드로 조회한다.
public class OrderQueryRepository {
private final EntityManager em;
public List<OrderQueryDto> findOrderQueryDtos() {
// query 1번 -> 결과는 N개
List<OrderQueryDto> result = findOrders();
// query N번
result.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return result;
}
}
이 방식은 결과적으로 forEach를 돌면서 N+1을 초래한다.