fetchJoin과 pagination을 같이 사용할 때
배경
- 페이징을 하려고 하는데 fetchJoin하고 같이 사용할 경우
- 정확히 말하면 fetchJoin하고 limit을 같이 사용할 경우
firstResult/maxResults specified with collection fetch; applying in memory
- 이러한 오류는 아니지만 경고 메세지를 보게 됩니다. - 즉 실행도 되고 데이터 양이 적을 때는 모르지만 나중에 문제 발생
저도 데이터를 가지고 테스트 할 때는 경고를 못보고 넘어갔는데 테스트 코드를 짜면서 계속 실패해서 찾게 되었습니다. (테스트 코드의 중요성)
문제
- 1개의 board에 여러개의 hashTag가 있음
이전에 작성한 코드
override fun findBySearchingPagination(
pageable: Pageable,
search: String?,
category: String?,
hashTagList: List<BoardHashTag>
): Page<Board> {
val results = queryFactory.selectFrom(board).distinct()
.leftJoin(board.boardHashTagList, boardHashTag).fetchJoin()
.where(
eqTitle(search),
eqCategory(category),
eqHashTagList(hashTagList)
)
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.orderBy(board.id.desc())
.fetch()
}
예상
select * from board b left join hashTag h on b.id = h.board_id
where h.id in (1, 2. ..) limit 5
하지만 결과는
select
distinct board0_.id as id1_0_,
board0_.created_date as created_2_0_,
board0_.modified_date as modified3_0_,
board0_.board_thumbnail_url as board_th4_0_,
board0_.category_id as categor11_0_,
board0_.comment_count as comment_5_0_,
board0_.content as content6_0_,
board0_.is_private as is_priva7_0_,
board0_.like_count as like_cou8_0_,
board0_.member_id as member_i9_0_,
board0_.title as title10_0_
from
board board0_
left outer join
board_hash_tag boardhasht1_
on board0_.id=boardhasht1_.board_id
⇒ limit이 없다.
이 부분은 실제로 QueryTranslatorImpl.java의 List list(SharedSessionContractImplementor session, QueryParameters queryParameters) 부분부터 따라가보면 왜 이렇게 동작하는지 코드로 확인 할 수 있다고 한다.
만약 Fetch Join을 사용한다면 RowSelection 객체인 selection을 복사해서 queryParametersToUse 로 사용하게 되는데 이 selection 객체는 우리가 사용하고자 하는 limit 정보를 아래와 같이 전부 null로 가지고 있다.
공식문서 Fetch should be used together with setMaxResults()or setFirstResult() , as these operations are based on the result rows which usually contain duplicates for eager collection fetching, hence, the number of rows is not what you would expect.
해결책
- 엔티티를 조회할거라면 comment를 기준(ManyToOne)으로 조회하면 원하는 결과를 얻을 수 있다.
- fetchJoin을 사용하지 않고 default_batch_fetch_size 사용 (저는 이것을 사용했습니다.)
- https://balldev.tistory.com/76
MultipleBagFetchException 발생시 - fetchJoin 여러개 썼을 때
JPA에서 N + 1 문제를 해결하려고 fetchJoin 을 사용하게 됩니다. 이 때 하나의 entity에서 1:N이 여러개 일때 fetchJoin을 여러번 사용했는데 MultipleBagFetchException이 발생하게 됩니다. 즉, 2개 이상의 OneT..
balldev.tistory.com
select
distinct board0_.id as id1_0_,
board0_.created_date as created_2_0_,
board0_.modified_date as modified3_0_,
board0_.board_thumbnail_url as board_th4_0_,
board0_.category_id as categor11_0_,
board0_.comment_count as comment_5_0_,
board0_.content as content6_0_,
board0_.is_private as is_priva7_0_,
board0_.like_count as like_cou8_0_,
board0_.member_id as member_i9_0_,
board0_.title as title10_0_
from
board board0_
left outer join
board_hash_tag boardhasht1_
on board0_.id=boardhasht1_.board_id
order by
board0_.id desc limit ?
결론
- 아직 모르는게 많다.
- 직접 해보며 경험하는게 있는듯하다.