시작이 반

[JPA] 1 + n 조회, x To One에서 join fetch 본문

Programming/JPA

[JPA] 1 + n 조회, x To One에서 join fetch

G_Gi 2021. 5. 29. 17:17
SMALL

Table 정보

 

orders

 

 

member

 

delivery

 

찾을 api 정보 : orders

    @GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDto> orderV2(){
        //ORDER 2개
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());

        List<SimpleOrderDto> result = orders.stream()
                .map(o -> new SimpleOrderDto(o))
                .collect(Collectors.toList());

        return result;
    }

결과:

api정보

    @Data
    static class SimpleOrderDto{
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;

        public SimpleOrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName(); //LAZY 초기화
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); //LAZY 초기화
        }
    }

 

LAZY 설정 때문에 쿼리수가 5개가 된다. ( 1+N 문제)

Order 을 처음에 조회한다. 1번 (2개의 결과 : userA, userB 그러면 1+N+N개가 나와야함 여기서  N = 2) 

2개를 루프돌면서 확인한다.

1. userA

member을 조회한다. 1번

delivery를 조회한다. 1번

 

2. userB

member을 조회한다. 1번

delivery를 조회한다. 1번

 

즉, 1+2+2개의 쿼리가 실행된다.

실행된 쿼리 개수 : 5개

 

이 문제를 해결하기 위하여

 

join fetch로 최적화 한다.

 

    @GetMapping("/api/v3/simple-orders")
    public List<SimpleOrderDto> orderV3(){
        List<Order> orders = orderRepository.findAllWithMemberDelivery();
        List<SimpleOrderDto> result = orders.stream()
                .map(o -> new SimpleOrderDto(o))
                .collect(Collectors.toList());

        return result;
    }
    public List<Order> findAllWithMemberDelivery() {
        return em.createQuery(
                "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch  o.delivery d", Order.class
        ).getResultList();
    }

이렇게 되면 order를 가져올때 member와 delibery도 한번에 가져온다.

엔티티를 페치 조인(fetch join)을 사용해서 쿼리 1번에 조회

페치 조인으로 order -> member , order -> delivery 는 이미 조회 된 상태 이므로 지연로딩X

실행된 쿼리 개수 : 1개

 

 

그런데 api 에 필요없는정보까지 select한다.

 

api 에 필요한 정보만 select하기 위해서는

레포지토리에서 DTO를 직접 조회한다. ( 안좋은 방법인듯, 재사용성도 떨어지고...)

    @GetMapping("/api/v4/simple-orders")
    public List<OrderSimpleQueryDto> orderV4(){
        return orderRepository.findOrderDto();
    }
    public List<OrderSimpleQueryDto> findOrderDto() {
        return em.createQuery(
                "select new jpabook.jpashop.repository.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address) " +
                "from Order o" +
                " join o.member m" +
                " join o.delivery d", OrderSimpleQueryDto.class)
                .getResultList();

    }

 

쿼리 방식 선택 권장 순서

1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.

2. 필요하면 페치 조인으로 성능을 최적화 한다. 대부분의 성능 이슈가 해결된다.

3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.

4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접사용한다.

 

 

참고:

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

LIST

'Programming > JPA' 카테고리의 다른 글

[JPA]OSIV  (0) 2021.06.23
[JPA] One To Many에서 join fetch ( 페이징 )  (1) 2021.06.07
[JPA] xToMany - ManyToX  (0) 2021.05.29
[JPA] 변경감지와 병합(merge)  (1) 2021.05.27
[JPA] DTO, Domain(Entity)  (0) 2021.02.19