Skip to content

Commit

Permalink
Merge pull request #51 from team-nabi/NABI-258--junhyuk--fix--cursor-…
Browse files Browse the repository at this point in the history
…pagination

NABI-258--FIX: cursor pagination
  • Loading branch information
hi-june authored Nov 20, 2023
2 parents 1ad1b36 + 0aab0a7 commit 8ae0440
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.prgrms.nabimarketbe.domain.card.service.CardService;
import org.prgrms.nabimarketbe.domain.category.entity.CategoryEnum;
import org.prgrms.nabimarketbe.domain.item.entity.PriceRange;
import org.prgrms.nabimarketbe.global.util.OrderCondition;
import org.prgrms.nabimarketbe.global.util.ResponseFactory;
import org.prgrms.nabimarketbe.global.util.model.CommonResult;
import org.prgrms.nabimarketbe.global.util.model.SingleResult;
Expand Down Expand Up @@ -76,13 +77,17 @@ public ResponseEntity<SingleResult<CardPagingResponseDTO>> getCardsByCondition(
@RequestParam(required = false) String cursorId,
@RequestParam Integer size
) {
// TODO: 클라이언트에게 정렬 조건 받도록 추후에 수정하면 더 유연할 듯
OrderCondition condition = OrderCondition.CARD_CREATED_DESC;

CardPagingResponseDTO cardListReadPagingResponseDTO = cardService.getCardsByCondition(
category,
priceRange,
status,
cardTitle,
cursorId,
size
size,
condition
);

return ResponseEntity.ok(ResponseFactory.getSingleResult(cardListReadPagingResponseDTO));
Expand Down Expand Up @@ -116,7 +121,7 @@ public ResponseEntity<SingleResult<CardResponseDTO<CardUpdateResponseDTO>>> upda

@PutMapping("/status/{cardId}")
public ResponseEntity<CommonResult> updateCardStatusById(
@RequestHeader(name = "authorization") String token,
@RequestHeader(name = "Authorization") String token,
@PathVariable Long cardId,
@RequestBody CardStatusUpdateRequestDTO cardStatusUpdateRequestDTO
) {
Expand All @@ -131,7 +136,7 @@ public ResponseEntity<CommonResult> updateCardStatusById(

@GetMapping("/{status}/my-cards")
public ResponseEntity<SingleResult<CardPagingResponseDTO>> getMyCardsByStatus(
@RequestHeader(name = "authorization") String token,
@RequestHeader(name = "Authorization") String token,
@PathVariable CardStatus status,
@RequestParam(required = false) String cursorId,
@RequestParam Integer size
Expand All @@ -148,7 +153,7 @@ public ResponseEntity<SingleResult<CardPagingResponseDTO>> getMyCardsByStatus(

@DeleteMapping("/{cardId}")
public ResponseEntity<CommonResult> deleteCardById(
@RequestHeader(name = "authorization") String token,
@RequestHeader(name = "Authorization") String token,
@PathVariable Long cardId
) {
cardService.deleteCardById(token, cardId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.prgrms.nabimarketbe.domain.category.entity.CategoryEnum;
import org.prgrms.nabimarketbe.domain.item.entity.PriceRange;
import org.prgrms.nabimarketbe.domain.user.entity.User;
import org.springframework.data.domain.Pageable;

import java.util.List;

Expand All @@ -17,7 +18,7 @@ CardPagingResponseDTO getCardsByCondition(
List<CardStatus> status,
String title,
String cursorId,
Integer size
Pageable pageable
);

CardPagingResponseDTO getMyCardsByStatus(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package org.prgrms.nabimarketbe.domain.card.repository;

import com.querydsl.core.types.ConstantImpl;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.core.types.dsl.StringExpressions;
import com.querydsl.core.types.dsl.StringTemplate;
import com.querydsl.jpa.impl.JPAQueryFactory;
Expand All @@ -20,18 +19,19 @@
import org.prgrms.nabimarketbe.domain.item.entity.PriceRange;
import org.prgrms.nabimarketbe.domain.suggestion.dto.response.projection.SuggestionInfo;
import org.prgrms.nabimarketbe.domain.user.entity.User;
import org.prgrms.nabimarketbe.global.util.CursorPaging;
import org.prgrms.nabimarketbe.global.util.QueryDslUtil;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.util.ArrayList;
import java.util.List;

import static org.prgrms.nabimarketbe.domain.card.entity.QCard.card;
import static org.prgrms.nabimarketbe.domain.item.entity.QItem.item;
import static org.prgrms.nabimarketbe.domain.suggestion.entity.QSuggestion.suggestion;

@RequiredArgsConstructor
public class CardRepositoryImpl implements CardRepositoryCustom {
public class CardRepositoryImpl implements CardRepositoryCustom, CursorPaging {
private static final int FAMOUS_CARD_SIZE = 5;

private final JPAQueryFactory jpaQueryFactory;
Expand All @@ -43,7 +43,7 @@ public CardPagingResponseDTO getCardsByCondition(
List<CardStatus> status,
String cardTitle,
String cursorId,
Integer size
Pageable pageable
) {
List<CardListReadResponseDTO> cardList = jpaQueryFactory.select(
Projections.fields(
Expand All @@ -67,14 +67,11 @@ public CardPagingResponseDTO getCardsByCondition(
priceRangeEquals(priceRange),
titleEquals(cardTitle)
)
.orderBy(getOrderSpecifier(Sort.by(
Sort.Order.desc("createdDate"),
Sort.Order.desc("cardId")
)))
.limit(size)
.orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort(), card))
.limit(pageable.getPageSize())
.fetch();

String nextCursor = cardList.size() < size ? null : generateCursor(cardList.get(cardList.size() - 1));
String nextCursor = cardList.size() < pageable.getPageSize() ? null : generateCursor(cardList.get(cardList.size() - 1));

return new CardPagingResponseDTO(cardList, nextCursor);
}
Expand Down Expand Up @@ -106,7 +103,15 @@ public CardPagingResponseDTO getMyCardsByStatus(
card.status.eq(status),
card.user.eq(user)
)
.orderBy(card.createdDate.desc()) // 디폴트는 생성일자 최신순 정렬
.orderBy(
QueryDslUtil.getOrderSpecifier(
Sort.by(
Sort.Order.desc("createdDate"),
Sort.Order.desc("cardId")
),
card
)
)
.limit(size)
.fetch();

Expand Down Expand Up @@ -149,6 +154,29 @@ public List<CardSuggestionResponseDTO> getSuggestionAvailableCards(
return cardList;
}

@Override
public BooleanExpression cursorId(String cursorId) {
if (cursorId == null) {
return null;
}

// 생성일자
StringTemplate dateCursorTemplate = Expressions.stringTemplate(
"DATE_FORMAT({0}, {1})",
card.createdDate,
ConstantImpl.create("%Y%m%d%H%i%s")
);

// pk
StringExpression pkCursorTemplate = StringExpressions.lpad(
card.cardId.stringValue(),
8,
'0'
);

return dateCursorTemplate.concat(pkCursorTemplate).lt(cursorId);
}

@Override
public List<CardFamousResponseDTO> getCardsByPopularity() {
List<CardFamousResponseDTO> cardList = jpaQueryFactory
Expand Down Expand Up @@ -178,25 +206,6 @@ private BooleanExpression statusEquals(CardStatus status) {
return card.status.eq(status);
}

private BooleanExpression cursorId(String cursorId) {
if (cursorId == null) {
return null;
}

StringTemplate stringTemplate = Expressions.stringTemplate(
"DATE_FORMAT({0}, {1})",
card.createdDate, // 디폴트는 생성일자 최신순 정렬
ConstantImpl.create("%Y%m%d%H%i%s")
);

return stringTemplate.concat(StringExpressions.lpad(
card.cardId.stringValue(),
8,
'0'
))
.lt(cursorId);
}

private BooleanExpression categoryEquals(CategoryEnum category) {
if (category == null) {
return null;
Expand Down Expand Up @@ -240,15 +249,4 @@ private String generateCursor(CardListReadResponseDTO cardListReadResponseDTO) {
.replace(":", "")
+ String.format("%08d", cardListReadResponseDTO.getCardId());
}

private OrderSpecifier[] getOrderSpecifier(Sort sort) {
List<OrderSpecifier> orders = new ArrayList<>();

for (Sort.Order order : sort) { // Sort에 여러 정렬 기준을 담을 수 있음
Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
orders.add(QueryDslUtil.getSortedColumn(direction, card, order.getProperty()));
}

return orders.toArray(OrderSpecifier[]::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import org.prgrms.nabimarketbe.domain.user.service.CheckService;
import org.prgrms.nabimarketbe.global.error.BaseException;
import org.prgrms.nabimarketbe.global.error.ErrorCode;
import org.prgrms.nabimarketbe.global.util.OrderCondition;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -201,20 +204,29 @@ public CardUserResponseDTO getCardById(

@Transactional(readOnly = true)
public CardPagingResponseDTO getCardsByCondition(
CategoryEnum category,
PriceRange priceRange,
List<CardStatus> status,
String title,
String cursorId,
Integer size
CategoryEnum category,
PriceRange priceRange,
List<CardStatus> status,
String title,
String cursorId,
Integer size,
OrderCondition orderCondition
) {

// 전달 받은 orderCondition 과 page size 에 맞게 pageRequest 를 구성 후 repo 에 넘김
PageRequest pageRequest = PageRequest.of(
0,
size,
getSortFromOrderCondition(orderCondition)
);

return cardRepository.getCardsByCondition(
category,
priceRange,
status,
title,
cursorId,
size
pageRequest
);
}

Expand Down Expand Up @@ -374,4 +386,16 @@ private List<CardImage> addThumbnail(

return newCardImages;
}

private Sort getSortFromOrderCondition(OrderCondition orderCondition) {
switch (orderCondition) {
case CARD_CREATED_DESC -> {
return Sort.by(
Sort.Order.desc("createdDate"),
Sort.Order.desc("cardId")
);
}
default -> throw new BaseException(ErrorCode.INVALID_ORDER_CONDITION);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.prgrms.nabimarketbe.global.util.QueryDslUtil;
import org.springframework.data.domain.Sort;

@Slf4j
@RequiredArgsConstructor
Expand Down Expand Up @@ -57,7 +59,15 @@ public HistoryListReadLimitResponseDTO getHistoryBySize(Integer size) {
.leftJoin(card).on(card.cardId.eq(completeRequest.fromCard.cardId))
.leftJoin(card).on(card.cardId.eq(completeRequest.toCard.cardId).as("toCardAlias"))
.where(completeRequest.completeRequestStatus.eq(CompleteRequestStatus.ACCEPTED))
.orderBy(completeRequest.modifiedDate.desc())
.orderBy(
QueryDslUtil.getOrderSpecifier(
Sort.by(
Sort.Order.desc("modifiedDate"),
Sort.Order.desc("completeRequestId")
),
completeRequest
)
)
.limit(size)
.fetch();

Expand Down Expand Up @@ -99,7 +109,15 @@ public HistoryListReadPagingResponseDTO getHistoryByUser(
.where(cursorIdLessThan(cursorId),
completeRequest.completeRequestStatus.eq(CompleteRequestStatus.ACCEPTED),
completeRequest.fromCard.user.eq(user).or(completeRequest.toCard.user.eq(user)))
.orderBy(completeRequest.modifiedDate.desc())
.orderBy(
QueryDslUtil.getOrderSpecifier(
Sort.by(
Sort.Order.desc("modifiedDate"),
Sort.Order.desc("completeRequestId")
),
completeRequest
)
)
.limit(size)
.fetch();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import org.prgrms.nabimarketbe.domain.suggestion.entity.SuggestionType;
import org.prgrms.nabimarketbe.global.error.BaseException;
import org.prgrms.nabimarketbe.global.error.ErrorCode;
import org.prgrms.nabimarketbe.global.util.QueryDslUtil;
import org.springframework.data.domain.Sort;

import java.util.List;

import static org.prgrms.nabimarketbe.domain.card.entity.QCard.card;
import static org.prgrms.nabimarketbe.domain.completeRequest.entity.QCompleteRequest.completeRequest;
import static org.prgrms.nabimarketbe.domain.suggestion.entity.QSuggestion.suggestion;

@RequiredArgsConstructor
Expand Down Expand Up @@ -62,7 +65,15 @@ public SuggestionListReadPagingResponseDTO getSuggestionsByType(
.join(getQcardByDirectionType(directionType), card)
.on(getExpressionByDirectionType(directionType, cardId))
.where(cursorIdLessThan(cursorId), suggestionTypeEquals(suggestionType))
.orderBy(suggestion.createdDate.desc())
.orderBy(
QueryDslUtil.getOrderSpecifier(
Sort.by(
Sort.Order.desc("createdDate"),
Sort.Order.desc("suggestionId")
),
suggestion
)
)
.limit(size)
.fetch();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public enum ErrorCode {
CATEGORY_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "CA0001", "존재하지 않는 카테고리입니다."),
COMPLETE_REQUEST_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "CR0001", "존재하지 않는 제안 요청입니다."),
COMPLETE_REQUEST_MYSELF_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "CR0002", "자신의 카드에는 거래 성사 요청을 할 수 없습니다."),
BATCH_INSERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "B0001", "이미지 저장 중에 문제가 발생했습니다.");
BATCH_INSERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "B0001", "이미지 저장 중에 문제가 발생했습니다."),
INVALID_ORDER_CONDITION(HttpStatus.INTERNAL_SERVER_ERROR, "O0001", "유효하지 않은 정렬 조건입니다.");

private HttpStatus status;
private String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.prgrms.nabimarketbe.global.util;

import com.querydsl.core.types.dsl.BooleanExpression;

public interface CursorPaging {
/**
* cursorId를 받아 cursor Id로 조건절을 만드는 메소드
* @param cursorId
* @return
*/
BooleanExpression cursorId(String cursorId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.prgrms.nabimarketbe.global.util;

public enum OrderCondition {
CARD_CREATED_DESC
}
Loading

0 comments on commit 8ae0440

Please sign in to comment.