[스프링 활용2] 주문 조회 V4 : JPA에서 DTO 직접 조회

atdawn·2024년 8월 19일

SPRING BOOT+JPA

목록 보기
46/49

참고 : [실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화] - 김영한


이번에는 컬렉션이 있는 주문 조회에서 DTO를 직접 조회하는 방법을 알아보자.

OrderApiController

...
private final OrderQueryRepository orderQueryRepository;
@GetMapping("/api/v4/orders")
    public List<OrderQueryDto> ordersV4() {
        return orderQueryRepository.findOrderQueryDtos();
    }
...

OrderRepository는 Order 엔티티를 조회하는 용도로 쓰이며,
OrderQueryRepository는 화면이나 API처럼 의존관계가 있기 때문에 패키지를 나누어 주는 것이 좋다.

OrderQueryDto

@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;
    }
}

OrderItemQueryDto

@Data
public class OrderItemQueryDto {

	@JsonIgnore
    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;
    }
}

OrderQueryRepository

@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {

    private final EntityManager em;
	
/**
* 컬렉션은 별도로 조회
* Query: 루트 1번, 컬렉션 N 번 * 단건 조회에서 많이 사용하는 방식 
*/
    public List<OrderQueryDto> findOrderQueryDtos(){
        //루트 조회(toOne 코드를 모두 한번에 조회)
        List<OrderQueryDto> result= findOrders();

		//루프를 돌면서 컬렉션 추가(추가 쿼리 실행)
        result.forEach(o->{
            List<OrderItemQueryDto> orderItems=findOrderItems(o.getOrderId());
            o.setOrderItems(orderItems);
        });
        return result;
        

    }

/**
* 1:N 관계(컬렉션)를 제외한 나머지를 한번에 조회 
*/
    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();


    }

    
/**
* 1:N 관계인 orderItems 조회 
*/
    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();
    }

}
  • 쿼리 : 루트 1번 + 컬렉션은 N번 실행
  • ToOne(N:1, 1:1) 관계들을 먼저 조회하고, ToMany(1:N) 관계는 각각 별도로 처리한다.
    • ToOne 관계는 조인해도 데이터 row 수가 증가하지 않는다.
    • ToMany(1:N) 관계는 조인하면 row 수가 증가한다.
  • row 수가 증가하지 않는 ToOne 관계는 조인으로 최적화 하기 쉬우므로 한번에 조회하고, ToMany 관계는 최적 화 하기 어려우므로 findOrderItems() 같은 별도의 메서드로 조회한다.

동작 원리

  1. findOrders() 메서드를 사용해 Order 엔티티와 연관된 Member와 Delivery 정보를 먼저 조회
    • Order와 Member, Delivery는 ManyToOne 또는 OneToOne 관계이므로 조인하여 한 번의 쿼리로 조회
  2. 이후 각 OrderQueryDto에 대해 findOrderItems() 메서드를 호출하여 OrderItem 컬렉션을 조회
    • 각 OrderQueryDto에 대해 별도의 쿼리가 실행되며, 해당 주문에 포함된 OrderItem 리스트가 조회
    • 이렇게 조회된 OrderItemQueryDto 리스트는 각각의 OrderQueryDto에 설정
  • findOrders()는 한 번의 쿼리로 전체 주문을 조회
  • findOrderItems()는 OrderQueryDto 리스트의 크기만큼 반복 실행되며, 각 주문의 OrderItem 리스트를 조회
profile
복습 복습 복습

0개의 댓글