From dde67cc600cd56a0421c96f8464769b5c8f749c3 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 19 Sep 2023 22:06:01 +0900 Subject: [PATCH 01/18] =?UTF-8?q?refactor:=20ProductsInCategoryResponse?= =?UTF-8?q?=EC=97=90=EC=84=9C=20PageDto(=ED=8E=98=EC=9D=B4=EC=A7=95?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9E=90=EC=84=B8=ED=95=9C=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4)=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20hasNext=EA=B0=92=EB=A7=8C=20=EA=B0=96=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/dto/ProductsInCategoryResponse.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/dto/ProductsInCategoryResponse.java b/backend/src/main/java/com/funeat/product/dto/ProductsInCategoryResponse.java index 4712e90fb..39c685268 100644 --- a/backend/src/main/java/com/funeat/product/dto/ProductsInCategoryResponse.java +++ b/backend/src/main/java/com/funeat/product/dto/ProductsInCategoryResponse.java @@ -1,24 +1,24 @@ package com.funeat.product.dto; -import com.funeat.common.dto.PageDto; import java.util.List; public class ProductsInCategoryResponse { - private final PageDto page; + private final boolean hasNext; private final List products; - public ProductsInCategoryResponse(final PageDto page, final List products) { - this.page = page; + public ProductsInCategoryResponse(final boolean hasNext, final List products) { + this.hasNext = hasNext; this.products = products; } - public static ProductsInCategoryResponse toResponse(final PageDto page, final List products) { - return new ProductsInCategoryResponse(page, products); + public static ProductsInCategoryResponse toResponse(final boolean hasNext, + final List products) { + return new ProductsInCategoryResponse(hasNext, products); } - public PageDto getPage() { - return page; + public boolean isHasNext() { + return hasNext; } public List getProducts() { From 1055b4cd9fe4924b91bf7f35d6a6de56e04063e9 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 19 Sep 2023 22:09:23 +0900 Subject: [PATCH 02/18] =?UTF-8?q?refactor:=20ProductRepository=EC=9D=98=20?= =?UTF-8?q?findAllByCategory=EC=99=80=20findAllByCategoryByReviewCountDesc?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=EB=A5=BC=20findAllByCategory?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=EB=A1=9C=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ReviewCount 반정규화로 인해 메소드 분리 필요성 없어짐 - ReviewCount를 위한 join 쿼리 제거 - 페이징에 대한 자세한 정보(ex. 전체 페이지 수등) 필요없기 때문에 반환값을 Page에서 Slice로 수정 --- .../persistence/ProductRepository.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java index ad4cdab0e..9d6b01a49 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java @@ -7,29 +7,17 @@ import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface ProductRepository extends JpaRepository { - @Query(value = "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, COUNT(r)) " + @Query(value = "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + "FROM Product p " - + "LEFT JOIN p.reviews r " - + "WHERE p.category = :category " - + "GROUP BY p ", - countQuery = "SELECT COUNT(p) FROM Product p WHERE p.category = :category") - Page findAllByCategory(@Param("category") final Category category, final Pageable pageable); - - @Query(value = "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, COUNT(r)) " - + "FROM Product p " - + "LEFT JOIN p.reviews r " - + "WHERE p.category = :category " - + "GROUP BY p " - + "ORDER BY COUNT(r) DESC, p.id DESC ", - countQuery = "SELECT COUNT(p) FROM Product p WHERE p.category = :category") - Page findAllByCategoryOrderByReviewCountDesc(@Param("category") final Category category, - final Pageable pageable); + + "WHERE p.category = :category ") + Slice findAllByCategory(@Param("category") final Category category, final Pageable pageable); @Query("SELECT new com.funeat.product.dto.ProductReviewCountDto(p, COUNT(r.id)) " + "FROM Product p " From 18538fb922a319fc4223bd8062d6fcf6a7f9c8e6 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 19 Sep 2023 22:10:37 +0900 Subject: [PATCH 03/18] =?UTF-8?q?refactor:=20ProductService=EC=9D=98=20rev?= =?UTF-8?q?iewCount=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=B6=84=EA=B8=B0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B6=80=EB=B6=84=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20PageDto=EB=8C=80=EC=8B=A0=20hasNext=EA=B0=92?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20response=20=EB=A7=8C=EB=93=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/application/ProductService.java b/backend/src/main/java/com/funeat/product/application/ProductService.java index 0301db183..8ddcb2f3a 100644 --- a/backend/src/main/java/com/funeat/product/application/ProductService.java +++ b/backend/src/main/java/com/funeat/product/application/ProductService.java @@ -32,11 +32,11 @@ import com.funeat.tag.domain.Tag; import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -75,20 +75,10 @@ public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId final Category category = categoryRepository.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(CATEGORY_NOT_FOUND, categoryId)); - final Page pages = getAllProductsInCategory(pageable, category); - - final PageDto pageDto = PageDto.toDto(pages); + final Slice pages = productRepository.findAllByCategory(category, pageable); final List productDtos = pages.getContent(); - return ProductsInCategoryResponse.toResponse(pageDto, productDtos); - } - - private Page getAllProductsInCategory(final Pageable pageable, final Category category) { - if (Objects.nonNull(pageable.getSort().getOrderFor(REVIEW_COUNT))) { - final PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); - return productRepository.findAllByCategoryOrderByReviewCountDesc(category, pageRequest); - } - return productRepository.findAllByCategory(category, pageable); + return ProductsInCategoryResponse.toResponse(pages.hasNext(), productDtos); } public ProductResponse findProductDetail(final Long productId) { From f97f332e1f66d4a458378fad5911afc13b40ee0d Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 19 Sep 2023 22:13:26 +0900 Subject: [PATCH 04/18] =?UTF-8?q?test:=20=EC=83=81=ED=92=88=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=9D=B8=EC=88=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B2=80=EC=A6=9D=20=EB=8C=80=EC=8B=A0=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9C=A0=EB=AC=B4?= =?UTF-8?q?=EB=A5=BC=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/acceptance/common/CommonSteps.java | 7 ++++ .../product/ProductAcceptanceTest.java | 41 +++++-------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java index 32dcb85e2..0f11394ed 100644 --- a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java @@ -74,4 +74,11 @@ public class CommonSteps { assertThat(actual).usingRecursiveComparison() .isEqualTo(expected); } + + public static void 다음_페이지_유무를_검증한다(final ExtractableResponse response, final boolean expected) { + final var actual = response.jsonPath().getBoolean("hasNext"); + + assertThat(actual) + .isEqualTo(expected); + } } diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index f6469f384..2b219a9ee 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -2,6 +2,7 @@ import static com.funeat.acceptance.auth.LoginSteps.로그인_쿠키_획득; import static com.funeat.acceptance.common.CommonSteps.STATUS_CODE를_검증한다; +import static com.funeat.acceptance.common.CommonSteps.다음_페이지_유무를_검증한다; import static com.funeat.acceptance.common.CommonSteps.사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.여러개_사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.정상_처리; @@ -124,14 +125,12 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { final var 상품2 = 단일_상품_저장(상품_삼각김밥_가격2000원_평점3점_생성(카테고리)); final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격4000원_평점4점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 가격_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품1, 상품2)); } @@ -144,14 +143,12 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { final var 상품2 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점3점_생성(카테고리)); final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점1점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 가격_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } @@ -168,14 +165,12 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { final var 상품2 = 단일_상품_저장(상품_삼각김밥_가격4000원_평점4점_생성(카테고리)); final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격2000원_평점3점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 가격_오름차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품1, 상품3, 상품2)); } @@ -188,14 +183,12 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격1000원_평점3점_생성(카테고리)); 단일_상품_저장(상품_삼각김밥_가격1000원_평점1점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(3L, 1L, true, true, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 가격_오름차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -212,14 +205,12 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점5점_생성(카테고리)); 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(3L, 1L, true, true, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(2L, 1L, 3L)); } @@ -232,14 +223,12 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(3L, 1L, true, true, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -256,14 +245,12 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리)); 단일_상품_저장(상품_삼각김밥_가격2000원_평점3점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(3L, 1L, true, true, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_오름차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(1L, 3L, 2L)); } @@ -276,14 +263,12 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(3L, 1L, true, true, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_오름차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -305,14 +290,12 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품2, 사진_명세_요청(이미지2), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품2, 사진_명세_요청(이미지3), 리뷰추가요청_재구매O_생성(점수_2점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 리뷰수_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품2, 상품1, 상품3)); } @@ -325,14 +308,12 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { final var 상품2 = 단일_상품_저장(상품_삼각김밥_가격5000원_평점3점_생성(카테고리)); final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격3000원_평점1점_생성(카테고리)); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 리뷰수_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_페이지_유무를_검증한다(응답, false); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } From 457db9709cf852f2e61db8cb6fa8b4d67bcc997c Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 19 Sep 2023 22:34:20 +0900 Subject: [PATCH 05/18] =?UTF-8?q?test:=20findAllByCategoryOrderByReviewCou?= =?UTF-8?q?ntDesc=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20findAllByCategory?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/product/domain/Product.java | 10 +++++ .../product/dto/ProductInCategoryDto.java | 17 ++++++++ .../com/funeat/fixture/ProductFixture.java | 16 ++++++++ .../persistence/ProductRepositoryTest.java | 39 +++++++------------ 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/domain/Product.java b/backend/src/main/java/com/funeat/product/domain/Product.java index eca71a02d..e87ec5912 100644 --- a/backend/src/main/java/com/funeat/product/domain/Product.java +++ b/backend/src/main/java/com/funeat/product/domain/Product.java @@ -68,6 +68,16 @@ public Product(final String name, final Long price, final String image, final St this.category = category; } + public Product(final String name, final Long price, final String image, final String content, + final Category category, final Long reviewCount) { + this.name = name; + this.price = price; + this.image = image; + this.content = content; + this.category = category; + this.reviewCount = reviewCount; + } + public void updateAverageRating(final Long rating, final Long count) { final double calculatedRating = ((count - 1) * averageRating + rating) / count; this.averageRating = Math.round(calculatedRating * 10.0) / 10.0; diff --git a/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java b/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java index 7ab4bf467..05e086330 100644 --- a/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java +++ b/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java @@ -21,6 +21,11 @@ public ProductInCategoryDto(final Long id, final String name, final Long price, this.reviewCount = reviewCount; } + public static ProductInCategoryDto toDto(final Product product) { + return new ProductInCategoryDto(product.getId(), product.getName(), product.getPrice(), product.getImage(), + product.getAverageRating(), product.getReviewCount()); + } + public static ProductInCategoryDto toDto(final Product product, final Long reviewCount) { return new ProductInCategoryDto(product.getId(), product.getName(), product.getPrice(), product.getImage(), product.getAverageRating(), reviewCount); @@ -49,4 +54,16 @@ public Double getAverageRating() { public Long getReviewCount() { return reviewCount; } + + @Override + public String toString() { + return "ProductInCategoryDto{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + ", image='" + image + '\'' + + ", averageRating=" + averageRating + + ", reviewCount=" + reviewCount + + '}'; + } } diff --git a/backend/src/test/java/com/funeat/fixture/ProductFixture.java b/backend/src/test/java/com/funeat/fixture/ProductFixture.java index 15b2fa27e..6bb7da0d9 100644 --- a/backend/src/test/java/com/funeat/fixture/ProductFixture.java +++ b/backend/src/test/java/com/funeat/fixture/ProductFixture.java @@ -130,6 +130,22 @@ public class ProductFixture { return new Product("애플망고", 3000L, "image.png", "맛있는 애플망고", 5.0, category); } + public static Product 상품_삼각김밥_가격5000원_리뷰0개_생성(final Category category) { + return new Product("삼각김밥", 5000L, "image.png", "맛있는 삼각김밥", category, 0L); + } + + public static Product 상품_삼각김밥_가격2000원_리뷰1개_생성(final Category category) { + return new Product("삼각김밥", 2000L, "image.png", "맛있는 삼각김밥", category, 1L); + } + + public static Product 상품_삼각김밥_가격1000원_리뷰3개_생성(final Category category) { + return new Product("삼각김밥", 1000L, "image.png", "맛있는 삼각김밥", category, 3L); + } + + public static Product 상품_삼각김밥_가격3000원_리뷰5개_생성(final Category category) { + return new Product("삼각김밥", 3000L, "image.png", "맛있는 삼각김밥", category, 5L); + } + public static ProductRecipe 레시피_안에_들어가는_상품_생성(final Product product, final Recipe recipe) { return new ProductRecipe(product, recipe); } diff --git a/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java b/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java index 887958516..20bebe3bc 100644 --- a/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java +++ b/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java @@ -6,28 +6,31 @@ import static com.funeat.fixture.MemberFixture.멤버_멤버3_생성; import static com.funeat.fixture.PageFixture.가격_내림차순; import static com.funeat.fixture.PageFixture.가격_오름차순; +import static com.funeat.fixture.PageFixture.리뷰수_내림차순; import static com.funeat.fixture.PageFixture.페이지요청_기본_생성; import static com.funeat.fixture.PageFixture.페이지요청_생성; import static com.funeat.fixture.PageFixture.평균_평점_내림차순; import static com.funeat.fixture.PageFixture.평균_평점_오름차순; import static com.funeat.fixture.ProductFixture.상품_망고빙수_가격5000원_평점4점_생성; +import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_리뷰3개_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점2점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점3점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점4점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점5점_생성; +import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_리뷰1개_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점4점_생성; +import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격3000원_리뷰5개_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격3000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격3000원_평점5점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격4000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격4000원_평점2점_생성; +import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격5000원_리뷰0개_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격5000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_애플망고_가격3000원_평점5점_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test1_평점1점_재구매X_생성; -import static com.funeat.fixture.ReviewFixture.리뷰_이미지test2_평점2점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매O_생성; -import static com.funeat.fixture.ReviewFixture.리뷰_이미지test4_평점4점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test4_평점4점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test5_평점5점_재구매O_생성; import static org.assertj.core.api.Assertions.assertThat; @@ -43,7 +46,7 @@ class ProductRepositoryTest extends RepositoryTest { @Nested - class findByAllCategory_성공_테스트 { + class findAllByCategory_성공_테스트 { @Test void 카테고리별_상품을_평점이_높은_순으로_정렬한다() { @@ -156,10 +159,6 @@ class findByAllCategory_성공_테스트 { assertThat(actual).usingRecursiveComparison() .isEqualTo(expected); } - } - - @Nested - class findAllByCategoryOrderByReviewCountDesc_성공_테스트 { @Test void 카테고리별_상품을_리뷰수가_많은_순으로_정렬한다() { @@ -167,10 +166,10 @@ class findAllByCategoryOrderByReviewCountDesc_성공_테스트 { final var category = 카테고리_간편식사_생성(); 단일_카테고리_저장(category); - final var product1 = 상품_삼각김밥_가격1000원_평점1점_생성(category); - final var product2 = 상품_삼각김밥_가격2000원_평점1점_생성(category); - final var product3 = 상품_삼각김밥_가격3000원_평점1점_생성(category); - final var product4 = 상품_삼각김밥_가격4000원_평점1점_생성(category); + final var product1 = 상품_삼각김밥_가격5000원_리뷰0개_생성(category); + final var product2 = 상품_삼각김밥_가격1000원_리뷰3개_생성(category); + final var product3 = 상품_삼각김밥_가격2000원_리뷰1개_생성(category); + final var product4 = 상품_삼각김밥_가격3000원_리뷰5개_생성(category); 복수_상품_저장(product1, product2, product3, product4); final var member1 = 멤버_멤버1_생성(); @@ -178,23 +177,15 @@ class findAllByCategoryOrderByReviewCountDesc_성공_테스트 { final var member3 = 멤버_멤버3_생성(); 복수_멤버_저장(member1, member2, member3); - final var review1_1 = 리뷰_이미지test1_평점1점_재구매X_생성(member1, product1, 0L); - final var review1_2 = 리뷰_이미지test3_평점3점_재구매O_생성(member2, product1, 0L); - final var review2_1 = 리뷰_이미지test4_평점4점_재구매O_생성(member3, product2, 0L); - final var review2_2 = 리뷰_이미지test2_평점2점_재구매X_생성(member1, product2, 0L); - final var review2_3 = 리뷰_이미지test3_평점3점_재구매O_생성(member2, product2, 0L); - final var review3_1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product3, 0L); - 복수_리뷰_저장(review1_1, review1_2, review2_1, review2_2, review2_3, review3_1); - - final var page = 페이지요청_기본_생성(0, 3); + final var page = 페이지요청_생성(0, 3, 리뷰수_내림차순); - final var productInCategoryDto1 = ProductInCategoryDto.toDto(product2, 3L); - final var productInCategoryDto2 = ProductInCategoryDto.toDto(product1, 2L); - final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3, 1L); + final var productInCategoryDto1 = ProductInCategoryDto.toDto(product4); + final var productInCategoryDto2 = ProductInCategoryDto.toDto(product2); + final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3); final var expected = List.of(productInCategoryDto1, productInCategoryDto2, productInCategoryDto3); // when - final var actual = productRepository.findAllByCategoryOrderByReviewCountDesc(category, page) + final var actual = productRepository.findAllByCategory(category, page) .getContent(); // then From 445a26cb6a809e838c2f291554c339ad7e87a17a Mon Sep 17 00:00:00 2001 From: hanueleee Date: Wed, 20 Sep 2023 22:50:34 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=A1=B0=ED=9A=8C=20api=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (기존) sort=price,asc&page=1 (수정) sort=price,asc&id=5 --- .../funeat/product/presentation/ProductApiController.java | 5 +++-- .../com/funeat/product/presentation/ProductController.java | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java b/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java index f71a1a706..164bd3d1c 100644 --- a/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java +++ b/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java @@ -29,8 +29,9 @@ public ProductApiController(final ProductService productService) { @GetMapping("/categories/{categoryId}/products") public ResponseEntity getAllProductsInCategory(@PathVariable final Long categoryId, - @PageableDefault final Pageable pageable) { - final ProductsInCategoryResponse response = productService.getAllProductsInCategory(categoryId, pageable); + @RequestParam(name = "id") Long lastId, + @RequestParam(name = "sort") String sort) { + final ProductsInCategoryResponse response = productService.getAllProductsInCategory(categoryId, lastId, sort); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/com/funeat/product/presentation/ProductController.java b/backend/src/main/java/com/funeat/product/presentation/ProductController.java index d7f9e653f..8346c1339 100644 --- a/backend/src/main/java/com/funeat/product/presentation/ProductController.java +++ b/backend/src/main/java/com/funeat/product/presentation/ProductController.java @@ -26,7 +26,9 @@ public interface ProductController { ) @GetMapping ResponseEntity getAllProductsInCategory( - @PathVariable(name = "category_id") final Long categoryId, @PageableDefault final Pageable pageable + @PathVariable final Long categoryId, + @RequestParam(name = "id") Long lastId, + @RequestParam(name = "sort") String sort ); @Operation(summary = "해당 상품 상세 조회", description = "해당 상품 상세정보를 조회한다.") From 73fd56c76b4d61af5d1e3db9ec1a2aa9bf4303b2 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Wed, 20 Sep 2023 22:55:19 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=EC=A0=95=EB=A0=AC=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=B3=84=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProductInCategoryRepository.java | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java new file mode 100644 index 000000000..427e9bc74 --- /dev/null +++ b/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java @@ -0,0 +1,177 @@ +package com.funeat.product.persistence; + +import com.funeat.product.domain.Category; +import com.funeat.product.dto.ProductInCategoryDto; +import java.util.List; +import javax.persistence.EntityManager; +import org.springframework.stereotype.Repository; + +@Repository +public class ProductInCategoryRepository { + + private final EntityManager entityManager; + + public ProductInCategoryRepository(final EntityManager entityManager) { + this.entityManager = entityManager; + } + + public List findProductByPriceAscFirst(Category category) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "WHERE p.category = :category " + + "ORDER BY p.price asc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByPriceAsc(Category category, Long lastProductId) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "JOIN Product p2 ON p2.id =:lastProductId " + + "WHERE p.category = :category AND " + + "( " + + " (p.price = p2.price AND p.id<:lastProductId)" + + "or p.price >p2.price " + + ") " + + "ORDER BY p.price asc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("lastProductId", lastProductId) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByPriceDescFirst(Category category) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "WHERE p.category = :category " + + "ORDER BY p.price desc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByPriceDesc(Category category, Long lastProductId) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "JOIN Product p2 ON p2.id =:lastProductId " + + "WHERE p.category = :category AND " + + "( " + + " (p.price = p2.price AND p.id<:lastProductId)" + + "or p.price < p2.price " + + ") " + + "ORDER BY p.price desc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("lastProductId", lastProductId) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByAverageRatingAscFirst(Category category) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "WHERE p.category = :category " + + "ORDER BY p.averageRating asc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByAverageRatingAsc(Category category, Long lastProductId) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "JOIN Product p2 ON p2.id =:lastProductId " + + "WHERE p.category = :category AND " + + "( " + + " (p.averageRating = p2.averageRating AND p.id<:lastProductId)" + + "or p.averageRating > p2.averageRating " + + ") " + + "ORDER BY p.averageRating asc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("lastProductId", lastProductId) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByAverageRatingDescFirst(Category category) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "WHERE p.category = :category " + + "ORDER BY p.averageRating desc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByAverageRatingDesc(Category category, Long lastProductId) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "JOIN Product p2 ON p2.id =:lastProductId " + + "WHERE p.category = :category AND " + + "( " + + " (p.averageRating = p2.averageRating AND p.id<:lastProductId)" + + "or p.averageRating < p2.averageRating " + + ") " + + "ORDER BY p.averageRating desc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("lastProductId", lastProductId) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByReviewCountDescFirst(Category category) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "WHERE p.category = :category " + + "ORDER BY p.reviewCount desc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } + + public List findProductByReviewCountDesc(Category category, Long lastProductId) { + String jpqlQuery = + "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + + "FROM Product p " + + "JOIN Product p2 ON p2.id =:lastProductId " + + "WHERE p.category = :category AND " + + "( " + + " (p.reviewCount = p2.reviewCount AND p.id<:lastProductId)" + + "or p.reviewCount < p2.reviewCount " + + ") " + + "ORDER BY p.averageRating desc, p.id DESC "; + + return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) + .setParameter("lastProductId", lastProductId) + .setParameter("category", category) + .setMaxResults(11) + .getResultList(); + } +} From 37a7bbe5d8e484875171c53e53caa3439642c465 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Wed, 20 Sep 2023 22:56:22 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20ProductService=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=A0=95=EB=A0=AC=20=EC=A1=B0=EA=B1=B4=EB=B3=84=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/application/ProductService.java b/backend/src/main/java/com/funeat/product/application/ProductService.java index 8ddcb2f3a..d5b2e5d5b 100644 --- a/backend/src/main/java/com/funeat/product/application/ProductService.java +++ b/backend/src/main/java/com/funeat/product/application/ProductService.java @@ -19,6 +19,7 @@ import com.funeat.product.exception.CategoryException.CategoryNotFoundException; import com.funeat.product.exception.ProductException.ProductNotFoundException; import com.funeat.product.persistence.CategoryRepository; +import com.funeat.product.persistence.ProductInCategoryRepository; import com.funeat.product.persistence.ProductRecipeRepository; import com.funeat.product.persistence.ProductRepository; import com.funeat.recipe.domain.Recipe; @@ -36,7 +37,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,7 +46,6 @@ public class ProductService { private static final int THREE = 3; private static final int TOP = 0; - public static final String REVIEW_COUNT = "reviewCount"; private static final int RANKING_SIZE = 3; private final CategoryRepository categoryRepository; @@ -56,11 +55,14 @@ public class ProductService { private final ProductRecipeRepository productRecipeRepository; private final RecipeImageRepository recipeImageRepository; private final RecipeRepository recipeRepository; + private final ProductInCategoryRepository productInCategoryRepository; public ProductService(final CategoryRepository categoryRepository, final ProductRepository productRepository, final ReviewTagRepository reviewTagRepository, final ReviewRepository reviewRepository, final ProductRecipeRepository productRecipeRepository, - final RecipeImageRepository recipeImageRepository, final RecipeRepository recipeRepository) { + final RecipeImageRepository recipeImageRepository, + final RecipeRepository recipeRepository, + final ProductInCategoryRepository productInCategoryRepository) { this.categoryRepository = categoryRepository; this.productRepository = productRepository; this.reviewTagRepository = reviewTagRepository; @@ -68,17 +70,55 @@ public ProductService(final CategoryRepository categoryRepository, final Product this.productRecipeRepository = productRecipeRepository; this.recipeImageRepository = recipeImageRepository; this.recipeRepository = recipeRepository; + this.productInCategoryRepository = productInCategoryRepository; } - public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId, - final Pageable pageable) { + public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId, final Long lastProductId, + final String sort) { final Category category = categoryRepository.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(CATEGORY_NOT_FOUND, categoryId)); - final Slice pages = productRepository.findAllByCategory(category, pageable); - final List productDtos = pages.getContent(); + List productDtos; + if (lastProductId == 0) { + productDtos = findFirstPage(sort, category); + } else { + productDtos = findPage(sort, category, lastProductId); + } - return ProductsInCategoryResponse.toResponse(pages.hasNext(), productDtos); + boolean hasNext = productDtos.size() == 11; + return ProductsInCategoryResponse.toResponse(hasNext, productDtos); + } + + private List findFirstPage(final String sort, final Category category) { + if ("price,asc".equals(sort)) { + return productInCategoryRepository.findProductByPriceAscFirst(category); + } else if ("price,desc".equals(sort)) { + return productInCategoryRepository.findProductByPriceDescFirst(category); + } else if ("averageRating,asc".equals(sort)) { + return productInCategoryRepository.findProductByAverageRatingAscFirst(category); + } else if ("averageRating,desc".equals(sort)) { + return productInCategoryRepository.findProductByAverageRatingDescFirst(category); + } else if ("reviewCount,desc".equals(sort)) { + return productInCategoryRepository.findProductByReviewCountDescFirst(category); + } else { + throw new IllegalArgumentException("정렬 조건을 확인해주세요."); + } + } + + private List findPage(final String sort, final Category category, final Long lastProductId) { + if ("price,asc".equals(sort)) { + return productInCategoryRepository.findProductByPriceAsc(category, lastProductId); + } else if ("price,desc".equals(sort)) { + return productInCategoryRepository.findProductByPriceDesc(category, lastProductId); + } else if ("averageRating,asc".equals(sort)) { + return productInCategoryRepository.findProductByAverageRatingAsc(category, lastProductId); + } else if ("averageRating,desc".equals(sort)) { + return productInCategoryRepository.findProductByAverageRatingDesc(category, lastProductId); + } else if ("reviewCount,desc".equals(sort)) { + return productInCategoryRepository.findProductByReviewCountDesc(category, lastProductId); + } else { + throw new IllegalArgumentException("정렬 조건을 확인해주세요."); + } } public ProductResponse findProductDetail(final Long productId) { From 13640f1a97e0de592ad17c2609a4cfb7f9ee760d Mon Sep 17 00:00:00 2001 From: hanueleee Date: Wed, 20 Sep 2023 22:56:51 +0900 Subject: [PATCH 09/18] =?UTF-8?q?test:=20=EC=83=81=ED=92=88=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=A1=B0=ED=9A=8Capi=20=EB=B3=80=EA=B2=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=9D=B8=EC=88=98=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/ProductAcceptanceTest.java | 22 +++++++++---------- .../acceptance/product/ProductSteps.java | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index 2b219a9ee..8e703ecab 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -126,7 +126,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격4000원_평점4점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 가격_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 가격_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -144,7 +144,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점1점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 가격_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 가격_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -166,7 +166,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격2000원_평점3점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 가격_오름차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 가격_오름차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -184,7 +184,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격1000원_평점1점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 가격_오름차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 가격_오름차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -206,7 +206,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -224,7 +224,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -246,7 +246,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점3점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_오름차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_오름차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -264,7 +264,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { 단일_상품_저장(상품_삼각김밥_가격2000원_평점1점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_오름차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(1L, 평균_평점_오름차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -291,7 +291,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품2, 사진_명세_요청(이미지3), 리뷰추가요청_재구매O_생성(점수_2점, List.of(태그))); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 리뷰수_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 리뷰수_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -309,7 +309,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { final var 상품3 = 단일_상품_저장(상품_삼각김밥_가격3000원_평점1점_생성(카테고리)); // when - final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 리뷰수_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(카테고리_아이디, 리뷰수_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 정상_처리); @@ -325,7 +325,7 @@ class getAllProductsInCategory_실패_테스트 { @Test void 상품을_정렬할때_카테고리가_존재하지_않으면_예외가_발생한다() { // given && when - final var 응답 = 카테고리별_상품_목록_조회_요청(존재하지_않는_카테고리, 가격_내림차순, FIRST_PAGE); + final var 응답 = 카테고리별_상품_목록_조회_요청(존재하지_않는_카테고리, 가격_내림차순, 0L); // then STATUS_CODE를_검증한다(응답, 찾을수_없음); diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java b/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java index 8092bc187..7e78a03dc 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java @@ -10,10 +10,10 @@ public class ProductSteps { public static ExtractableResponse 카테고리별_상품_목록_조회_요청(final Long categoryId, final String sort, - final Long page) { + final Long lastId) { return given() .queryParam("sort", sort) - .queryParam("page", page) + .queryParam("id", lastId) .when() .get("/api/categories/{category_id}/products", categoryId) .then() From e589031bb5b1025c983ef460f939ad7866b1fdc1 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Thu, 21 Sep 2023 00:03:11 +0900 Subject: [PATCH 10/18] =?UTF-8?q?chore:=20toString=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/product/dto/ProductInCategoryDto.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java b/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java index 05e086330..e4c73b606 100644 --- a/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java +++ b/backend/src/main/java/com/funeat/product/dto/ProductInCategoryDto.java @@ -54,16 +54,4 @@ public Double getAverageRating() { public Long getReviewCount() { return reviewCount; } - - @Override - public String toString() { - return "ProductInCategoryDto{" + - "id=" + id + - ", name='" + name + '\'' + - ", price=" + price + - ", image='" + image + '\'' + - ", averageRating=" + averageRating + - ", reviewCount=" + reviewCount + - '}'; - } } From 8c423d19636258c9e203ab61d8d8f81977b917c0 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Thu, 21 Sep 2023 15:48:29 +0900 Subject: [PATCH 11/18] =?UTF-8?q?fix:=20findProductByReviewCountDesc=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/product/persistence/ProductInCategoryRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java index 427e9bc74..7ba24d9fe 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java @@ -166,7 +166,7 @@ public List findProductByReviewCountDesc(Category category + " (p.reviewCount = p2.reviewCount AND p.id<:lastProductId)" + "or p.reviewCount < p2.reviewCount " + ") " - + "ORDER BY p.averageRating desc, p.id DESC "; + + "ORDER BY p.reviewCount desc, p.id DESC "; return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) .setParameter("lastProductId", lastProductId) From 501b254b99472be260ae930aa6ebe289a008882a Mon Sep 17 00:00:00 2001 From: hanueleee Date: Sun, 15 Oct 2023 22:17:56 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20specification=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EB=8F=99=EC=A0=81=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 73 ++++---- .../product/exception/ProductErrorCode.java | 1 + .../product/exception/ProductException.java | 6 + .../ProductInCategoryRepository.java | 177 ------------------ .../persistence/ProductRepository.java | 3 +- .../persistence/ProductSpecifications.java | 85 +++++++++ 6 files changed, 128 insertions(+), 217 deletions(-) delete mode 100644 backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java create mode 100644 backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java diff --git a/backend/src/main/java/com/funeat/product/application/ProductService.java b/backend/src/main/java/com/funeat/product/application/ProductService.java index d5b2e5d5b..1ae654ed9 100644 --- a/backend/src/main/java/com/funeat/product/application/ProductService.java +++ b/backend/src/main/java/com/funeat/product/application/ProductService.java @@ -19,9 +19,9 @@ import com.funeat.product.exception.CategoryException.CategoryNotFoundException; import com.funeat.product.exception.ProductException.ProductNotFoundException; import com.funeat.product.persistence.CategoryRepository; -import com.funeat.product.persistence.ProductInCategoryRepository; import com.funeat.product.persistence.ProductRecipeRepository; import com.funeat.product.persistence.ProductRepository; +import com.funeat.product.persistence.ProductSpecifications; import com.funeat.recipe.domain.Recipe; import com.funeat.recipe.domain.RecipeImage; import com.funeat.recipe.dto.RecipeDto; @@ -37,6 +37,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -47,6 +48,8 @@ public class ProductService { private static final int THREE = 3; private static final int TOP = 0; private static final int RANKING_SIZE = 3; + private static final int DEFAULT_PAGE_SIZE = 10; + private static final int DEFAULT_CURSOR_PAGINATION_SIZE = 11; private final CategoryRepository categoryRepository; private final ProductRepository productRepository; @@ -55,14 +58,12 @@ public class ProductService { private final ProductRecipeRepository productRecipeRepository; private final RecipeImageRepository recipeImageRepository; private final RecipeRepository recipeRepository; - private final ProductInCategoryRepository productInCategoryRepository; public ProductService(final CategoryRepository categoryRepository, final ProductRepository productRepository, final ReviewTagRepository reviewTagRepository, final ReviewRepository reviewRepository, final ProductRecipeRepository productRecipeRepository, final RecipeImageRepository recipeImageRepository, - final RecipeRepository recipeRepository, - final ProductInCategoryRepository productInCategoryRepository) { + final RecipeRepository recipeRepository) { this.categoryRepository = categoryRepository; this.productRepository = productRepository; this.reviewTagRepository = reviewTagRepository; @@ -70,55 +71,49 @@ public ProductService(final CategoryRepository categoryRepository, final Product this.productRecipeRepository = productRecipeRepository; this.recipeImageRepository = recipeImageRepository; this.recipeRepository = recipeRepository; - this.productInCategoryRepository = productInCategoryRepository; } public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId, final Long lastProductId, final String sort) { final Category category = categoryRepository.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(CATEGORY_NOT_FOUND, categoryId)); + final Product lastProduct = productRepository.findById(lastProductId).orElse(null); - List productDtos; - if (lastProductId == 0) { - productDtos = findFirstPage(sort, category); - } else { - productDtos = findPage(sort, category, lastProductId); - } + final String[] split = sort.split(","); + final String sortBy = split[0]; + final String sortOrder = split[1]; + + final Specification specification = ProductSpecifications.searchBy(category, lastProduct, sortBy, + sortOrder); + + final PageRequest pageable = PageRequest.of(0, DEFAULT_CURSOR_PAGINATION_SIZE); + final Page paginationResult = productRepository.findAll(specification, pageable); + + final List productDtos = getProductInCategoryDtos(paginationResult); + final Boolean hasNext = hasNextPage(paginationResult); - boolean hasNext = productDtos.size() == 11; return ProductsInCategoryResponse.toResponse(hasNext, productDtos); } - private List findFirstPage(final String sort, final Category category) { - if ("price,asc".equals(sort)) { - return productInCategoryRepository.findProductByPriceAscFirst(category); - } else if ("price,desc".equals(sort)) { - return productInCategoryRepository.findProductByPriceDescFirst(category); - } else if ("averageRating,asc".equals(sort)) { - return productInCategoryRepository.findProductByAverageRatingAscFirst(category); - } else if ("averageRating,desc".equals(sort)) { - return productInCategoryRepository.findProductByAverageRatingDescFirst(category); - } else if ("reviewCount,desc".equals(sort)) { - return productInCategoryRepository.findProductByReviewCountDescFirst(category); - } else { - throw new IllegalArgumentException("정렬 조건을 확인해주세요."); - } + private List getProductInCategoryDtos(final Page paginationResult) { + final List findProducts = paginationResult.getContent(); + final int resultSize = getResultSize(findProducts); + final List products = findProducts.subList(0, resultSize); + + return products.stream() + .map(ProductInCategoryDto::toDto) + .collect(Collectors.toList()); } - private List findPage(final String sort, final Category category, final Long lastProductId) { - if ("price,asc".equals(sort)) { - return productInCategoryRepository.findProductByPriceAsc(category, lastProductId); - } else if ("price,desc".equals(sort)) { - return productInCategoryRepository.findProductByPriceDesc(category, lastProductId); - } else if ("averageRating,asc".equals(sort)) { - return productInCategoryRepository.findProductByAverageRatingAsc(category, lastProductId); - } else if ("averageRating,desc".equals(sort)) { - return productInCategoryRepository.findProductByAverageRatingDesc(category, lastProductId); - } else if ("reviewCount,desc".equals(sort)) { - return productInCategoryRepository.findProductByReviewCountDesc(category, lastProductId); - } else { - throw new IllegalArgumentException("정렬 조건을 확인해주세요."); + private int getResultSize(final List findProducts) { + if (findProducts.size() < DEFAULT_CURSOR_PAGINATION_SIZE) { + return findProducts.size(); } + return DEFAULT_PAGE_SIZE; + } + + private Boolean hasNextPage(final Page paginationResult) { + return paginationResult.getContent().size() > DEFAULT_PAGE_SIZE; } public ProductResponse findProductDetail(final Long productId) { diff --git a/backend/src/main/java/com/funeat/product/exception/ProductErrorCode.java b/backend/src/main/java/com/funeat/product/exception/ProductErrorCode.java index 933f91098..e3b4d3ccc 100644 --- a/backend/src/main/java/com/funeat/product/exception/ProductErrorCode.java +++ b/backend/src/main/java/com/funeat/product/exception/ProductErrorCode.java @@ -5,6 +5,7 @@ public enum ProductErrorCode { PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 상품입니다. 상품 id를 확인하세요.", "1001"), + NOT_SUPPORTED_PRODUCT_SORTING_CONDITION(HttpStatus.BAD_REQUEST, "정렬 조건이 올바르지 않습니다. 정렬 조건을 확인하세요", "1002"); ; private final HttpStatus status; diff --git a/backend/src/main/java/com/funeat/product/exception/ProductException.java b/backend/src/main/java/com/funeat/product/exception/ProductException.java index c9b1f1720..bdbb4782b 100644 --- a/backend/src/main/java/com/funeat/product/exception/ProductException.java +++ b/backend/src/main/java/com/funeat/product/exception/ProductException.java @@ -15,4 +15,10 @@ public ProductNotFoundException(final ProductErrorCode errorCode, final Long pro super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), productId)); } } + + public static class NotSupportedProductSortingConditionException extends ProductException { + public NotSupportedProductSortingConditionException(final ProductErrorCode errorCode, final String sortBy) { + super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), sortBy)); + } + } } diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java deleted file mode 100644 index 7ba24d9fe..000000000 --- a/backend/src/main/java/com/funeat/product/persistence/ProductInCategoryRepository.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.funeat.product.persistence; - -import com.funeat.product.domain.Category; -import com.funeat.product.dto.ProductInCategoryDto; -import java.util.List; -import javax.persistence.EntityManager; -import org.springframework.stereotype.Repository; - -@Repository -public class ProductInCategoryRepository { - - private final EntityManager entityManager; - - public ProductInCategoryRepository(final EntityManager entityManager) { - this.entityManager = entityManager; - } - - public List findProductByPriceAscFirst(Category category) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "WHERE p.category = :category " - + "ORDER BY p.price asc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByPriceAsc(Category category, Long lastProductId) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "JOIN Product p2 ON p2.id =:lastProductId " - + "WHERE p.category = :category AND " - + "( " - + " (p.price = p2.price AND p.id<:lastProductId)" - + "or p.price >p2.price " - + ") " - + "ORDER BY p.price asc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("lastProductId", lastProductId) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByPriceDescFirst(Category category) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "WHERE p.category = :category " - + "ORDER BY p.price desc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByPriceDesc(Category category, Long lastProductId) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "JOIN Product p2 ON p2.id =:lastProductId " - + "WHERE p.category = :category AND " - + "( " - + " (p.price = p2.price AND p.id<:lastProductId)" - + "or p.price < p2.price " - + ") " - + "ORDER BY p.price desc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("lastProductId", lastProductId) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByAverageRatingAscFirst(Category category) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "WHERE p.category = :category " - + "ORDER BY p.averageRating asc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByAverageRatingAsc(Category category, Long lastProductId) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "JOIN Product p2 ON p2.id =:lastProductId " - + "WHERE p.category = :category AND " - + "( " - + " (p.averageRating = p2.averageRating AND p.id<:lastProductId)" - + "or p.averageRating > p2.averageRating " - + ") " - + "ORDER BY p.averageRating asc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("lastProductId", lastProductId) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByAverageRatingDescFirst(Category category) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "WHERE p.category = :category " - + "ORDER BY p.averageRating desc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByAverageRatingDesc(Category category, Long lastProductId) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "JOIN Product p2 ON p2.id =:lastProductId " - + "WHERE p.category = :category AND " - + "( " - + " (p.averageRating = p2.averageRating AND p.id<:lastProductId)" - + "or p.averageRating < p2.averageRating " - + ") " - + "ORDER BY p.averageRating desc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("lastProductId", lastProductId) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByReviewCountDescFirst(Category category) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "WHERE p.category = :category " - + "ORDER BY p.reviewCount desc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } - - public List findProductByReviewCountDesc(Category category, Long lastProductId) { - String jpqlQuery = - "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "JOIN Product p2 ON p2.id =:lastProductId " - + "WHERE p.category = :category AND " - + "( " - + " (p.reviewCount = p2.reviewCount AND p.id<:lastProductId)" - + "or p.reviewCount < p2.reviewCount " - + ") " - + "ORDER BY p.reviewCount desc, p.id DESC "; - - return entityManager.createQuery(jpqlQuery, ProductInCategoryDto.class) - .setParameter("lastProductId", lastProductId) - .setParameter("category", category) - .setMaxResults(11) - .getResultList(); - } -} diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java index 9d6b01a49..1df61371f 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java @@ -9,10 +9,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface ProductRepository extends JpaRepository { +public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { @Query(value = "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + "FROM Product p " diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java b/backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java new file mode 100644 index 000000000..390a6ff3e --- /dev/null +++ b/backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java @@ -0,0 +1,85 @@ +package com.funeat.product.persistence; + +import static com.funeat.product.exception.ProductErrorCode.NOT_SUPPORTED_PRODUCT_SORTING_CONDITION; + +import com.funeat.product.domain.Category; +import com.funeat.product.domain.Product; +import com.funeat.product.exception.ProductException.NotSupportedProductSortingConditionException; +import java.util.Objects; +import javax.persistence.criteria.Path; +import org.springframework.data.jpa.domain.Specification; + +public class ProductSpecifications { + + public static Specification searchBy(final Category category, final Product lastProduct, + final String sortBy, final String sortOrder) { + return (root, query, builder) -> { + + if ("desc".equals(sortOrder)) { + query.orderBy(builder.desc(root.get(sortBy)), builder.desc(root.get("id"))); + } else { + query.orderBy(builder.asc(root.get(sortBy)), builder.desc(root.get("id"))); + } + + return Specification + .where(sameCategory(category)) + .and(findNext(lastProduct, sortBy, sortOrder)) + .toPredicate(root, query, builder); + }; + } + + private static Specification sameCategory(final Category category) { + return (root, query, builder) -> { + final Path categoryPath = root.get("category"); + + return builder.equal(categoryPath, category); + }; + } + + private static Specification findNext(final Product lastProduct, final String sortBy, + final String sortOrder) { + return (root, query, builder) -> { + if (Objects.isNull(lastProduct)) { + return null; + } + + final Comparable comparisonValue = (Comparable) getComparisonValue(lastProduct, sortBy); + + return builder.or( + sameValue(sortBy, lastProduct.getId(), comparisonValue).toPredicate(root, query, builder), + notSameValue(sortBy, sortOrder, comparisonValue).toPredicate(root, query, builder) + ); + }; + } + + private static Object getComparisonValue(final Product lastProduct, final String sortBy) { + if ("price".equals(sortBy)) { + return lastProduct.getPrice(); + } + if ("averageRating".equals(sortBy)) { + return lastProduct.getAverageRating(); + } + if ("reviewCount".equals(sortBy)) { + return lastProduct.getReviewCount(); + } + throw new NotSupportedProductSortingConditionException(NOT_SUPPORTED_PRODUCT_SORTING_CONDITION, sortBy); + } + + private static Specification sameValue(final String sortBy, final Long lastProductId, + final Comparable comparisonValue) { + return (root, query, builder) -> builder.and( + builder.equal(root.get(sortBy), comparisonValue), + builder.lessThan(root.get("id"), lastProductId)); + } + + private static Specification notSameValue(final String sortBy, final String sortOrder, + final Comparable comparisonValue) { + return (root, query, builder) -> { + if (sortOrder.equals("desc")) { + return builder.lessThan(root.get(sortBy), comparisonValue); + } else { + return builder.greaterThan(root.get(sortBy), comparisonValue); + } + }; + } +} From 04df1324e03cc3a189e7e56e29467b0028fc3adc Mon Sep 17 00:00:00 2001 From: hanueleee Date: Mon, 16 Oct 2023 01:26:22 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20count=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=95=88=EB=82=98=EA=B0=80=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/funeat/FuneatApplication.java | 3 ++ .../common/repository/BaseRepository.java | 17 +++++++ .../common/repository/BaseRepositoryImpl.java | 51 +++++++++++++++++++ .../product/application/ProductService.java | 15 +++--- .../persistence/ProductRepository.java | 5 +- 5 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/java/com/funeat/common/repository/BaseRepository.java create mode 100644 backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java diff --git a/backend/src/main/java/com/funeat/FuneatApplication.java b/backend/src/main/java/com/funeat/FuneatApplication.java index 9fae64b95..53bd185c0 100644 --- a/backend/src/main/java/com/funeat/FuneatApplication.java +++ b/backend/src/main/java/com/funeat/FuneatApplication.java @@ -1,9 +1,12 @@ package com.funeat; +import com.funeat.common.repository.BaseRepositoryImpl; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication +@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class) public class FuneatApplication { public static void main(String[] args) { diff --git a/backend/src/main/java/com/funeat/common/repository/BaseRepository.java b/backend/src/main/java/com/funeat/common/repository/BaseRepository.java new file mode 100644 index 000000000..5211e6a4b --- /dev/null +++ b/backend/src/main/java/com/funeat/common/repository/BaseRepository.java @@ -0,0 +1,17 @@ +package com.funeat.common.repository; + +import java.io.Serializable; +import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface BaseRepository extends JpaRepository { + + Page findAllForPagination(final Specification spec, final Pageable pageable, final Long totalElements); + + List findAllWithSpecification(final Specification spec, final int size); +} diff --git a/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java b/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java new file mode 100644 index 000000000..26bbbab90 --- /dev/null +++ b/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java @@ -0,0 +1,51 @@ +package com.funeat.common.repository; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; + +public class BaseRepositoryImpl extends SimpleJpaRepository implements + BaseRepository { + + + public BaseRepositoryImpl(final JpaEntityInformation entityInformation, final EntityManager entityManager) { + super(entityInformation, entityManager); + } + + @Override + public Page findAllForPagination(final Specification spec, final Pageable pageable, + final Long totalElements) { + final TypedQuery query = getQuery(spec, pageable.getSort()); + + final int pageSize = pageable.getPageSize(); + + if (totalElements == null) { + return findAll(spec, pageable); + } + + if (pageSize < 1) { + throw new IllegalArgumentException("페이지는 1미만이 될 수 없습니다."); + } + + query.setMaxResults(pageable.getPageSize()); + + return new PageImpl<>(query.getResultList(), PageRequest.of(0, pageSize), totalElements); + } + + @Override + public List findAllWithSpecification(final Specification spec, final int size) { + final TypedQuery query = getQuery(spec, Sort.unsorted()); + query.setMaxResults(size); + + return query.getResultList(); + } +} diff --git a/backend/src/main/java/com/funeat/product/application/ProductService.java b/backend/src/main/java/com/funeat/product/application/ProductService.java index 1ae654ed9..051a49886 100644 --- a/backend/src/main/java/com/funeat/product/application/ProductService.java +++ b/backend/src/main/java/com/funeat/product/application/ProductService.java @@ -85,18 +85,15 @@ public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId final Specification specification = ProductSpecifications.searchBy(category, lastProduct, sortBy, sortOrder); + final List findResults = productRepository.findAllWithSpecification(specification, DEFAULT_CURSOR_PAGINATION_SIZE); - final PageRequest pageable = PageRequest.of(0, DEFAULT_CURSOR_PAGINATION_SIZE); - final Page paginationResult = productRepository.findAll(specification, pageable); - - final List productDtos = getProductInCategoryDtos(paginationResult); - final Boolean hasNext = hasNextPage(paginationResult); + final List productDtos = getProductInCategoryDtos(findResults); + final Boolean hasNext = hasNextPage(findResults); return ProductsInCategoryResponse.toResponse(hasNext, productDtos); } - private List getProductInCategoryDtos(final Page paginationResult) { - final List findProducts = paginationResult.getContent(); + private List getProductInCategoryDtos(final List findProducts) { final int resultSize = getResultSize(findProducts); final List products = findProducts.subList(0, resultSize); @@ -112,8 +109,8 @@ private int getResultSize(final List findProducts) { return DEFAULT_PAGE_SIZE; } - private Boolean hasNextPage(final Page paginationResult) { - return paginationResult.getContent().size() > DEFAULT_PAGE_SIZE; + private Boolean hasNextPage(final List findProducts) { + return findProducts.size() > DEFAULT_PAGE_SIZE; } public ProductResponse findProductDetail(final Long productId) { diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java index 1df61371f..07539366f 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java @@ -1,5 +1,6 @@ package com.funeat.product.persistence; +import com.funeat.common.repository.BaseRepository; import com.funeat.product.domain.Category; import com.funeat.product.domain.Product; import com.funeat.product.dto.ProductInCategoryDto; @@ -8,12 +9,10 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { +public interface ProductRepository extends BaseRepository { @Query(value = "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " + "FROM Product p " From e9e658a12f5cc422751926647df4ffe2070278c8 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Mon, 16 Oct 2023 01:47:41 +0900 Subject: [PATCH 14/18] =?UTF-8?q?refactor:=20=EC=A0=95=EB=A0=AC=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4(sortBy,=20sortOrder)=EC=9A=A9=20dto=EC=9D=B8=20Produc?= =?UTF-8?q?tSortCondition=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 16 +++++------- .../product/dto/ProductSortCondition.java | 25 +++++++++++++++++++ ...cations.java => ProductSpecification.java} | 24 ++++++++++-------- .../presentation/ProductApiController.java | 4 ++- 4 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 backend/src/main/java/com/funeat/product/dto/ProductSortCondition.java rename backend/src/main/java/com/funeat/product/persistence/{ProductSpecifications.java => ProductSpecification.java} (79%) diff --git a/backend/src/main/java/com/funeat/product/application/ProductService.java b/backend/src/main/java/com/funeat/product/application/ProductService.java index 051a49886..921d07d7a 100644 --- a/backend/src/main/java/com/funeat/product/application/ProductService.java +++ b/backend/src/main/java/com/funeat/product/application/ProductService.java @@ -9,6 +9,7 @@ import com.funeat.product.dto.ProductInCategoryDto; import com.funeat.product.dto.ProductResponse; import com.funeat.product.dto.ProductReviewCountDto; +import com.funeat.product.dto.ProductSortCondition; import com.funeat.product.dto.ProductsInCategoryResponse; import com.funeat.product.dto.RankingProductDto; import com.funeat.product.dto.RankingProductsResponse; @@ -21,7 +22,7 @@ import com.funeat.product.persistence.CategoryRepository; import com.funeat.product.persistence.ProductRecipeRepository; import com.funeat.product.persistence.ProductRepository; -import com.funeat.product.persistence.ProductSpecifications; +import com.funeat.product.persistence.ProductSpecification; import com.funeat.recipe.domain.Recipe; import com.funeat.recipe.domain.RecipeImage; import com.funeat.recipe.dto.RecipeDto; @@ -74,21 +75,16 @@ public ProductService(final CategoryRepository categoryRepository, final Product } public ProductsInCategoryResponse getAllProductsInCategory(final Long categoryId, final Long lastProductId, - final String sort) { + final ProductSortCondition sortCondition) { final Category category = categoryRepository.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(CATEGORY_NOT_FOUND, categoryId)); final Product lastProduct = productRepository.findById(lastProductId).orElse(null); - final String[] split = sort.split(","); - final String sortBy = split[0]; - final String sortOrder = split[1]; - - final Specification specification = ProductSpecifications.searchBy(category, lastProduct, sortBy, - sortOrder); + final Specification specification = ProductSpecification.searchBy(category, lastProduct, sortCondition); final List findResults = productRepository.findAllWithSpecification(specification, DEFAULT_CURSOR_PAGINATION_SIZE); final List productDtos = getProductInCategoryDtos(findResults); - final Boolean hasNext = hasNextPage(findResults); + final boolean hasNext = hasNextPage(findResults); return ProductsInCategoryResponse.toResponse(hasNext, productDtos); } @@ -109,7 +105,7 @@ private int getResultSize(final List findProducts) { return DEFAULT_PAGE_SIZE; } - private Boolean hasNextPage(final List findProducts) { + private boolean hasNextPage(final List findProducts) { return findProducts.size() > DEFAULT_PAGE_SIZE; } diff --git a/backend/src/main/java/com/funeat/product/dto/ProductSortCondition.java b/backend/src/main/java/com/funeat/product/dto/ProductSortCondition.java new file mode 100644 index 000000000..8a929f99c --- /dev/null +++ b/backend/src/main/java/com/funeat/product/dto/ProductSortCondition.java @@ -0,0 +1,25 @@ +package com.funeat.product.dto; + +public class ProductSortCondition { + + private final String by; + private final String order; + + private ProductSortCondition(final String by, final String order) { + this.by = by; + this.order = order; + } + + public static ProductSortCondition toDto(final String sort) { + final String[] split = sort.split(","); + return new ProductSortCondition(split[0], split[1]); + } + + public String getBy() { + return by; + } + + public String getOrder() { + return order; + } +} diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java b/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java similarity index 79% rename from backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java rename to backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java index 390a6ff3e..8263ce901 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductSpecifications.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java @@ -4,26 +4,28 @@ import com.funeat.product.domain.Category; import com.funeat.product.domain.Product; +import com.funeat.product.dto.ProductSortCondition; import com.funeat.product.exception.ProductException.NotSupportedProductSortingConditionException; import java.util.Objects; import javax.persistence.criteria.Path; import org.springframework.data.jpa.domain.Specification; -public class ProductSpecifications { +public class ProductSpecification { + + private static final String DESC = "desc"; public static Specification searchBy(final Category category, final Product lastProduct, - final String sortBy, final String sortOrder) { + final ProductSortCondition sortCondition) { return (root, query, builder) -> { - - if ("desc".equals(sortOrder)) { - query.orderBy(builder.desc(root.get(sortBy)), builder.desc(root.get("id"))); + if (DESC.equals(sortCondition.getOrder())) { + query.orderBy(builder.desc(root.get(sortCondition.getBy())), builder.desc(root.get("id"))); } else { - query.orderBy(builder.asc(root.get(sortBy)), builder.desc(root.get("id"))); + query.orderBy(builder.asc(root.get(sortCondition.getBy())), builder.desc(root.get("id"))); } return Specification .where(sameCategory(category)) - .and(findNext(lastProduct, sortBy, sortOrder)) + .and(findNext(lastProduct, sortCondition)) .toPredicate(root, query, builder); }; } @@ -36,8 +38,10 @@ private static Specification sameCategory(final Category category) { }; } - private static Specification findNext(final Product lastProduct, final String sortBy, - final String sortOrder) { + private static Specification findNext(final Product lastProduct, final ProductSortCondition sortCondition) { + final String sortBy = sortCondition.getBy(); + final String sortOrder = sortCondition.getOrder(); + return (root, query, builder) -> { if (Objects.isNull(lastProduct)) { return null; @@ -75,7 +79,7 @@ private static Specification sameValue(final String sortBy, final Long private static Specification notSameValue(final String sortBy, final String sortOrder, final Comparable comparisonValue) { return (root, query, builder) -> { - if (sortOrder.equals("desc")) { + if (DESC.equals(sortOrder)) { return builder.lessThan(root.get(sortBy), comparisonValue); } else { return builder.greaterThan(root.get(sortBy), comparisonValue); diff --git a/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java b/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java index 164bd3d1c..ab317b7ed 100644 --- a/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java +++ b/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java @@ -1,6 +1,7 @@ package com.funeat.product.presentation; import com.funeat.product.application.ProductService; +import com.funeat.product.dto.ProductSortCondition; import com.funeat.product.dto.ProductResponse; import com.funeat.product.dto.ProductsInCategoryResponse; import com.funeat.product.dto.RankingProductsResponse; @@ -31,7 +32,8 @@ public ProductApiController(final ProductService productService) { public ResponseEntity getAllProductsInCategory(@PathVariable final Long categoryId, @RequestParam(name = "id") Long lastId, @RequestParam(name = "sort") String sort) { - final ProductsInCategoryResponse response = productService.getAllProductsInCategory(categoryId, lastId, sort); + final ProductSortCondition sortCondition = ProductSortCondition.toDto(sort); + final ProductsInCategoryResponse response = productService.getAllProductsInCategory(categoryId, lastId, sortCondition); return ResponseEntity.ok(response); } From 25e449c7eb56e16b4d74042db6ea8b717b60b35d Mon Sep 17 00:00:00 2001 From: hanueleee Date: Mon, 16 Oct 2023 02:01:21 +0900 Subject: [PATCH 15/18] =?UTF-8?q?refactor:=20ProductSpecification=EC=9D=98?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ProductSpecification.java | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java b/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java index 8263ce901..b084f7566 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java @@ -7,7 +7,10 @@ import com.funeat.product.dto.ProductSortCondition; import com.funeat.product.exception.ProductException.NotSupportedProductSortingConditionException; import java.util.Objects; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Path; +import javax.persistence.criteria.Root; import org.springframework.data.jpa.domain.Specification; public class ProductSpecification { @@ -17,19 +20,27 @@ public class ProductSpecification { public static Specification searchBy(final Category category, final Product lastProduct, final ProductSortCondition sortCondition) { return (root, query, builder) -> { - if (DESC.equals(sortCondition.getOrder())) { - query.orderBy(builder.desc(root.get(sortCondition.getBy())), builder.desc(root.get("id"))); - } else { - query.orderBy(builder.asc(root.get(sortCondition.getBy())), builder.desc(root.get("id"))); - } + setOrderBy(sortCondition, root, query, builder); return Specification .where(sameCategory(category)) - .and(findNext(lastProduct, sortCondition)) + .and(nextCursor(lastProduct, sortCondition)) .toPredicate(root, query, builder); }; } + private static void setOrderBy(final ProductSortCondition sortCondition, final Root root, + final CriteriaQuery query, final CriteriaBuilder builder) { + final String sortBy = sortCondition.getBy(); + final String sortOrder = sortCondition.getOrder(); + + if (DESC.equals(sortOrder)) { + query.orderBy(builder.desc(root.get(sortBy)), builder.desc(root.get("id"))); + } else { + query.orderBy(builder.asc(root.get(sortBy)), builder.desc(root.get("id"))); + } + } + private static Specification sameCategory(final Category category) { return (root, query, builder) -> { final Path categoryPath = root.get("category"); @@ -38,7 +49,7 @@ private static Specification sameCategory(final Category category) { }; } - private static Specification findNext(final Product lastProduct, final ProductSortCondition sortCondition) { + private static Specification nextCursor(final Product lastProduct, final ProductSortCondition sortCondition) { final String sortBy = sortCondition.getBy(); final String sortOrder = sortCondition.getOrder(); @@ -50,8 +61,8 @@ private static Specification findNext(final Product lastProduct, final final Comparable comparisonValue = (Comparable) getComparisonValue(lastProduct, sortBy); return builder.or( - sameValue(sortBy, lastProduct.getId(), comparisonValue).toPredicate(root, query, builder), - notSameValue(sortBy, sortOrder, comparisonValue).toPredicate(root, query, builder) + sameValueAndSmallerId(sortBy, lastProduct.getId(), comparisonValue).toPredicate(root, query, builder), + nextValue(sortBy, sortOrder, comparisonValue).toPredicate(root, query, builder) ); }; } @@ -69,15 +80,15 @@ private static Object getComparisonValue(final Product lastProduct, final String throw new NotSupportedProductSortingConditionException(NOT_SUPPORTED_PRODUCT_SORTING_CONDITION, sortBy); } - private static Specification sameValue(final String sortBy, final Long lastProductId, - final Comparable comparisonValue) { + private static Specification sameValueAndSmallerId(final String sortBy, final Long lastProductId, + final Comparable comparisonValue) { return (root, query, builder) -> builder.and( builder.equal(root.get(sortBy), comparisonValue), builder.lessThan(root.get("id"), lastProductId)); } - private static Specification notSameValue(final String sortBy, final String sortOrder, - final Comparable comparisonValue) { + private static Specification nextValue(final String sortBy, final String sortOrder, + final Comparable comparisonValue) { return (root, query, builder) -> { if (DESC.equals(sortOrder)) { return builder.lessThan(root.get(sortBy), comparisonValue); From f0553faf03b189b9fc0323c2f7a4ba08a12fea32 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 17 Oct 2023 16:36:32 +0900 Subject: [PATCH 16/18] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/repository/BaseRepository.java | 2 +- .../common/repository/BaseRepositoryImpl.java | 4 +- .../persistence/ProductRepository.java | 8 - .../persistence/ProductSpecification.java | 22 ++- .../presentation/ProductApiController.java | 8 +- .../presentation/ProductController.java | 4 +- .../funeat/acceptance/common/CommonSteps.java | 3 +- .../product/ProductAcceptanceTest.java | 20 +-- .../acceptance/product/ProductSteps.java | 4 +- .../persistence/ProductRepositoryTest.java | 149 ------------------ 10 files changed, 37 insertions(+), 187 deletions(-) diff --git a/backend/src/main/java/com/funeat/common/repository/BaseRepository.java b/backend/src/main/java/com/funeat/common/repository/BaseRepository.java index 5211e6a4b..448db7766 100644 --- a/backend/src/main/java/com/funeat/common/repository/BaseRepository.java +++ b/backend/src/main/java/com/funeat/common/repository/BaseRepository.java @@ -13,5 +13,5 @@ public interface BaseRepository extends JpaRepositor Page findAllForPagination(final Specification spec, final Pageable pageable, final Long totalElements); - List findAllWithSpecification(final Specification spec, final int size); + List findAllWithSpecification(final Specification spec, final int pageSize); } diff --git a/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java b/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java index a94416375..64cd508f6 100644 --- a/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java +++ b/backend/src/main/java/com/funeat/common/repository/BaseRepositoryImpl.java @@ -42,9 +42,9 @@ public Page findAllForPagination(final Specification spec, final Pageable } @Override - public List findAllWithSpecification(final Specification spec, final int size) { + public List findAllWithSpecification(final Specification spec, final int pageSize) { final TypedQuery query = getQuery(spec, Sort.unsorted()); - query.setMaxResults(size); + query.setMaxResults(pageSize); return query.getResultList(); } diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java index 07539366f..9b4036361 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductRepository.java @@ -1,24 +1,16 @@ package com.funeat.product.persistence; import com.funeat.common.repository.BaseRepository; -import com.funeat.product.domain.Category; import com.funeat.product.domain.Product; -import com.funeat.product.dto.ProductInCategoryDto; import com.funeat.product.dto.ProductReviewCountDto; import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface ProductRepository extends BaseRepository { - @Query(value = "SELECT new com.funeat.product.dto.ProductInCategoryDto(p.id, p.name, p.price, p.image, p.averageRating, p.reviewCount) " - + "FROM Product p " - + "WHERE p.category = :category ") - Slice findAllByCategory(@Param("category") final Category category, final Pageable pageable); - @Query("SELECT new com.funeat.product.dto.ProductReviewCountDto(p, COUNT(r.id)) " + "FROM Product p " + "LEFT JOIN Review r ON r.product.id = p.id " diff --git a/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java b/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java index b084f7566..f2dbd2ff2 100644 --- a/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java +++ b/backend/src/main/java/com/funeat/product/persistence/ProductSpecification.java @@ -15,7 +15,15 @@ public class ProductSpecification { + private ProductSpecification() { + } + private static final String DESC = "desc"; + private static final String CATEGORY = "category"; + private static final String ID = "id"; + private static final String REVIEW_COUNT = "reviewCount"; + private static final String AVERAGE_RATING = "averageRating"; + private static final String PRICE = "price"; public static Specification searchBy(final Category category, final Product lastProduct, final ProductSortCondition sortCondition) { @@ -35,15 +43,15 @@ private static void setOrderBy(final ProductSortCondition sortCondition, final R final String sortOrder = sortCondition.getOrder(); if (DESC.equals(sortOrder)) { - query.orderBy(builder.desc(root.get(sortBy)), builder.desc(root.get("id"))); + query.orderBy(builder.desc(root.get(sortBy)), builder.desc(root.get(ID))); } else { - query.orderBy(builder.asc(root.get(sortBy)), builder.desc(root.get("id"))); + query.orderBy(builder.asc(root.get(sortBy)), builder.desc(root.get(ID))); } } private static Specification sameCategory(final Category category) { return (root, query, builder) -> { - final Path categoryPath = root.get("category"); + final Path categoryPath = root.get(CATEGORY); return builder.equal(categoryPath, category); }; @@ -68,13 +76,13 @@ private static Specification nextCursor(final Product lastProduct, fina } private static Object getComparisonValue(final Product lastProduct, final String sortBy) { - if ("price".equals(sortBy)) { + if (PRICE.equals(sortBy)) { return lastProduct.getPrice(); } - if ("averageRating".equals(sortBy)) { + if (AVERAGE_RATING.equals(sortBy)) { return lastProduct.getAverageRating(); } - if ("reviewCount".equals(sortBy)) { + if (REVIEW_COUNT.equals(sortBy)) { return lastProduct.getReviewCount(); } throw new NotSupportedProductSortingConditionException(NOT_SUPPORTED_PRODUCT_SORTING_CONDITION, sortBy); @@ -84,7 +92,7 @@ private static Specification sameValueAndSmallerId(final String sortBy, final Comparable comparisonValue) { return (root, query, builder) -> builder.and( builder.equal(root.get(sortBy), comparisonValue), - builder.lessThan(root.get("id"), lastProductId)); + builder.lessThan(root.get(ID), lastProductId)); } private static Specification nextValue(final String sortBy, final String sortOrder, diff --git a/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java b/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java index ab317b7ed..46435aee7 100644 --- a/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java +++ b/backend/src/main/java/com/funeat/product/presentation/ProductApiController.java @@ -1,8 +1,8 @@ package com.funeat.product.presentation; import com.funeat.product.application.ProductService; -import com.funeat.product.dto.ProductSortCondition; import com.funeat.product.dto.ProductResponse; +import com.funeat.product.dto.ProductSortCondition; import com.funeat.product.dto.ProductsInCategoryResponse; import com.funeat.product.dto.RankingProductsResponse; import com.funeat.product.dto.SearchProductResultsResponse; @@ -30,10 +30,10 @@ public ProductApiController(final ProductService productService) { @GetMapping("/categories/{categoryId}/products") public ResponseEntity getAllProductsInCategory(@PathVariable final Long categoryId, - @RequestParam(name = "id") Long lastId, - @RequestParam(name = "sort") String sort) { + @RequestParam final Long lastProductId, + @RequestParam final String sort) { final ProductSortCondition sortCondition = ProductSortCondition.toDto(sort); - final ProductsInCategoryResponse response = productService.getAllProductsInCategory(categoryId, lastId, sortCondition); + final ProductsInCategoryResponse response = productService.getAllProductsInCategory(categoryId, lastProductId, sortCondition); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/com/funeat/product/presentation/ProductController.java b/backend/src/main/java/com/funeat/product/presentation/ProductController.java index 8346c1339..3fa704eb7 100644 --- a/backend/src/main/java/com/funeat/product/presentation/ProductController.java +++ b/backend/src/main/java/com/funeat/product/presentation/ProductController.java @@ -27,8 +27,8 @@ public interface ProductController { @GetMapping ResponseEntity getAllProductsInCategory( @PathVariable final Long categoryId, - @RequestParam(name = "id") Long lastId, - @RequestParam(name = "sort") String sort + @RequestParam final Long lastProductId, + @RequestParam final String sort ); @Operation(summary = "해당 상품 상세 조회", description = "해당 상품 상세정보를 조회한다.") diff --git a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java index 0f11394ed..35e5e3c3a 100644 --- a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java @@ -78,7 +78,6 @@ public class CommonSteps { public static void 다음_페이지_유무를_검증한다(final ExtractableResponse response, final boolean expected) { final var actual = response.jsonPath().getBoolean("hasNext"); - assertThat(actual) - .isEqualTo(expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index 69f319571..8cbf1d54e 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -130,7 +130,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품1, 상품2)); } @@ -148,7 +148,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } @@ -170,7 +170,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품1, 상품3, 상품2)); } @@ -188,7 +188,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -210,7 +210,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(2L, 1L, 3L)); } @@ -228,7 +228,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -250,7 +250,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(1L, 3L, 2L)); } @@ -268,7 +268,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -295,7 +295,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품2, 상품1, 상품3)); } @@ -313,7 +313,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, false); + 다음_페이지_유무를_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java b/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java index 7e78a03dc..d41bd5be1 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductSteps.java @@ -10,10 +10,10 @@ public class ProductSteps { public static ExtractableResponse 카테고리별_상품_목록_조회_요청(final Long categoryId, final String sort, - final Long lastId) { + final Long lastProductId) { return given() .queryParam("sort", sort) - .queryParam("id", lastId) + .queryParam("lastProductId", lastProductId) .when() .get("/api/categories/{category_id}/products", categoryId) .then() diff --git a/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java b/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java index 20bebe3bc..e1eff6623 100644 --- a/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java +++ b/backend/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java @@ -45,155 +45,6 @@ @SuppressWarnings("NonAsciiCharacters") class ProductRepositoryTest extends RepositoryTest { - @Nested - class findAllByCategory_성공_테스트 { - - @Test - void 카테고리별_상품을_평점이_높은_순으로_정렬한다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - - final var product1 = 상품_삼각김밥_가격1000원_평점1점_생성(category); - final var product2 = 상품_삼각김밥_가격1000원_평점2점_생성(category); - final var product3 = 상품_삼각김밥_가격1000원_평점3점_생성(category); - final var product4 = 상품_삼각김밥_가격1000원_평점4점_생성(category); - final var product5 = 상품_삼각김밥_가격1000원_평점5점_생성(category); - 복수_상품_저장(product1, product2, product3, product4, product5); - - final var page = 페이지요청_생성(0, 3, 평균_평점_내림차순); - - final var productInCategoryDto1 = ProductInCategoryDto.toDto(product5, 0L); - final var productInCategoryDto2 = ProductInCategoryDto.toDto(product4, 0L); - final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3, 0L); - final var expected = List.of(productInCategoryDto1, productInCategoryDto2, productInCategoryDto3); - - // when - final var actual = productRepository.findAllByCategory(category, page).getContent(); - - // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - } - - @Test - void 카테고리별_상품을_평점이_낮은_순으로_정렬한다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - - final var product1 = 상품_삼각김밥_가격1000원_평점1점_생성(category); - final var product2 = 상품_삼각김밥_가격1000원_평점2점_생성(category); - final var product3 = 상품_삼각김밥_가격1000원_평점3점_생성(category); - final var product4 = 상품_삼각김밥_가격1000원_평점4점_생성(category); - final var product5 = 상품_삼각김밥_가격1000원_평점5점_생성(category); - 복수_상품_저장(product1, product2, product3, product4, product5); - - final var page = 페이지요청_생성(0, 3, 평균_평점_오름차순); - - final var productInCategoryDto1 = ProductInCategoryDto.toDto(product1, 0L); - final var productInCategoryDto2 = ProductInCategoryDto.toDto(product2, 0L); - final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3, 0L); - final var expected = List.of(productInCategoryDto1, productInCategoryDto2, productInCategoryDto3); - - // when - final var actual = productRepository.findAllByCategory(category, page).getContent(); - - // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - } - - @Test - void 카테고리별_상품을_가격이_높은_순으로_정렬한다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - - final var product1 = 상품_삼각김밥_가격1000원_평점1점_생성(category); - final var product2 = 상품_삼각김밥_가격2000원_평점1점_생성(category); - final var product3 = 상품_삼각김밥_가격3000원_평점1점_생성(category); - final var product4 = 상품_삼각김밥_가격4000원_평점1점_생성(category); - final var product5 = 상품_삼각김밥_가격5000원_평점1점_생성(category); - 복수_상품_저장(product1, product2, product3, product4, product5); - - final var page = 페이지요청_생성(0, 3, 가격_내림차순); - - final var productInCategoryDto1 = ProductInCategoryDto.toDto(product5, 0L); - final var productInCategoryDto2 = ProductInCategoryDto.toDto(product4, 0L); - final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3, 0L); - final var expected = List.of(productInCategoryDto1, productInCategoryDto2, productInCategoryDto3); - - // when - final var actual = productRepository.findAllByCategory(category, page).getContent(); - - // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - } - - @Test - void 카테고리별_상품을_가격이_낮은_순으로_정렬한다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - - final var product1 = 상품_삼각김밥_가격1000원_평점1점_생성(category); - final var product2 = 상품_삼각김밥_가격2000원_평점1점_생성(category); - final var product3 = 상품_삼각김밥_가격3000원_평점1점_생성(category); - final var product4 = 상품_삼각김밥_가격4000원_평점1점_생성(category); - final var product5 = 상품_삼각김밥_가격5000원_평점1점_생성(category); - 복수_상품_저장(product1, product2, product3, product4, product5); - - final var page = 페이지요청_생성(0, 3, 가격_오름차순); - - final var productInCategoryDto1 = ProductInCategoryDto.toDto(product1, 0L); - final var productInCategoryDto2 = ProductInCategoryDto.toDto(product2, 0L); - final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3, 0L); - final var expected = List.of(productInCategoryDto1, productInCategoryDto2, productInCategoryDto3); - - // when - final var actual = productRepository.findAllByCategory(category, page).getContent(); - - // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - } - - @Test - void 카테고리별_상품을_리뷰수가_많은_순으로_정렬한다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - - final var product1 = 상품_삼각김밥_가격5000원_리뷰0개_생성(category); - final var product2 = 상품_삼각김밥_가격1000원_리뷰3개_생성(category); - final var product3 = 상품_삼각김밥_가격2000원_리뷰1개_생성(category); - final var product4 = 상품_삼각김밥_가격3000원_리뷰5개_생성(category); - 복수_상품_저장(product1, product2, product3, product4); - - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); - - final var page = 페이지요청_생성(0, 3, 리뷰수_내림차순); - - final var productInCategoryDto1 = ProductInCategoryDto.toDto(product4); - final var productInCategoryDto2 = ProductInCategoryDto.toDto(product2); - final var productInCategoryDto3 = ProductInCategoryDto.toDto(product3); - final var expected = List.of(productInCategoryDto1, productInCategoryDto2, productInCategoryDto3); - - // when - final var actual = productRepository.findAllByCategory(category, page) - .getContent(); - - // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - } - } - @Nested class findAllByAverageRatingGreaterThan3_성공_테스트 { From 312d43126ccba4850822b4187d8b8cf804c8dbc4 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 17 Oct 2023 17:49:27 +0900 Subject: [PATCH 17/18] =?UTF-8?q?chore:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/acceptance/common/CommonSteps.java | 2 +- .../product/ProductAcceptanceTest.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java index 35e5e3c3a..d0c1cab62 100644 --- a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java @@ -75,7 +75,7 @@ public class CommonSteps { .isEqualTo(expected); } - public static void 다음_페이지_유무를_검증한다(final ExtractableResponse response, final boolean expected) { + public static void 다음_페이지가_있는지_검증한다(final ExtractableResponse response, final boolean expected) { final var actual = response.jsonPath().getBoolean("hasNext"); assertThat(actual).isEqualTo(expected); diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index 8cbf1d54e..d90178227 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -2,7 +2,7 @@ import static com.funeat.acceptance.auth.LoginSteps.로그인_쿠키_획득; import static com.funeat.acceptance.common.CommonSteps.STATUS_CODE를_검증한다; -import static com.funeat.acceptance.common.CommonSteps.다음_페이지_유무를_검증한다; +import static com.funeat.acceptance.common.CommonSteps.다음_페이지가_있는지_검증한다; import static com.funeat.acceptance.common.CommonSteps.사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.여러개_사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.정상_처리; @@ -130,7 +130,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품1, 상품2)); } @@ -148,7 +148,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } @@ -170,7 +170,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품1, 상품3, 상품2)); } @@ -188,7 +188,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -210,7 +210,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(2L, 1L, 3L)); } @@ -228,7 +228,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -250,7 +250,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(1L, 3L, 2L)); } @@ -268,7 +268,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -295,7 +295,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품2, 상품1, 상품3)); } @@ -313,7 +313,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지_유무를_검증한다(응답, 마지막페이지X); + 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } From 9d9008a110b1281f9463b0ef57f381917d3077d5 Mon Sep 17 00:00:00 2001 From: hanueleee Date: Tue, 17 Oct 2023 17:55:02 +0900 Subject: [PATCH 18/18] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20fail?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/ProductAcceptanceTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index d90178227..5a73e6554 100644 --- a/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/backend/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -2,7 +2,7 @@ import static com.funeat.acceptance.auth.LoginSteps.로그인_쿠키_획득; import static com.funeat.acceptance.common.CommonSteps.STATUS_CODE를_검증한다; -import static com.funeat.acceptance.common.CommonSteps.다음_페이지가_있는지_검증한다; +import static com.funeat.acceptance.common.CommonSteps.다음_데이터가_있는지_검증한다; import static com.funeat.acceptance.common.CommonSteps.사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.여러개_사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.정상_처리; @@ -130,7 +130,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품1, 상품2)); } @@ -148,7 +148,7 @@ class 가격_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } } @@ -170,7 +170,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품1, 상품3, 상품2)); } @@ -188,7 +188,7 @@ class 가격_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -210,7 +210,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(2L, 1L, 3L)); } @@ -228,7 +228,7 @@ class 평점_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -250,7 +250,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(1L, 3L, 2L)); } @@ -268,7 +268,7 @@ class 평점_기준_오름차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(3L, 2L, 1L)); } } @@ -295,7 +295,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품2, 상품1, 상품3)); } @@ -313,7 +313,7 @@ class 리뷰수_기준_내림차순으로_카테고리별_상품_목록_조회 { // then STATUS_CODE를_검증한다(응답, 정상_처리); - 다음_페이지가_있는지_검증한다(응답, 마지막페이지X); + 다음_데이터가_있는지_검증한다(응답, 마지막페이지X); 카테고리별_상품_목록_조회_결과를_검증한다(응답, List.of(상품3, 상품2, 상품1)); } }