diff --git a/src/main/java/org/prgrms/nabimarketbe/domain/card/api/CardController.java b/src/main/java/org/prgrms/nabimarketbe/domain/card/api/CardController.java index 158c761a..95c9d6e9 100644 --- a/src/main/java/org/prgrms/nabimarketbe/domain/card/api/CardController.java +++ b/src/main/java/org/prgrms/nabimarketbe/domain/card/api/CardController.java @@ -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; @@ -76,13 +77,17 @@ public ResponseEntity> 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)); @@ -116,7 +121,7 @@ public ResponseEntity>> upda @PutMapping("/status/{cardId}") public ResponseEntity updateCardStatusById( - @RequestHeader(name = "authorization") String token, + @RequestHeader(name = "Authorization") String token, @PathVariable Long cardId, @RequestBody CardStatusUpdateRequestDTO cardStatusUpdateRequestDTO ) { @@ -131,7 +136,7 @@ public ResponseEntity updateCardStatusById( @GetMapping("/{status}/my-cards") public ResponseEntity> getMyCardsByStatus( - @RequestHeader(name = "authorization") String token, + @RequestHeader(name = "Authorization") String token, @PathVariable CardStatus status, @RequestParam(required = false) String cursorId, @RequestParam Integer size @@ -148,7 +153,7 @@ public ResponseEntity> getMyCardsByStatus( @DeleteMapping("/{cardId}") public ResponseEntity deleteCardById( - @RequestHeader(name = "authorization") String token, + @RequestHeader(name = "Authorization") String token, @PathVariable Long cardId ) { cardService.deleteCardById(token, cardId); diff --git a/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryCustom.java b/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryCustom.java index 0e8c18eb..a6ccf6a4 100644 --- a/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryCustom.java +++ b/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryCustom.java @@ -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; @@ -17,7 +18,7 @@ CardPagingResponseDTO getCardsByCondition( List status, String title, String cursorId, - Integer size + Pageable pageable ); CardPagingResponseDTO getMyCardsByStatus( diff --git a/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryImpl.java b/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryImpl.java index 0b543547..fe00778b 100644 --- a/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryImpl.java +++ b/src/main/java/org/prgrms/nabimarketbe/domain/card/repository/CardRepositoryImpl.java @@ -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; @@ -20,10 +19,11 @@ 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; @@ -31,7 +31,7 @@ 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; @@ -43,7 +43,7 @@ public CardPagingResponseDTO getCardsByCondition( List status, String cardTitle, String cursorId, - Integer size + Pageable pageable ) { List cardList = jpaQueryFactory.select( Projections.fields( @@ -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); } @@ -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(); @@ -149,6 +154,29 @@ public List 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 getCardsByPopularity() { List cardList = jpaQueryFactory @@ -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; @@ -240,15 +249,4 @@ private String generateCursor(CardListReadResponseDTO cardListReadResponseDTO) { .replace(":", "") + String.format("%08d", cardListReadResponseDTO.getCardId()); } - - private OrderSpecifier[] getOrderSpecifier(Sort sort) { - List 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); - } } diff --git a/src/main/java/org/prgrms/nabimarketbe/domain/card/service/CardService.java b/src/main/java/org/prgrms/nabimarketbe/domain/card/service/CardService.java index 80ccd3c9..10dbdb57 100644 --- a/src/main/java/org/prgrms/nabimarketbe/domain/card/service/CardService.java +++ b/src/main/java/org/prgrms/nabimarketbe/domain/card/service/CardService.java @@ -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; @@ -201,20 +204,29 @@ public CardUserResponseDTO getCardById( @Transactional(readOnly = true) public CardPagingResponseDTO getCardsByCondition( - CategoryEnum category, - PriceRange priceRange, - List status, - String title, - String cursorId, - Integer size + CategoryEnum category, + PriceRange priceRange, + List 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 ); } @@ -374,4 +386,16 @@ private List 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); + } + } } diff --git a/src/main/java/org/prgrms/nabimarketbe/domain/completeRequest/repository/CompleteRequestRepositoryImpl.java b/src/main/java/org/prgrms/nabimarketbe/domain/completeRequest/repository/CompleteRequestRepositoryImpl.java index 4c7877c5..c6fc372b 100644 --- a/src/main/java/org/prgrms/nabimarketbe/domain/completeRequest/repository/CompleteRequestRepositoryImpl.java +++ b/src/main/java/org/prgrms/nabimarketbe/domain/completeRequest/repository/CompleteRequestRepositoryImpl.java @@ -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 @@ -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(); @@ -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(); diff --git a/src/main/java/org/prgrms/nabimarketbe/domain/suggestion/repository/SuggestionRepositoryImpl.java b/src/main/java/org/prgrms/nabimarketbe/domain/suggestion/repository/SuggestionRepositoryImpl.java index bc183482..84ef3823 100644 --- a/src/main/java/org/prgrms/nabimarketbe/domain/suggestion/repository/SuggestionRepositoryImpl.java +++ b/src/main/java/org/prgrms/nabimarketbe/domain/suggestion/repository/SuggestionRepositoryImpl.java @@ -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 @@ -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(); diff --git a/src/main/java/org/prgrms/nabimarketbe/global/error/ErrorCode.java b/src/main/java/org/prgrms/nabimarketbe/global/error/ErrorCode.java index 5a402e6d..4075523a 100644 --- a/src/main/java/org/prgrms/nabimarketbe/global/error/ErrorCode.java +++ b/src/main/java/org/prgrms/nabimarketbe/global/error/ErrorCode.java @@ -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; diff --git a/src/main/java/org/prgrms/nabimarketbe/global/util/CursorPaging.java b/src/main/java/org/prgrms/nabimarketbe/global/util/CursorPaging.java new file mode 100644 index 00000000..a5552a12 --- /dev/null +++ b/src/main/java/org/prgrms/nabimarketbe/global/util/CursorPaging.java @@ -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); +} diff --git a/src/main/java/org/prgrms/nabimarketbe/global/util/OrderCondition.java b/src/main/java/org/prgrms/nabimarketbe/global/util/OrderCondition.java new file mode 100644 index 00000000..a2558aef --- /dev/null +++ b/src/main/java/org/prgrms/nabimarketbe/global/util/OrderCondition.java @@ -0,0 +1,5 @@ +package org.prgrms.nabimarketbe.global.util; + +public enum OrderCondition { + CARD_CREATED_DESC +} diff --git a/src/main/java/org/prgrms/nabimarketbe/global/util/QueryDslUtil.java b/src/main/java/org/prgrms/nabimarketbe/global/util/QueryDslUtil.java index 0d946a24..d26de98b 100644 --- a/src/main/java/org/prgrms/nabimarketbe/global/util/QueryDslUtil.java +++ b/src/main/java/org/prgrms/nabimarketbe/global/util/QueryDslUtil.java @@ -4,8 +4,35 @@ import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Path; import com.querydsl.core.types.dsl.Expressions; +import org.springframework.data.domain.Sort; + +import java.util.ArrayList; +import java.util.List; + public class QueryDslUtil { + /** + * 정렬을 기준을 반환하기 위한 메소드. + * 여러 정렬 조건들을 반환할 수 있다. + * + * @param sort Sort 객체 + * @param parent compileQuerydsl 빌드를 통해서 생성된 Q타입 클래스의 객체(Sort의 대상이 되는 Q타입 클래스 객체를 전달한다.) + * @return + */ + public static OrderSpecifier[] getOrderSpecifier( + Sort sort, + Path parent + ) { + List orders = new ArrayList<>(); + + for (Sort.Order order : sort) { // Sort에 여러 정렬 기준이 담겨 올 수 있음 + Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; + orders.add(QueryDslUtil.getSortedColumn(direction, parent, order.getProperty())); + } + + return orders.toArray(OrderSpecifier[]::new); + } + /** * Order, Path, fieldName을 전달하면 OrderSpecifier 객체를 리턴하는 Util 클래스. * Sort시 마다 사용할 수 있도록 한다.