spring boot/기술 적용

fetchJoin과 pagination을 같이 사용할 때

ballde 2022. 6. 24. 12:06

배경

  • 페이징을 하려고 하는데 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 ?

결론

  • 아직 모르는게 많다.
  • 직접 해보며 경험하는게 있는듯하다.