WEB/JPA

실전! 스프링부트와 JPA와 활용2 (컬렉션 조회 최적화)

Tony Lim 2021. 4. 6. 16:16
728x90

컬렉션 조회 최적화

컬렉션같은 x to many의 경우 데이터가뻥튀기 되어서 돌아온다. 

   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();

   }
    @GetMapping("/api/v3/orders")
    public Result ordersV3()
    {
        List<Order> orders = orderRepository.findAllWithItem();
        List<OrderDto> result = orders.stream()
                .map(OrderDto::new)
                .collect(Collectors.toList());
       return new Result(result,result.size());
    }

    @Data
    @AllArgsConstructor
    static class Result<T>
    {
        private T data;
        private int count;
    }

    @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.getOrderStatus();
            address = order.getDelivery().getAddress();
            orderItems = order.getOrderItems().stream()
                    .map(OrderItemDto::new)
                    .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();
        }
    }

fetch join 할때 distinct 를 넣어주면 sql 과 다르게 jpql은 application level에서 중복을 제거해준다. 

{
    "data": [
        {
            "orderId": 4,
            "name": "userA",
            "orderDate": "2021-04-05T14:39:48.266819",
            "orderStatus": "ORDER",
            "address": {
                "city": "서울",
                "street": "1",
                "zipcode": "1111"
            },
            "orderItems": [
                {
                    "itemName": "JPA1 Book",
                    "orderPrice": 10000,
                    "count": 1
                },
                {
                    "itemName": "JPA2 Book",
                    "orderPrice": 20000,
                    "count": 2
                }
            ]
        },
        {
            "orderId": 11,
            "name": "userB",
            "orderDate": "2021-04-05T14:39:48.44082",
            "orderStatus": "ORDER",
            "address": {
                "city": "진주",
                "street": "2",
                "zipcode": "2222"
            },
            "orderItems": [
                {
                    "itemName": "Spring1 Book",
                    "orderPrice": 10000,
                    "count": 1
                },
                {
                    "itemName": "Spring2 Book",
                    "orderPrice": 20000,
                    "count": 2
                }
            ]
        }
    ],
    "count": 2
}

알맞게 중복이제거되어 2개가 제대로 나온것을 확인할수 있다. 하지만 이제 distinct 를 썼으니 페이징을 할 수 가없다. 하더라도 뜻대로 동작하지 않을 수 있다. 

컬렉션 패치조인은 1개만 사용할 수 있다. 컬렉셔 ㄴ둘이상에 패치 조인을 사용하면 안된다. 데이터가 이상하게 조합될수 있다. 데이터가 완전히 엄청 뻥튀기 된다. 1 * M * N 이런식으로 커진다. 나중에는 뭘 기준으로 객체 그래프를 끌고와야하는지 모르게 될 수 있다.

 

@BatchSize

    @GetMapping("/api/v3.1/orders")
    public Result ordersV3_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(OrderDto::new)
                .collect(Collectors.toList());
        return new Result(result,result.size());
    }
   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();
   }

현재 이것은 컬렉셔 fetch join이 아니고 (many to one , one to one) 이기 때문에 페이징이 오류없이 된다 하지만 

Hibernate: 
    select
        order0_.id as 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_.order_status as order_st3_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_.delivery_status as delivery5_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 ?
2021-04-05 18:26:04.286  INFO 18440 --- [nio-8084-exec-2] p6spy                                    : #1617614764286 | took 4ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
select order0_.id as 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_.order_status as order_st3_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_.delivery_status as delivery5_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 order0_.id as 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_.order_status as order_st3_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_.delivery_status as delivery5_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 100;
Hibernate: 
    select
        orderitems0_.order_id as order_id5_5_0_,
        orderitems0_.order_item_id as order_it1_5_0_,
        orderitems0_.order_item_id as order_it1_5_1_,
        orderitems0_.count as count2_5_1_,
        orderitems0_.item_id as item_id4_5_1_,
        orderitems0_.order_id as order_id5_5_1_,
        orderitems0_.order_price as order_pr3_5_1_ 
    from
        order_item orderitems0_ 
    where
        orderitems0_.order_id=?
2021-04-05 18:26:04.311  INFO 18440 --- [nio-8084-exec-2] p6spy                                    : #1617614764311 | took 1ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=?
select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=4;
Hibernate: 
    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=?
2021-04-05 18:26:04.336  INFO 18440 --- [nio-8084-exec-2] p6spy                                    : #1617614764336 | took 2ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
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=?
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=2;
Hibernate: 
    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=?
