Spring

반려건 돌봄 서비스 (5) - 중복 제거한 페이징 시 카운트 쿼리에 fetchOne()을 사용해야 하는 이유

인성코린이 2025. 3. 15. 16:26

돌봄 예약 가능 날짜를 등록한 회원(돌봄사) 중, 현재 예약이 가능한 회원(돌봄사)만 조회하는 로직을 QueryDSL을 사용하여 구현했었다. 이때, DTO로 직접 조회하고 페이징을 적용하면서 중복 제거된 결과를 반환하는 방법에 대해 새로운 사실을 알게 되었다.

 

보통 JPA에서 카운트 쿼리는 fetchCount()를 사용하여 개수를 조회했었어서, QueryDSL에서도 똑같이 fetchCount()를 사용해서 content 개수를 조회하려고 했다. 그런데 문제가 카운트 쿼리가 중복된 데이터들을 제외한 content 개수를 카운트 쿼리로 날려야 하는데 자꾸 중복 데이터를 포함한 content 개수를 카운트 쿼리로 날리고 있는 상황이 발생했다. 왜 인지하고 찾아보니 중복 제거를 고려할 때는 fetchOne()을 사용해야 중복이 제거된 정확한 카운트가 반환된다는 점을 알게 되었다.

 

코드로 살펴보자면

 

 

1) select 문에서 DTO를 직접 매핑하면서 .distinct()를 사용하여 중복을 제거, 이렇게 하면 careAvailableDate 테이블에서 같은 sitter.id가 여러 번 나오는 경우라도 하나만 선택됨.

 

2) .distinct()를 적용한 개수를 구할 때는 fetchOne()을 사용해야 함.(여기서 fetchCount()를 사용하면 중복이 제거되지 않은 상태에서 개수를 세므로 정확하지 않은 결과가 나올 수 있기 때문 + JPA에서도 동일)

 

 

 

그럼 이번엔 JPA로 예를 들자면

 

// JPA에서 fetchCount()를 사용했을 때
JPAQuery<Long> countQuery = queryFactory
    .select(careAvailableDate.sitter.id.countDistinct()) // 또는 careAvailableDate.sitter.countDistinct()
    .from(careAvailableDate)
    .where(careAvailableDate.status.eq(CareAvailableDateStatus.POSSIBILITY));

long totalCount = countQuery.fetchCount(); // 중복이 제거되지 않은 상태에서 개수를 셈

 

JPA에서 카운트 쿼리를 실행할 때 fetchCount()를 사용하면, 내부적으로 단순한 COUNT(*) sql 쿼리문이 실행이 되게 된다.
중복 제거(DISTINCT)가 필요한 경우, fetchCount()를 사용하면 중복이 제거되지 않은 상태에서 카운트 쿼리가 나감.

-> 내부적으로 단순한 COUNT(*) SQL이 실행되므로 중복이 제거되지 않은 값이 나옴.

 

// JPA에서 fetchOne()을 사용했을 때
JPAQuery<Long> countQuery = queryFactory
    .select(careAvailableDate.sitter.id.countDistinct())
    .from(careAvailableDate)
    .where(careAvailableDate.status.eq(CareAvailableDateStatus.POSSIBILITY));

long totalCount = countQuery.fetchOne(); // 중복 제거된 정확한 개수 반환

 

 

이 경우 countDistinct()와 fetchOne()을 함께 사용하면 내가 원했던 중복이 제거된 정확한 개수를 반환받을 수 있음.

 

 


 

결론

  • JPA, QueryDSL에서 중복 제거된 결과를 페이징 처리할 때는 fetchOne()을 사용하여 정확한 개수를 구해야 한다.
  • fetchCount()를 사용하면 중복 제거가 적용되지 않아 의도하지 않은 잘못된 개수가 나올 수 있다.