[JPA] 컬렉션 조회

박연주·2022년 7월 1일
0

JPA

목록 보기
2/8

주문 조회시 회원정보와 배송정보, 상품정보 같이 조회하기

  • Order - Member      N : 1
  • Order - Delivery       1 : 1
  • Order - OrderItem   1 : N
  • OrderItem - Item     1 : N

엔티티 조회

  • V1 : 엔티티를 조회해서 그대로 반환
  • V2 : 엔티티 조회 후 DTO로 변환
  • V3 : 페치 조인으로 쿼리 수 최적화
  • V3.1 : 컬렉션 페이징과 한계 돌파
    컬렉션은 페치 조인시 페이징이 불가능
    ToOne 관계는 페치 조인으로 쿼리 수 최적화
    컬렉션은 페치 조인 대신에 지연 로딩을 유지하고, hibernate.default_batch_fetch_size , @BatchSize 로 최적화

DTO 직접 조회

  • V4 : JPA에서 DTO를 직접 조회
  • V5 : 컬렉션 조회 최적화 - 일대다 관계인 컬렉션은 IN 절을 활용해서 메모리에 미리 조회해서 최적화
  • V6 : 플랫 데이터 최적화 - JOIN 결과를 그대로 조회 후 애플리케이션에서 원하는 모양으로 직접 변환

1. 엔티티 직접 노출

	@Bean
	Hibernate5Module hibernate5Module() {
		Hibernate5Module hibernate5Module = new Hibernate5Module();
//		hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
		return hibernate5Module;
	}

@GetMapping("/api/v1/orders")
    public List<Order> getOrderV1() {
        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;
    }



2. 엔티티를 DTO로 변환

@GetMapping("/api/v2/orders")
    public List<OrderDto> getOrderV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> result = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(Collectors.toList());
        return result;
    }

    @Getter
    static class OrderDto {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItem> orderItems;

        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            orderItems = order.getOrderItems();
        }
    }    -> OrderItems 가 엔티티라 더이상 조회 안됨.
    
    order.getOrderItems().stream().forEach(o -> o.getItem().getName());
    orderItems = order.getOrderItems();    이렇게 바꾸면 Item도 조회 가능
    # 엔티티가 DTO 내에서 노출됨
    # DTO내의 엔티티도 다시 DTO 처리

@GetMapping("/api/v2/orders")
    public List<OrderDto> getOrderV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> result = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(Collectors.toList());
        return result;
    }
    
 @Getter
    static class OrderDto {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItemDto> orderItems;

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

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



상품의 이름, 가격, 수량으로 딱 필요한 정보만 보기



3. 엔티티를 DTO로 변환 (페치 조인)

1. 페이징 X

public List<Order> findAllWithItem() {
        return em.createQuery(
                "select distinct o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d" +
                        " join fetch o.orderItems oi" +
                        " join fetch oi.item i", Order.class
        ).getResultList();
}
# distinct
# DB에 distinct 키워드를 날려준다
# 엔티티가 중복인 경우에 중복 걸러서 컬렉션에 담아줌

@GetMapping("/api/v3/orders")
    public List<OrderDto> getOrderV3() {
        List<Order> orders = orderRepository.findAllWithItem();
        List<OrderDto> result = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(Collectors.toList());
        return result;
    }
# 어떤 Order를 가져오는지 궁금하다면
// for (Order order : orders) {
//	  System.out.println("order ref = " + order + " id = " +order.getId());
// }



2. 페이징 O

  • 페이징은 1을 기준으로 페이징하는 것이 목적이다. (강의노트)
  • toOne은 하나의 쿼리로 처리
  • 쿼리 호출 수가 1 + N -> 1 + 1
1. order,member,delivery는 페치조인 및 offset,limit 페이징(to One)
2. orderitems, item 은 지연로딩, in query로 size만큼 한번에 불러옴(to Many)
   (hibernate.default_batch_fetch_size 설정, max : 1000)
1. application.yml 에 추가 - hibernate
	default_batch_fetch_size: 100

2. controller
	@GetMapping("/api/v3.1/orders")
    public List<OrderDto> getOrderV3_page(
            @RequestParam(value = "offset", defaultValue = "0") int offset,
            @RequestParam(value = "limit", defaultValue = "100") int limit) {
        List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);

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

3. repository
	public List<Order> findAllWithMemberDelivery(int offset, int limit) {
        return em.createQuery(
                "select o from Order o" +
                        " join fetch o.member" +
                        " join fetch o.delivery d", Order.class
        )       .setFirstResult(offset)
                .setMaxResults(limit)
                .getResultList();
    }
>3번의 쿼리 돔 (1 + 1 + 1)
>  페치조인 쿼리 - order, member, delivery
	select
        order0_.order_id as order_id1_6_0_,
        member1_.member_id as member_i1_4_1_,
        delivery2_.delivery_id as delivery1_2_2_,
        order0_.delivery_id as delivery4_6_0_,
        order0_.member_id as member_i5_6_0_,
        order0_.order_date as order_da2_6_0_,
        order0_.status as status3_6_0_,
        member1_.city as city2_4_1_,
        member1_.street as street3_4_1_,
        member1_.zipcode as zipcode4_4_1_,
        member1_.name as name5_4_1_,
        delivery2_.city as city2_2_2_,
        delivery2_.street as street3_2_2_,
        delivery2_.zipcode as zipcode4_2_2_,
        delivery2_.status as status5_2_2_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id 
    inner join
        delivery delivery2_ 
            on order0_.delivery_id=delivery2_.delivery_id limit ?


>  select
        orderitems0_.order_id as order_id5_5_1_,
        orderitems0_.order_item_id as order_it1_5_1_,
        orderitems0_.order_item_id as order_it1_5_0_,
        orderitems0_.count as count2_5_0_,
        orderitems0_.item_id as item_id4_5_0_,
        orderitems0_.order_id as order_id5_5_0_,
        orderitems0_.order_price as order_pr3_5_0_ 
    from
        order_item orderitems0_ 
    where
        orderitems0_.order_id in (      // in 쿼리
            ?, ?
        )
  : binding parameter [1] as [BIGINT] - [4]
  : binding parameter [2] as [BIGINT] - [11]   // [4,11]로 한번에

 // limit : 100, 100개까지 한꺼번에 in 쿼리로 가져옴

> select
        item0_.item_id as item_id2_3_0_,
        item0_.name as name3_3_0_,
        item0_.price as price4_3_0_,
        item0_.stock_quantity as stock_qu5_3_0_,
        item0_.artist as artist6_3_0_,
        item0_.etc as etc7_3_0_,
        item0_.author as author8_3_0_,
        item0_.isbn as isbn9_3_0_,
        item0_.actor as actor10_3_0_,
        item0_.director as directo11_3_0_,
        item0_.dtype as dtype1_3_0_ 
    from
        item item0_ 
    where
        item0_.item_id in (     // in 쿼리
            ?, ?, ?, ?
        )
  : binding parameter [1] as [BIGINT] - [2]
  : binding parameter [2] as [BIGINT] - [3]
  : binding parameter [3] as [BIGINT] - [9]
  : binding parameter [4] as [BIGINT] - [10]  // [2, 3, 9, 10]
profile
하루에 한 개념씩

0개의 댓글