인프런 커뮤니티 질문&답변

Jaden Kim님의 프로필 이미지

작성한 질문수

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

default_batch_fetch_size의 원리가 궁금합니다!

해결된 질문

작성

·

529

0

안녕하세요 영한님! 페이징과 한계돌파 부분을 수강하면서 질문하고 싶은 내용이 생겨 남기게 되었습니다.

default_batch_fetch_size=n 으로 지정할 경우, 컬렉션에서 n개씩 값을 가져오는 것으로 이해하였습니다.

코드로 그 과정을 살펴보면,

List<OrderDto> result = orders.stream()
.map(o -> new OrderDto(o))

을 통해서 각각의 order를 순회하면서 OrderDto의 생성자를 호출하고, 생성자 안에서

orderItems = order.getOrderItems().stream()
.map(orderItem -> new OrderItemDto(orderItem))

이렇게 orderItem을 순회하면서 OrderItemDto를 만들고, 

public OrderItemDto(OrderItem orderItem) {
itemName = orderItem.getItem().getName();
orderPrice = orderItem.getOrderPrice();
count = orderItem.getCount();
}

최종적으로 OrderItemDto의 생성자 안에서  orderItem.getItem().getName() 를 호출하면서 orderItem과 그 안의 Item이 지연로딩되는 것으로 이해하였습니다.

그렇다면 JPA는 default_batch_fetch_size=n으로 인해 어떤 n개의 orderItem을 쿼리로 요청할지 결정해야 하는데, 이는 어떠한 방식으로 이루어지는 것인가요?

위와 같이 iteration을 돌면서 요청 보낼 orderItem의 id를 하나하나 모았다가 n개가 다 차게 되면 쿼리를 보내는 식인지, 아니면 다른 방식으로 결정이 되는 것인지 궁금합니다!

답변 4

0

Jaden Kim님의 프로필 이미지
Jaden Kim
질문자

앗 그렇군요! 지금 살펴보니 쿼리 -> 10개 출력 -> 쿼리 -> 10개 출력... 순서로 되어있는 게 맞습니다!

Hibernate: 

    select

        orderitems0_.order_id as order_id5_5_1_,

        orderitems0_.order_iem_id as order_ie1_5_1_,

        orderitems0_.order_iem_id as order_ie1_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 (

            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?

        )

orderItem count : 1

orderItem count : 2

orderItem count : 3

orderItem count : 4

orderItem count : 5

orderItem count : 6

orderItem count : 7

orderItem count : 8

orderItem count : 9

orderItem count : 10

Hibernate: 

    select

        orderitems0_.order_id as order_id5_5_1_,

        orderitems0_.order_iem_id as order_ie1_5_1_,

        orderitems0_.order_iem_id as order_ie1_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 (

            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?

        )

orderItem count : 11

orderItem count : 12

orderItem count : 13

orderItem count : 14

orderItem count : 15

orderItem count : 16

orderItem count : 17

orderItem count : 18

orderItem count : 19

orderItem count : 20

...

그런데, 아직도 이해가 어려운 것은

미리  default_batch_fetch_size 만큼의 데이터를 가져오기 위해서는 어떤 데이터가 미래에 사용하는 데이터인지를 알아야 하는 것 아닌가요..?!

그렇지 않다면 단순히 id기준으로 뒤의  default_batch_fetch_size개 데이터를 가져오는 것인가요?

김영한님의 프로필 이미지
김영한
지식공유자

하이버네이트는 본인이 조회한 데이터를 알고 있습니다.

예를 들어서 a -> b 관계로 엔티티가 있을 때 select로 a를 100개 조회했습니다.

그러면 내부에서 a1 ~ a100을 조회했다는 것을 알고 있습니다.

이제 a1.getB()을 조회할 때 a1~a10을 in 쿼리를 사용해서 b 10개를 한번에 조회할 수 있습니다.

감사합니다.

Jaden Kim님의 프로필 이미지
Jaden Kim
질문자

