알아보니 2개 이상의 OneToMany 자식 테이블에 fetch를 걸어버리면 발생하는 예외라네요.
저 같은 경우는 history-question 그리고 answer-image 로 OneToMany가 2개 있어 존재해서 발생했습니다.
이런 경우는 크게 2가지 방법으로 해결 가능합니다.
1. 쿼리 분리
간단하게 그냥 2번 조회하도록 쿼리를 분리해버리면 됩니다. 근데 아무래도 코드가 더 복잡해지겠죠.
그래서 전 2번 방법을 선택했습니다.
2. default_batch_fetch_size 설정
이번에 처음 알게된 설정인데 진짜 신기합니다.
기존에 N+1 문제는 다음과 같이 발생합니다.
history 하나당 question 여러개가 검사되고 다시 또 question과 1대1 매칭되는 answer가 하나당 image 여러개가 검사되죠?
이때 그럼 default_batch_fetch_size 설정을 세팅하면 어떻게 될까요?
default_batch_fetch_size 만큼 지정된 숫자만큼 in절에 부모키(answer_id)가 넘어가서 자식 엔티티들이 조회됩니다.
@Query("select h from History h " +
"left join fetch h.questions q " +
"left join fetch q.answer a " +
"where h.id = :historyId")
History findHistoryWithQuestionsAndAnswers(@Param("historyId") Long historyId);
이때 기존 쿼리에서 answer-image를 fetch 조회하는 부분은 삭제해야 in절로 조회하기가 적용됩니다.
저는 1000개로 설정해서 그런지 많이 있네요.
더 놀라운 점은 저 default_batch_fetch_size 설정 하나만으로 애플리케이션 전반의 다른 지연 로딩 N+1 문제도 함께 해결해주는 효과를 얻을 수 있습니다.