Left Join Fetch와 Page를 함께 사용할 때 발생하는 문제와 해결법Left Join Fetch와 Page를 함께 사용하면 다음과 같은 Hibernate 경고를 보게 된다
"HHH000104: firstResult/maxResults specified with collection fetch"
이 오류는 페이징(firstResult / maxResults)과 컬렉션 페치 조인(collection fetch join)을 동시에 사용할 때 발생한다
@EntityGraph 어노테이션 사용 두 단계 쿼리 방식으로, 페이징과 Fetch Join을 분리해 Hibernate 경고를 회피하는 전략이다
JOIN FETCH로 가져옴SELECT id FROM posts WHERE user_id = 1;
SELECT * FROM posts WHERE id IN (1,2,3...);
💡 복잡한 조인을 피하고, 필요한 경우에만 데이터를 로딩함으로써 성능을 최적화할 수 있다.
Two-Step Query Pattern은 다음과 같이 작동하여 Hibernate 오류를 피한다:
JOIN FETCH 실행👉 페이징과 Fetch Join을 직접 결합하지 않기 때문에 안전하다.
@Query("SELECT o.id FROM Order o " +
"WHERE o.userId = :userId " +
"AND (:period = 'all' OR (o.createdAt >= :startDate AND o.createdAt <= :endDate))")
Page<Long> findOrderIdsByPeriod(@Param("userId") Long userId,
@Param("period") String period,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate,
Pageable pageable);
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Page<Long> orderIdsPage = orderRepository.findOrderIdsByPeriod(
user.getId(), period, startDate, today, pageable
);
📌 장점:
JOIN FETCH를 피할 수 있음@Query("SELECT DISTINCT o FROM Order o " +
"LEFT JOIN FETCH o.orderDetails od " +
"LEFT JOIN FETCH od.saleProduct sp " +
"LEFT JOIN FETCH sp.option " +
"LEFT JOIN FETCH sp.product " +
"LEFT JOIN FETCH sp.stock " +
"WHERE o.id IN :ids " +
"ORDER BY o.createdAt DESC")
List<Order> findOrdersWithDetailsByIds(@Param("ids") List<Long> ids);
List<Long> orderIds = orderIdsPage.getContent();
List<Order> orders = orderRepository.findOrdersWithDetailsByIds(orderIds);
📌 장점:
DISTINCT 사용)| 목적 | 설명 |
|---|---|
| ✅ 성능 최적화 | 페이징 쿼리와 Fetch Join 분리로 효율적 |
| ✅ N+1 문제 방지 | 연관 엔티티를 한 번에 조회 |
| ✅ 안정성 확보 | Hibernate 오류 회피 |
| ✅ 대규모 데이터 처리 | 대용량에서도 효율적인 데이터 조회 가능 |