커서기반 페이지네이션 + queryDsl 동적 쿼리

고승원·2023년 2월 28일
0

Spring

목록 보기
3/14

서론

내 주문 리스트를 조회하는 api를 작성했다.

조회할 엔티티는 위와 같고 조건은

  • visit_date로 오름차순 정렬,
  • orderStatus가 없으면 전체 조회, 있으면 where절의 조건이 된다.
  • 페이지네이션

위 조건(동적쿼리, 페이지네이션)을 만족하려면 jdbcTemplate, MyBatis, queryDsl의 선택지가 있다.

페이지 네이션

오프셋기반과 커서기반 두가지로 나뉜다.

오프셋 기반 페이지네이션은 레코드가 많아질수록 오버헤드가 생기고 인덱스별 검색이 가능하지만, 무한스크롤을 사용하기에 다 커서기반 페이지네이션을 사용했다.


커서기반 페이지 네이션의 개념은 아래 블로그에 상세하게 적혀있다.

커서 기반 페이지네이션 (Cursor-based Pagination) 구현하기

queryDsl 설정

본인은 queryDsl로 구현하려 하는데, 설정 관련해서는 테코블을 참조했다.

Spring Boot에 QueryDSL을 사용해보자


구현 코드

jpaQueryFactory와 q객체를 생성

private final JPAQueryFactory jpaQueryFactory;
QOrder qOrder = QOrder.order;

쿼리 생성

@Override
public List<Order> findAllByMemberIdOrderByVisitDateAsc(
		Long memberId,            //내 주문 리스트를 조회하기 위한 id
		String option,            //orderStatus가 있을경우 조회조건에 포함
		LocalDateTime cursorTime, // 정렬 조건이 visitTime이기 떄문에 커서도 LocalDateTime
		int pageSize.             // 한번에 제공할 데이터 
) {
	return jpaQueryFactory
		.selectFrom(qOrder)     
			.where(
				gtOrderTime(cursorTime),      //동적 쿼리 -> 하단 참조
				orderStatus(option),          //동적 쿼리 -> 하단 참조
				qOrder.memberId.eq(memberId)  //매번 사용되는 검색 조건
		).orderBy(qOrder.visitDate.asc())     //visitDate 오름차순 정렬
		.limit(pageSize)                      //한 번에 보여줄 데이터 개수
		.fetch();
	}

동적 쿼리를 위한 메서드

private BooleanExpression gtOrderTime(LocalDateTime cursorTime) {
	// cursorTime이 null이라면 조건은 없고, 있다면 cursorTime 보다 큰 데이터만 조회한다.
	return cursorTime == null ? null : qOrder.visitDate.gt(cursorTime);
}

private BooleanExpression orderStatus(String option) {
	// option이 null이라면 조건은 없고, 있다면 option 과 같은 데이터만 조회한다.
	return option == null ? null : qOrder.orderStatus.eq(OrderStatus.valueOf(option));
}

전체 코드

//OrderCustomRepositoryImple.java
@Repository
@RequiredArgsConstructor
public class OrderCustomRepositoryImpl implements OrderCustomRepository {
	private final JPAQueryFactory jpaQueryFactory;
	QOrder qOrder = QOrder.order;

	@Override
	public List<Order> findAllByMemberIdOrderByVisitDateAsc(
		Long memberId,
		String option,
		LocalDateTime cursorTime,
		int pageSize
) {
	return jpaQueryFactory
			.selectFrom(qOrder)
			.where(
					gtOrderTime(cursorTime),
					orderStatus(option),
					qOrder.memberId.eq(memberId)
			).orderBy(qOrder.visitDate.asc())
			.limit(pageSize)
			.fetch();
	}

	private BooleanExpression gtOrderTime(LocalDateTime cursorTime) {
		return cursorTime == null ? null : qOrder.visitDate.gt(cursorTime);
	}

	private BooleanExpression orderStatus(String option) {
		return option == null ? null : qOrder.orderStatus.eq(OrderStatus.valueOf(option));
	}
}

반환값

//OrderService.java
@Transactional(readOnly = true)
public List<Order> getMyOrders(MyOrdersRequest myOrdersRequest, Long memberId) {

	LocalDateTime cursorTime = null;
	if (myOrdersRequest.cursorId() != null) {
		cursorTime = orderRepository.findById(myOrdersRequest.cursorId())
				.map(Order::getVisitDate)
				.orElse(null);
	}

	return orderQueryDslRepository.findAllByMemberIdOrderByVisitDateAsc(
			memberId,
			myOrdersRequest.orderStatus(),
			cursorTime,
			myOrdersRequest.pageSize()
	);
}

커서 기반 페이지네이션을 구현할 때 마지막 데이터에 정보도 함께 넘겨줘야 하는데, 이게 추후에 cursor가 된다.

visitTime으로 정렬을 하기 때문에 id로 cursorTime을 조회하고, Repository에 호출한다. 조회된 데이터가 없으면 반환된 pageSize로 상위 레이어에서 확인한다.

profile
봄은 영어로 스프링

0개의 댓글