내 주문 리스트를 조회하는 api를 작성했다.
조회할 엔티티는 위와 같고 조건은
위 조건(동적쿼리, 페이지네이션)을 만족하려면 jdbcTemplate, MyBatis, queryDsl의 선택지가 있다.
오프셋기반과 커서기반 두가지로 나뉜다.
오프셋 기반 페이지네이션은 레코드가 많아질수록 오버헤드가 생기고 인덱스별 검색이 가능하지만, 무한스크롤을 사용하기에 다 커서기반 페이지네이션을 사용했다.
커서 기반 페이지네이션 (Cursor-based Pagination) 구현하기
본인은 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로 상위 레이어에서 확인한다.