[SpringBoot] Pagenation JPA vs QueryDSL 사용법 차이

박철현·2023년 11월 14일

개념정리

목록 보기
5/5

개인 프로젝트를 하다 보니 Query DSL로 페이징을 구현해야 하는 상황에 직면하여 JPA와 QueryDSL 페이징 처리 방법에 대해 포스팅 하고자 합니다.

공통점

  • Page / PageRequest / Pageable 활용
  • PageRequest의 of 메소드를 사용해 Pageable 객체를 생성하는 것 동일
  • 페이지에 맞춘 객체 반환
  • Pageable 객체 생성 부분에서 페이지 번호는 0부터 시작

JPA 사용법

  • PageRequest의 of 메소드를 사용해 Pageable 객체를 생성합니다.
    • 첫번째 매개변수 : 몇번째 페이지인지
    • 두번째 매개변수 : 페이지당 개수
  • Repository에서 findAll메서드의 매개변수로 Pageable 객체를 활용합니다.
// Pageable 객체 생성, Service or Controller단
 public Page<Question> getList(int page) {
        Pageable pageable = PageRequest.of(page, 10);
        return this.questionRepository.findAll(pageable);
    }
// Repository
 Page<Question> findAll(Pageable pageable);

Query DSL

  • PageRequest의 of 메소드를 사용해 Pageable 객체를 생성합니다.
  • Repository 구현체에서 .offset으로 페이지 번호, .limit으로 한 페이지당 갯수 지정
  • 페이지 처리 대상 개수 구하는 쿼리
  • PageImpl 객체 생성
public Page<Expenditure> searchExpenditure(Member member, SearchRequestDTO searchRequestDTO, Pageable pageable) {
    // 기존의 쿼리 로직...

	// 페이지에 맞춘 데이터 추출
    List<Expenditure> results = jpaQueryFactory.selectFrom(expenditure)
        .where(/* 조건들... */)
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();
        
	// 전체 데이터 개수 추출
    long total = jpaQueryFactory.selectFrom(expenditure)
        .where(/* 동일한 조건들... */)
        .fetchCount();

    return new PageImpl<>(results, pageable, total);
}
  • fetchCount 사용 이유 : JPA에서 Page 처리 시 페이지네이션 대상 전체 데이터 개수를 구하는 쿼리도 발생
    • QueryDSL도 동일하게 적용하기 위함

차이점

  • 페이징 대상 전체 개수를 따로 구해줘야하고 PageImpl 객체를 생성해줘야 합니다.
  • JPA를 활용하는 것이 편하나 쿼리의 복잡도 등의 이유로 QueryDsl을 사용해야 하는 경우 두 쿼리를 작성해야하는 번거로움은 불가피합니다.

fetchCount() 메서드 더이상 지원하지 않음

지원하지 않는 이유

  • GroupBy나 Having절이 포함되는 복잡한 쿼리에서는 잘 작동하지 않음
  • 메모리에서 카운트를 생성하기 때문에 큰 결과 집합에서는 성능에 문제가 생길 수 있음
    • 쿼리 조건에 맞는 데이터를 모두 가져온 뒤 개수 세는 문제

대안

  • count 쿼리를 별도록 작성한다.
    • fetchCount() 삭제하고 select(entity.count()). ~~~ .fetchOne()
    • fetchOne() Nullable 하기에 null일 경우 0으로 변경하여 리턴

변경전

	/*
		페이지별 지출 데이터 조회
	 */
	@Override
	public Page<Expenditure> searchExpenditure(Member member, SearchRequestDTO searchRequestDTO) {
		LocalDate startDate = searchRequestDTO.getStartDate();
		LocalDate endDate = searchRequestDTO.getEndDate();
		Long categoryId = searchRequestDTO.getCategoryId();
		Integer minPrice = searchRequestDTO.getMinPrice();
		Integer maxPrice = searchRequestDTO.getMaxPrice();

		Integer pageNumber = searchRequestDTO.getPageNumber();
		Integer pageLimit = searchRequestDTO.getPageLimit();

		Pageable pageable = PageRequest.of(pageNumber, pageLimit);

		// 여러 조건을 동적으로 추가할 수 있도록 도와주는 BooleanBuilder 도입
		BooleanBuilder builder = createBooleanBuilder(categoryId, minPrice, maxPrice);

		// 조건에 맞는 목록 구하기
		List<Expenditure> expenditures = jpaQueryFactory.selectFrom(expenditure)
			.where(
				expenditure.member.eq(member),
				expenditure.spendDate.between(startDate, endDate),
				builder // 조건 동적 추가
			)
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();

		// 전체 데이터 개수 추출
		long total = jpaQueryFactory.selectFrom(expenditure)
			.where(
				expenditure.member.eq(member),
				expenditure.spendDate.between(startDate, endDate),
				builder // 조건 동적 추가
			)
			.fetchCount();

		return new PageImpl<>(expenditures, pageable, total);
	}

변경 후

	/*
		페이지별 지출 데이터 조회
	 */
	@Override
	public Page<Expenditure> searchExpenditure(Member member, SearchRequestDTO searchRequestDTO) {
		LocalDate startDate = searchRequestDTO.getStartDate();
		LocalDate endDate = searchRequestDTO.getEndDate();
		Long categoryId = searchRequestDTO.getCategoryId();
		Integer minPrice = searchRequestDTO.getMinPrice();
		Integer maxPrice = searchRequestDTO.getMaxPrice();

		Integer pageNumber = searchRequestDTO.getPageNumber();
		Integer pageLimit = searchRequestDTO.getPageLimit();

		Pageable pageable = PageRequest.of(pageNumber, pageLimit);

		// 여러 조건을 동적으로 추가할 수 있도록 도와주는 BooleanBuilder 도입
		BooleanBuilder builder = createBooleanBuilder(categoryId, minPrice, maxPrice);

		// 조건에 맞는 목록 구하기
		List<Expenditure> expenditures = jpaQueryFactory.selectFrom(expenditure)
			.where(
				expenditure.member.eq(member),
				expenditure.spendDate.between(startDate, endDate),
				builder // 조건 동적 추가
			)
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();

		// 전체 데이터 개수 추출
		Long total = jpaQueryFactory.select(expenditure.count())
			.from(expenditure)
			.where(
				expenditure.member.eq(member),
				expenditure.spendDate.between(startDate, endDate),
				builder // 조건 동적 추가
			)
			.fetchOne();

		return new PageImpl<>(expenditures, pageable, total != null ? total : 0L);
	}
profile
비슷한 어려움을 겪는 누군가에게 도움이 되길

0개의 댓글