-
QueryDSL으로 페이징 구현하기JPA 2023. 9. 18. 00:01
개요
페이지네이션에 대해 알아보다 보면 상황에 따라 offset 기반과 cursor 기반의 방식으로 구현할 수 있습니다.
이때 offset 방식을 활용하여 QueryDSL으로 쿼리를 구현해보고자 합니다.
Offset과 Limit 그리고 성능
SELECT * FROM TABLE_NAME LIMIT 10 OFFSET 5000;
offset절은 만약 N이 들어온다면 N번째행부터 읽겠다는 의미입니다.
예를 들어 5000이라면 5000번째 행부터 읽겠다는 의미입니다.
Limit 절은 쿼리 결과에서 지정된 순서에 위치한 레코드만 가져오고자 할 때 사용됩니다.
Limit에는 클라이언트가 요청한 크기 N이 들어가게 되고 offset을 통해 가져온 전체 데이터에서 상위 N개를 가져옵니다.
위 둘을 조합하게 되면 5000번째부터 10번째 레코드만 읽겠다는 의미입니다.
이때 offset이 점점 커지게 되면 조회비용이 증가하게 되어 주의해야 합니다.
N개 결과의 row만 DB에서 조회하는것이 아니라 모든 row를 디스크로부터 읽어오기 때문에 데이터베이스에 많은 부하를 주게 됩니다.
예를 들어 5000~5010번째 데이터를 가져오는 것이 아니라 1~5010번째 데이터를 디스크에서 읽어옵니다.
Spring에서 실행되는 쿼리
SELECT * FROM TABLE OFFSET 5000 ROWS FETCH FIRST 10 row only;
실제 날라가는 쿼리는 위와 같아서 찾아보니 limit offset 방식과 실행계획은 동일하게 보입니다.
구현
override fun selectDeliveryInfos(condition: Condition, pageable: Pageable, pagingQuery: PagingQuery): Page<DeliveryJpaEntity> { val delivery: QDeliveryJpaEntity = QDeliveryJpaEntity.deliveryJpaEntity val content = jpaQueryFactory.select(delivery) .from(delivery) .where(betweenDateConditionType(condition)) .offset(pageable.offset) .limit(pageable.pageSize.toLong()) .orderBy(setOrderByQuery(pagingQuery.sort)) .fetch() val count: Long = jpaQueryFactory .select(delivery.count()) .from(delivery) .where(betweenDateConditionType(condition)) .fetchOne() ?: 0 return PageImpl(content, pageable, count) } private fun setOrderByQuery(sortCondition: String): OrderSpecifier<*> { //TODO: sortCondition에 따라 분기문 추가 현재는 createdAt만 오름차순(날짜 빠른순)으로 지원 val delivery: QDeliveryJpaEntity = QDeliveryJpaEntity.deliveryJpaEntity return OrderSpecifier(Order.ASC, delivery.createdAt) }
offset과 limiet을 주고 orderBy의 경우에는 OrderSpecifier를 활용하여 지정하였습니다.
ORderSpecifier는 동적정렬에서 활용됩니다.
count 쿼리는 별도로 작성하였고 추후 데이터가 쌓여가면서 느려질 수 있기 때문에 비즈니스 상황에 따라 no offset 방법이나 트리거를 활용하여 count 수를 따로 관리해 주는 방안으로 빠르게 개선할 수 있을 것 같습니다.
혹은 offset대신 다음과 같은 방식도 고려해 볼 수 있습니다.
SELECT ... FROM ... WHERE ... AND id < ?last_seen_id ORDER BY id DESC FETCH FIRST 10 ROWS ONLY
이러한 방법을 seek method 또는 keyset pagination이라고 부릅니다.
참고자료
https://thalals.tistory.com/298
https://jojoldu.tistory.com/528
https://brownbears.tistory.com/582
'JPA' 카테고리의 다른 글
[JPA] Page vs Slice (1) 2023.10.08 QueryDSL 페이징에 QuerydslPredicateExecutor 활용하기 (0) 2023.10.03 QueryDSL으로 동적쿼리 작성하기 (0) 2023.09.17 JPA Persistable으로 성능최적화 해보기 (0) 2023.09.04 JPA 복합키 엔티티 (0) 2023.02.08