2021-04-05 18:26:04.339  INFO 18440 --- [nio-8084-exec-2] p6spy                                    : #1617614764339 | took 0ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
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=?
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=3;
Hibernate: 
    select
        orderitems0_.order_id as order_id5_5_0_,
        orderitems0_.order_item_id as order_it1_5_0_,
        orderitems0_.order_item_id as order_it1_5_1_,
        orderitems0_.count as count2_5_1_,
        orderitems0_.item_id as item_id4_5_1_,
        orderitems0_.order_id as order_id5_5_1_,
        orderitems0_.order_price as order_pr3_5_1_ 
    from
        order_item orderitems0_ 
    where
        orderitems0_.order_id=?
2021-04-05 18:26:04.341  INFO 18440 --- [nio-8084-exec-2] p6spy                                    : #1617614764341 | took 0ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=?
select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=11;
Hibernate: 
    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=?
2021-04-05 18:26:04.344  INFO 18440 --- [nio-8084-exec-2] p6spy                                    : #1617614764344 | took 0ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
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=?
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=9;
Hibernate: 
    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=?

첫번쨰 fetch join쿼리가 나가고 유저당 아이템이 2개씩 존재함으로

첫번쨰 유저 Orderitem 프록시 초기화 (1) + 아이템 프록시 초기화 (2)

두번쨰 유저도 마찬가지 총 7번의쿼리가 나가게 되는 N+1 의 문제가 발생해버린다.

 

@Batchsize 나 application.yml 에서 jpa.hibernate.default_batch_fetch_size 글로벌 하게 설정을 해주면

in query를 지정해준 숫자만큼 날려준다. XtoOne관계들은 fetch join으로 잡아주고 나머지는 in query로 잡아오는게 best다.

결론 = toOne관계는 fetch join 해도 페이징에 영향을 주지 않는다. 나머지는 batch 로 최적화 하자. 

100~1000 사이가 적당할 것이다. 너무 적으면 쿼리가 많이 나가게 됨으로. 100 이든 1000이든 어차피 전체 데이터를 로딩할떄까지 loop 가 blocking 되고 있기때문에 메모리 사용량은 같다.

 

JPA에서 DTO 직접 조회

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.orderStatus, d.address)" +
                        " from Order o" +
                        " join o.member m" +
                        " join o.delivery d",OrderQueryDto.class)
                .getResultList();
    }

toOne 관계는 조인해도 데이터수가 증가하지않는다. toMany경우 증가하게됨으로 loop를 돌면서 일일이 채워 넣었다.

Hibernate: 
    select
        order0_.id as col_0_0_,
        member1_.name as col_1_0_,
        order0_.order_date as col_2_0_,
        order0_.order_status as col_3_0_,
        delivery2_.city as col_4_0_,
        delivery2_.street as col_4_1_,
        delivery2_.zipcode as col_4_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
2021-04-06 14:00:59.265  INFO 10992 --- [nio-8084-exec-1] p6spy                                    : #1617685259265 | took 3ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
select order0_.id as col_0_0_, member1_.name as col_1_0_, order0_.order_date as col_2_0_, order0_.order_status as col_3_0_, delivery2_.city as col_4_0_, delivery2_.street as col_4_1_, delivery2_.zipcode as col_4_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
select order0_.id as col_0_0_, member1_.name as col_1_0_, order0_.order_date as col_2_0_, order0_.order_status as col_3_0_, delivery2_.city as col_4_0_, delivery2_.street as col_4_1_, delivery2_.zipcode as col_4_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;
Hibernate: 
    select
        orderitem0_.order_id as col_0_0_,
        item1_.name as col_1_0_,
        orderitem0_.order_price as col_2_0_,
        orderitem0_.count as col_3_0_ 
    from
        order_item orderitem0_ 
    inner join
        item item1_ 
            on orderitem0_.item_id=item1_.item_id 
    where
        orderitem0_.order_id=?
2021-04-06 14:00:59.299  INFO 10992 --- [nio-8084-exec-1] p6spy                                    : #1617685259299 | took 2ms | statement | connection 19| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
select orderitem0_.order_id as col_0_0_, item1_.name as col_1_0_, orderitem0_.order_price as col_2_0_, orderitem0_.count as col_3_0_ from order_item orderitem0_ inner join item item1_ on orderitem0_.item_id=item1_.item_id where orderitem0_.order_id=?
select orderitem0_.order_id as col_0_0_, item1_.name as col_1_0_, orderitem0_.order_price as col_2_0_, orderitem0_.count as col_3_0_ from order_item orderitem0_ inner join item item1_ on orderitem0_.item_id=item1_.item_id where orderitem0_.order_id=4;
Hibernate: 
    select
        orderitem0_.order_id as col_0_0_,
        item1_.name as col_1_0_,
        orderitem0_.order_price as col_2_0_,
        orderitem0_.count as col_3_0_ 
    from
        order_item orderitem0_ 
    inner join
        item item1_ 
            on orderitem0_.item_id=item1_.item_id 
    where
        orderitem0_.order_id=?

