Spring

[JPA] OneToMany(1:N) 페이징 처리

728x90

지금까지 연관 엔티티를 불러오기 위해 지연로딩과 즉시로딩 방법을 알아보았고, N+1 문제를 해결하기 위해 fetch join을 사용하는 것과 이 fetch join을 쉽게 적용할 수 있는 @EntityGraph에 대해서도 알아보았습니다.

 

하지만 마지막 하나 남아있는게 있습니다. DB 테이블에는 얼마나 많은 양의 정보가 있는지 모르기때문에, 특정 조건으로 데이터 리스트를 조회할때 보통 페이징 처리를 합니다.

 

그런데 이 연관 엔티티를 페이징 처리하려면 어떻게 해야할까요??

 

보통 페이징 처리를 할때 저희는 PageRequest 객체를 만들어서 파라미터로 넘깁니다.

public interface OrderRepository extends JpaRepository<Order, Long> {
  Page<OrderEntity> findOrderWithOrderProducts(Pageable pageable);
}

 

그럼 지금까지 모든 방법을 종합했을 때..

 

연관 엔티티에 지연로딩을 적용하고, @EntityGraph를 적용하고 PageRequest 객체를 넘기면 되겠네요?? 

아닙니다. @EntityGraph를 적용하면 SQL에 limit이 적용되지 않아서 연관된 모든 엔티티를 전부 불러온 후, 메모리에 적재한 후에 paging을 진행합니다. 따라서 서버에 굉장한 부하를 줄 수 있습니다.

 

그럼 @EntityGraph 를 없애면요?

@EntityGraph 즉 fetch join을 없애면, SQL에 limit이 적용돼서 페이징을 할수있습니다! 하지만... 저희가 fetch join 없이 마주했던 N+1문제가 다시 생겨버리죠 ㅠㅠ

 

그럼 어떻게하죠..?

Spring Data JPA 설정에는 연관된 컬렉션을 한꺼번에 가져올 배치 사이즈(Batch Size)를 어노테이션이나 설정파일을 통해 설정할 수 있습니다.

spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name="user_id")
@BatchSize(size=100)
private Set<FileEntity> fileEntitySet = new LinkedHashSet<>();

위와 같이 100으로 설정해두면, 한 엔티티를 불러올 때 관련된 연관 엔티티를 100개까지 불러올 수 있다는 말이됩니다. 즉, 페이지네이션에서 한 페이지당 size = 100 까지 끊어서 자동으로 가져올 수 있게 처리됩니다.

 

그리고 핵심은 이렇게 설정해두면, fetch join을 쓰지 않아도 N+1 문제가 생기지 않고 in 쿼리를 이용해서 똑똑하게 연관 엔티티를 불러옵니다. 아래는 BatchSize 설정까지 마친 후 hibernate가 생성한 SQL입니다

Hibernate: 
    select
        fileentity0_.user_id as user_id9_0_1_,
        fileentity0_.id as id1_0_1_,
        fileentity0_.id as id1_0_0_,
        fileentity0_.created_at as created_2_0_0_,
        fileentity0_.modified_at as modified3_0_0_,
        fileentity0_.fancy_size as fancy_si4_0_0_,
        fileentity0_.mime as mime5_0_0_,
        fileentity0_.name as name6_0_0_,
        fileentity0_.size as size7_0_0_,
        fileentity0_.task as task8_0_0_,
        fileentity0_.user_id as user_id9_0_0_ 
    from
        file fileentity0_ 
    where
        fileentity0_.user_id in (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )

 

출처:
- [Spring Data JPA] One에서 Many를 fetch하며 페이징할 때
- 일대다(1:N) 페이징 처리(OneToMany Pagination)
728x90