그렇군요!! 이제 명료하게 이해가 됩니다ㅎㅎ

감사합니다 :)

0

김영한님의 프로필 이미지
김영한
지식공유자

Jaden Kim님 테스트하고 공유해주셔서 감사합니다^^

그런데 JPA는 버퍼처럼 모아두고 쿼리를 하는 것이 아니라 미리 쿼리를 합니다.

1,2,3,4,5,6,7,8,9,10 이렇게 데이터가 있고 batchsize=5라면

1을 조회하는 순간 1,2,3,4,5를 미리 조회해두고

이후 6을 조회하는 순간 6,7,8,9,10을 조회합니다.

이 부분은 지연로딩을 조회하기 직전에 로그를 남겨보시면 확인할 수 있습니다.

감사합니다.

0

Jaden Kim님의 프로필 이미지
Jaden Kim
질문자

말씀해주신대로 테스트를 해봤습니다!

InitDb에서 아래의 코드로 Order에 OrderItem을 한개씩 등록하여  총 100개의 주문을 생성했습니다

for (int i = 1; i <= 100; i++) {
OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, i);
Order order = Order.createOrder(member, delivery, orderItem1);
em.persist(order);
}

default_batch_fetch_size=10 으로 지정했고, OrderApiController의 코드를 아래와 같이 for문으로 풀어쓰고

System.out을 통해 어떤 식으로 쿼리가 발생하는지 확인했습니다

for (OrderItem orderItem : order.getOrderItems()) {
OrderItemDto orderItemDto = new OrderItemDto(orderItem);
System.out.println("orderItem count : " + orderItem.getCount());
list.add(orderItemDto);
}

이때, count를 1~100의 숫자로 순차적으로 저장했기 때문에, getCount()를 통해 몇번째 orderItem이 출력되고 있는지 확인했습니다.

아래의 코드를 통해서 각각의 order를 순회하면서 위 코드를 호출하게 되고, orderItem을 하나씩 지연로딩 시킵니다

for (Order order : orders) {
result.add(new OrderDto(order));
}

결과는 아래와 같습니다

orderItem count : 1

orderItem count : 2

orderItem count : 3

orderItem count : 4

orderItem count : 5

orderItem count : 6

orderItem count : 7

orderItem count : 8

orderItem count : 9

orderItem count : 10

Hibernate: 

    select

        orderitems0_.order_id as order_id5_5_1_,

        orderitems0_.order_iem_id as order_ie1_5_1_,

        orderitems0_.order_iem_id as order_ie1_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 (

            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?

        )

orderItem count : 11

orderItem count : 12

orderItem count : 13

orderItem count : 14

orderItem count : 15

orderItem count : 16

orderItem count : 17

orderItem count : 18

orderItem count : 19

orderItem count : 20

Hibernate: 

    select

        orderitems0_.order_id as order_id5_5_1_,

        orderitems0_.order_iem_id as order_ie1_5_1_,

        orderitems0_.order_iem_id as order_ie1_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 (

            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?

        )

...

테스트 결과, 지연로딩이 발생해야 하는 객체를 버퍼처럼 10개씩 모아놨다가, default_batch_fetch_size로 지정한 개수가 다 차게 되면 쿼리가 발생하는 것을 확인했습니다!

감사합니다 :)

0

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. Jaden Kim님

위와 같이 iteration을 돌면서 요청 보낼 orderItem의 id를 하나하나 모았다가 n개가 다 차게 되면 쿼리를 보내는 식인지, 아니면 다른 방식으로 결정이 되는 것인지 궁금합니다!

-> 이 부분을 한번 테스트 해보시면 더 많은 것을 이해하실 수 있을거에요^^!

단순히 for로 바꾸어서 System.out으로 지연 로딩 전에 찍어보시면 어떤 타이밍에 쿼리가 나가는지 확실히 이해하실 수 있을거에요.

테스트 해보시고 결과를 남겨주세요^^

감사합니다.