쿼리는 findOrders 에서 한번 그이후에는 findOrderItems 에서 총 오더가 2개있음으로 2번의 OrderItems를 조회하는 쿼리가 나간다.

 

JPA에서 DTO 직접 조회(컬렉션 조회 최적화)

    public List<OrderQueryDto> findAllByDto_optimization()
    {
        List<OrderQueryDto> result = findOrders();

        List<Long> orderIds = result.stream()
                .map(o -> o.getOrderId())
                .collect(Collectors.toList());

        List<OrderItemQueryDto> orderItems = 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 in :orderIds", OrderItemQueryDto.class)
                .setParameter("orderIds", orderIds)
                .getResultList();
        Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
                .collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));

        result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));

        return result;
    }

order들을 findOrders로 가져온후에 orderItem들을 in 쿼리를 통해 한번에 가져온다. 가져온 orderItems 들을 id를 키로해서 Map 형태로 메모리 에 담은후에 가져온 OrderQueryDto 의 orderItems에 채워 넣어준다.

Hibernate: 
    select
        order0_.id as col_0_0_,
        member1_.name as col_1_0_,
        order0_.order_date as col_2_0_,
        order0_.order_status as col_3_0_,
        delivery2_.city as col_4_0_,
        delivery2_.street as col_4_1_,
        delivery2_.zipcode as col_4_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
2021-04-06 14:03:18.120  INFO 10992 --- [nio-8084-exec-5] p6spy                                    : #1617685398120 | took 1ms | statement | connection 20| url jdbc:mysql://localhost:3306/tony_jpashop?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
select order0_.id as col_0_0_, member1_.name as col_1_0_, order0_.order_date as col_2_0_, order0_.order_status as col_3_0_, delivery2_.city as col_4_0_, delivery2_.street as col_4_1_, delivery2_.zipcode as col_4_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
select order0_.id as col_0_0_, member1_.name as col_1_0_, order0_.order_date as col_2_0_, order0_.order_status as col_3_0_, delivery2_.city as col_4_0_, delivery2_.street as col_4_1_, delivery2_.zipcode as col_4_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;
Hibernate: 
    select
        orderitem0_.order_id as col_0_0_,
        item1_.name as col_1_0_,
        orderitem0_.order_price as col_2_0_,
        orderitem0_.count as col_3_0_ 
    from
        order_item orderitem0_ 
    inner join
        item item1_ 
            on orderitem0_.item_id=item1_.item_id 
    where
        orderitem0_.order_id in (
            ? , ?
        )

쿼리는 findOrder에서 한번 그 이후에 findOrderitems 에서 in 쿼리 한번 총 2번이 나가게 된다.

 

    @GetMapping("/api/v6/orders")
    public List<OrderQueryDto> ordersV6()
    {
        List<OrderFlatDto> flats = orderQueryRepository.findAllByDto_flat();

        Set<Map.Entry<OrderQueryDto, List<OrderItemQueryDto>>> entries = flats.stream()
                .collect(groupingBy(o ->  new OrderQueryDto(o.getOrderId(), o.getName(),
                                o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
                        mapping(o -> new OrderItemQueryDto(o.getOrderId(),
                                o.getItemName(), o.getOrderPrice(), o.getCount()), toList())))
                .entrySet();

        List<OrderQueryDto> collect = entries.stream().map(e -> new OrderQueryDto(
                e.getKey().getOrderId(),
                e.getKey().getName(),
                e.getKey().getOrderDate(),
                e.getKey().getOrderStatus(),
                e.getKey().getAddress(),
                e.getValue()))
                .collect(toList());
        return collect;
    }

이렇게 하면 쿼리가 한번 나가지만 매우 번거롭게 노가다를 해서 api 스펙에 맞춰줘야 한다. 또한 OrderQueryDto 의 orderId로 grouping하고싶다고 명확히 아래와 같이 @EqualsAndHashCode(of = "orderId")를 써주어야한다.

@Data
@EqualsAndHashCode(of = "orderId")
public class OrderQueryDto
{

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

    public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address, List<OrderItemQueryDto> orderItems)
    {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
        this.orderItems = orderItems;
    }
}

 

권장순서

1. 엔티티 조회 방식으로 우선 접근 

패치조인으로 쿼리수를 최적화

컬렉션 최적화

  1. 페이징 필요 = batch 최적화
  2. 페이징 필요 X = 패치 조인사용

2. 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용

3. 다안되면 nativeSQL or 스프링 JdbcTemplate

 

728x90