[Spring] default_batch_fetch_size의 작동원리

Jaden Kim·2021년 7월 17일
5

스프링 JPA에서 default_batch_fetch_size 설정은 복잡한 조회쿼리 작성시,
지연로딩으로 발생해야 하는 쿼리를 IN절로 한번에 모아보내는 기능이다.
조회 성능 개선을 위해 반드시 활용해야 하는 설정이다!

그런데 이 기능을 사용하면서 궁금한 점이 생겼다.
JPA에서는 지연로딩 대상 객체를 어떤식으로 모아서 보내는 것일까?

지연로딩이라는 것은 아직 사용하기 이전의 엔티티를 프록시 객체로 저장해두고,
실제 사용하는 시점에 DB에 쿼리를 보내서 데이터를 불러오는 기능이다.

그런데 default_batch_fetch_size를 지정하면
해당 개수만큼을 IN절로 묶어서 쿼리을 보내게 된다.
이는 어떻게 처리가 되는 걸까?
지연로딩해야 하는 데이터를 만났을 때, 당장은 로딩하지 않은 채
default_batch_fetch_size가 다 찰때까지 기다리는 것일까?
코드로 확인해보자!

현재 엔티티는 Order와 OrderItem이 일대다 관계로 묶여있다.
이때 Order 객체를 db에서 받아오면
OrderItem은 프록시 객체 상태로 영속성 컨텍스트에 존재한다.

application.yml의 환경설정은 아래와 같다

spring:
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        default_batch_fetch_size: 10

default_batch_fetch_size는 10으로 지정한다.

for (int i = 1; i <= 100; i++) {
    OrderItem orderItem1 = OrderItem.createOrderItem("book1", i); // parameter: itemName, count
    Order order = Order.createOrder(orderItem1); // parameter : orderItem
    em.persist(order);
}

먼저 위와 같은 코드로 총 100개의 OrderItem을 만들고,
이를 통해 100개의 Order를 영속화한다.
Order -> OrderItem에 대해서 CascadeType.ALL로 설정되어 있기 때문에,
Order를 영속화할 때 OrderItem도 함께 영속화된다.
예제를 단순화하기 위해 Order 하나당 OrderItem 하나를 등록했다

List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
	for(OrderItem : order.orderItems) {
        System.out.println("orderItem count : " + order.getOrderItem.getCount());
    }
}

이제 Controller에서 각각의 Order를 순회하고,
그 안의 OrderItem을 순회하면서 지연로딩을 발생시킨다
(각 Order에 OrderItem이 하나씩 등록되어 있으므로,
각 Order에 대한 OrderItem의 순회는 한 번씩 이루어지고 끝난다)

이제 결과를 확인해보자

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_.count as count2_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_.count as count2_5_0_,
    from
        order_item orderitems0_ 
    where
        orderitems0_.order_id in (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
...

이러한 결과를 통해 알 수 있는 것은,
JPA는 지연로딩할 객체를 만났을 때,
default_batch_fetch_size개수만큼 모일 때까지 쌓아두었다가,
해당 개수가 다 모이면 쿼리를 보낸다는 것을 알 수 있다!

1개의 댓글

comment-user-thumbnail
2024년 10월 27일

1~10 까지 수는 전부 preparedstatement 준비되기 때문에 오해하신 것 같습니다.
지연로딩할 객체를 만났을때 default_batch_fetch_size 개수 만큼 모일 때 까지 쌓아두고 모아서 쿼리를 보내는 것이 아닙니다. hibernate 자체적으로 적절히 preparedstatement 를 캐싱하여 사용합니다.
자세한 내용은 링크로 첨부드립니다.
https://www.inflearn.com/community/questions/34469/default-batch-fetch-size-%EA%B4%80%EB%A0%A8%EC%A7%88%EB%AC%B8
다른분들이 오해하지 않도록 내용 수정부탁드립니다.

답글 달기