[스프링 JPA 활용] WEEK 11

enxnong·2023년 11월 26일
0

김영환님의 강의 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 보면서 공부한 내용입니다.

🏊‍♀️ 섹션 3

간단한 주문 조회 V3

📝 V3 - fetch join 사용

    @GetMapping("/api/v3/simple-orders")
    public List<SimpleOrderDto> ordersV3(){
        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(); 
    // 한 번의 쿼리로 member와 delivery를 sql 입장에서는 
    // join 이면서 select 입장에서는 한 번에 가져올 수 있도록 함
    // lozy 무시, proxy 가 아닌 진짜 객체 값을 가져옴
    }

📝 결과

✅ 엔티티를 fetch join해서 쿼리 1번에 조회
✅ 패치 조인으로 order → member, order → delivery는 이미 조회된 상태이므로 지연로딩 X

    select
        o1_0.order_id,
        d1_0.delivery_id,
        d1_0.city,
        d1_0.street,
        d1_0.zipcode,
        d1_0.status,
        m1_0.member_id,
        m1_0.city,
        m1_0.street,
        m1_0.zipcode,
        m1_0.name,
        o1_0.order_date,
        o1_0.status 
    from
        orders o1_0 
    join
        member m1_0 
            on m1_0.member_id=o1_0.member_id 
    join
        delivery d1_0 
            on d1_0.delivery_id=o1_0.delivery_id

간단한 주문 조회 V4

📝 V3 - JPA에서 DTO로 바로 조회

    @GetMapping("/api/v4/simple-orders")
    public List<OrderSimpleQueryDto> ordersV4(){
        return orderRepository.findOrderDtos();
    }
    
        public List<OrderSimpleQueryDto> findOrderDtos() {
        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();
    }

📝 결과

✅ 일반적인 SQL을 사용할 때 처럼 원하는 값을 선택해서 조회
new 명령어를 사용해서 JPQL 결과를 DTO로 즉시 변환
✅ SELECT 절에서 원하는 데이터를 직접 선택하므로 DB → 애플리케이션 네트워크 용량 최적화
✅ 리포지토리 재사용성 떨어짐, API 스펙에 맞춘 코드가 리포지토리에 들어가는 단점

    select
        o1_0.order_id,
        m1_0.name,
        o1_0.order_date,
        o1_0.status,
        d1_0.city,
        d1_0.street,
        d1_0.zipcode 
    from
        orders o1_0 
    join
        member m1_0 
            on m1_0.member_id=o1_0.member_id 
    join
        delivery d1_0 
            on d1_0.delivery_id=o1_0.delivery_id

💡 쿼리 방식 선택 권장 순서
1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다 (V2)
→ 코드 유지보수성에서 우수하다
2. 필요하면 패치 조인으로 성능을 최적화 한다 (V3)
3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다 (V4)
4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다

🏊‍♀️ 섹션 4

주문 조회 V1: 엔티티 직접 노출

    @GetMapping("/api/v1/orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        for (Order order : all) {
            order.getMember().getName();
            order.getDelivery().getAddress();
            List<OrderItem> orderItems =order.getOrderItems();
            orderItems.stream().forEach(o -> o.getItem().getName());
        }
        return all;
    }

✅ orderItem의 엔티티값이 전체 다 반환됨

주문 조회 V2: 엔티티를 DTO로 변환

    @GetMapping("/api/v2/orders")
    public List<OrderDto> ordersV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> collect = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(Collectors.toList());
        return collect;
    }
    
	@Data
    static class OrderDto{
        private Long orderId;
        private String name;
        private LocalDateTime localDateTime;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItemDto> orderItems;
        // 엔티티 수정 시 화면이 전체적으로 바뀜 => OrderItem도 전체 다 DTO로 변환시켜야됨

        public OrderDto(Order o){
            orderId = o.getId();
            name = o.getMember().getName();
            orderStatus = o.getStatus();
            address = o.getDelivery().getAddress();
            orderItems = o.getOrderItems().stream()
                    .map(orderItem -> new OrderItemDto(orderItem))
                    .collect(Collectors.toList());
        }
    }

    @Data
    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();
        }
    }   

✅ 지정한 DTO의 값만 반환됨

주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화

    @GetMapping("/api/v3/orders")
    public List<OrderDto> ordersV3() {
        List<Order> orders = orderRepository.findAllWithItem();

        List<OrderDto> result = orders.stream()
                .map(OrderDto::new)
                .collect(toList());

        return result;
    }
    
    
        public List<Order> findAllWithItem() {
        return em.createQuery("select distinct o from Order o" // Order에서 같은 id 값이면 중복제거해줌
   // 단, 페이징처리가 안되므로 사용불가능
   // 컬렌션 패치 조인은 1개만 사용할 수 있다. 켈렉션 둘 이상에 패치 조인을 사용하면 데이터가 부정합하게 조회될 수 있다. 
                    + " join fetch o.member m"
                    + " join fetch o.delivery d"
                    + " join fetch o.orderItems oi"
                    + " join fetch oi.item i", Order.class)
                .getResultList();
    }

💡 distinct를 사용한 이유 :

  • JPA의 distinct는 SQL에 distinct를 추가하고, 더해서 같은 엔티티가 조회되면 애플리케이션에서 중복을 걸러준다. 즉, order가 컬렉션 패치 조인 때문에 중복 조회되는 것을 막아준다.
  • 단점 : 페이징 불가능
profile
높은 곳을 향해서

0개의 댓글