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 을 즉시로딩한다는 점을 생각하고 유의해서 사용해야 한다.
반응형
'개발관련' 카테고리의 다른 글
commit 메시지에 자동으로 branch명 추가해보기 (0) | 2020.11.04 |
---|---|
Webflux Functional Endpoints 시작하기 (0) | 2020.09.30 |
JPA - ManyToMany 관계시 Set과 List의 차이 (0) | 2020.08.11 |
SpringBoot - http request와 response 로깅하기 (0) | 2020.08.05 |
Spring 에서 Service 인터페이스 사용? (0) | 2020.05.20 |