본문 바로가기
개발관련

JPA Fetch Join과 페이징 문제

by 부발자 2020. 9. 22.

JPA 로 개발을 할때, 컬렉션을 Fetch Join 하면 페이징 조회 시 메모리에서 페이징 처리하는 문제가 있다.

데이터가 적으면 크게 상관은 없겠지만, 데이터가 많다고 한다면 큰 문제가 발생하게 된다.

아래와 같은 경고 메시지가 출력된다.

 

경고 메시지

HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

 

아래와 같이 엔티티를 정의해 보자


@Entity
public class Category {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  private String name;

  @ManyToMany(mappedBy = "categories")
  private List<Article> articles = new ArrayList<>();
  
}
@Entity
public class Article {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  private String title;

  @ManyToMany
  @JoinTable(name = "article_category",
      joinColumns = { @JoinColumn(name = "article_id") },
      inverseJoinColumns = { @JoinColumn(name = "category_id") })
  private List<Category> categories = new ArrayList<>();

}

 

Article 을 페이징으로 조회 해보자


@Query(
	value = "select a from Article a",
	countQuery = "select count(a) from Article a"
)
Page<Article> findAllByJoinFetch(Pageable pageable);

아래와 같은 쿼리가 출력된다.

Hibernate: 
    select
        article0_.id as id1_0_,
        article0_.title as title2_0_ 
    from
        article article0_ limit ? offset ?
Hibernate: 
    select
        count(article0_.id) as col_0_0_ 
    from
        article article0_

 

Article 을 페이징으로 조회하면서 categories 를 fetch join 해보자


@Query(
	value = "select a from Article a join fetch a.categories",
	countQuery = "select count(a) from Article a"
)
Page<Article> findAllByJoinFetch(Pageable pageable);

아래와 같은 쿼리가 출력 되는데, limit 와 offset 이 빠져있다!

데이터를 모두 메모리로 읽은 후에, 메모리에서 페이징 처리를 하고 있다고 보면 된다.

Hibernate: 
    select
        article0_.id as id1_0_0_,
        category2_.id as id1_2_1_,
        article0_.title as title2_0_0_,
        category2_.name as name2_2_1_,
        categories1_.article_id as article_1_1_0__,
        categories1_.category_id as category2_1_0__ 
    from
        article article0_ 
    inner join
        article_category categories1_ 
            on article0_.id=categories1_.article_id 
    inner join
        category category2_ 
            on categories1_.category_id=category2_.id
Hibernate: 
    select
        count(article0_.id) as col_0_0_ 
    from
        article article0_

 

해결 방법


먼저 메모리에서 페이징되는 현상이 운영중인 서버에서 일어난다면, 크리티컬한 이슈가 발생할 것으로 생각이 들기 때문에, 원천적으로 이런 현상을 막을수 있는 옵션 값이 있다.

아래와 같이 설정하게 되면 collection 을 fetch 할때 페이징 처리하게 된다면 오류가 발생된다.

이렇게 설정을 하면 보통 개발 후 개발 테스트를 할때 발견이 되기 때문에 운영에 올라가기전에 개선의 여지가 있다고 생각한다.

hibernate.query.fail_on_pagination_over_collection_fetch: true

 

 

@BatchSize + 즉시로딩

보통 N+1 쿼리를 회피하기 위해서 fetch join 으로 접근하기 때문에 페이징을 해야 한다면 BatchSize 로 N+1 쿼리를 회피하는 방법이 있다.

@Entity
public class Article {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  private String title;

  @BatchSize(size=100)
  @ManyToMany(fetch = FetchType.EAGER)
  @JoinTable(name = "article_category",
      joinColumns = { @JoinColumn(name = "article_id") },
      inverseJoinColumns = { @JoinColumn(name = "category_id") })
  private List<Category> categories = new ArrayList<>();

}

이렇게 하면 항상 collection 을 즉시로딩한다는 점을 생각하고 유의해서 사용해야 한다.

반응형