Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] refactor: 상품 자동 완성 API 페이징 방식 변경 #757

Merged
merged 8 commits into from
Dec 29, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ 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 PAGE_SIZE = 10;
private static final int DEFAULT_PAGE_SIZE = 10;
private static final int DEFAULT_CURSOR_PAGINATION_SIZE = 11;

Expand Down Expand Up @@ -135,15 +136,23 @@ public RankingProductsResponse getTop3Products() {
return RankingProductsResponse.toResponse(rankingProductDtos);
}

public SearchProductsResponse searchProducts(final String query, final Pageable pageable) {
final Page<Product> products = productRepository.findAllByNameContaining(query, pageable);
public SearchProductsResponse searchProducts(final String query, final Long lastId) {
final List<Product> products = findAllByNameContaining(query, lastId);

final PageDto pageDto = PageDto.toDto(products);
final boolean hasNext = products.size() > PAGE_SIZE;
final List<SearchProductDto> productDtos = products.stream()
.map(SearchProductDto::toDto)
.collect(Collectors.toList());

return SearchProductsResponse.toResponse(pageDto, productDtos);
return SearchProductsResponse.toResponse(hasNext, productDtos);
}

private List<Product> findAllByNameContaining(final String query, final Long lastId) {
final PageRequest size = PageRequest.ofSize(PAGE_SIZE);
if (lastId == 0) {
return productRepository.findAllByNameContainingFirst(query, size);
}
return productRepository.findAllByNameContaining(query, lastId, size);
}

public SearchProductResultsResponse getSearchResults(final String query, final Pageable pageable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
package com.funeat.product.dto;

import com.funeat.common.dto.PageDto;
import java.util.List;

public class SearchProductsResponse {

private final PageDto page;
private final boolean hasNext;
private final List<SearchProductDto> products;

public SearchProductsResponse(final PageDto page, final List<SearchProductDto> products) {
this.page = page;
private SearchProductsResponse(final boolean hasNext, final List<SearchProductDto> products) {
this.hasNext = hasNext;
this.products = products;
}

public static SearchProductsResponse toResponse(final PageDto page, final List<SearchProductDto> products) {
return new SearchProductsResponse(page, products);
public static SearchProductsResponse toResponse(final boolean hasNext, final List<SearchProductDto> products) {
return new SearchProductsResponse(hasNext, products);
}

public PageDto getPage() {
return page;
public boolean isHasNext() {
return hasNext;
}

public List<SearchProductDto> getProducts() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ List<ProductReviewCountDto> findAllByAverageRatingGreaterThan3(final LocalDateTi
+ "ORDER BY "
+ "(CASE WHEN p.name LIKE CONCAT(:name, '%') THEN 1 ELSE 2 END), "
+ "p.id DESC")
Page<Product> findAllByNameContaining(@Param("name") final String name, final Pageable pageable);
List<Product> findAllByNameContainingFirst(@Param("name") final String name, final Pageable pageable);

@Query("SELECT p FROM Product p "
+ "JOIN Product last ON last.id = :lastId "
+ "WHERE p.name LIKE CONCAT('%', :name, '%') "
+ "AND (last.name LIKE CONCAT(:name, '%') "
+ "AND ((p.name LIKE CONCAT(:name, '%') AND p.id < :lastId) OR (p.name NOT LIKE CONCAT(:name, '%'))) "
+ "OR (p.name NOT LIKE CONCAT(:name, '%') AND p.id < :lastId)) "
+ "ORDER BY (CASE WHEN p.name LIKE CONCAT(:name, '%') THEN 1 ELSE 2 END), p.id DESC")
List<Product> findAllByNameContaining(@Param("name") final String name, final Long lastId, 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 "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ public ResponseEntity<RankingProductsResponse> getRankingProducts() {

@GetMapping("/search/products")
public ResponseEntity<SearchProductsResponse> searchProducts(@RequestParam final String query,
@PageableDefault final Pageable pageable) {
final PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize());
final SearchProductsResponse response = productService.searchProducts(query, pageRequest);
@RequestParam final Long lastId) {
final SearchProductsResponse response = productService.searchProducts(query, lastId);
return ResponseEntity.ok(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ ResponseEntity<ProductsInCategoryResponse> getAllProductsInCategory(
)
@GetMapping
ResponseEntity<SearchProductsResponse> searchProducts(@RequestParam final String query,
@PageableDefault final Pageable pageable);
@RequestParam final Long lastId);

@Operation(summary = "상품 검색 결과 조회", description = "문자열을 받아 상품을 검색하고 검색 결과들을 조회한다.")
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import com.funeat.product.dto.RankingProductDto;
import com.funeat.product.dto.SearchProductDto;
import com.funeat.product.dto.SearchProductResultDto;
import com.funeat.product.dto.SearchProductsResponse;
import com.funeat.recipe.dto.RecipeDto;
import com.funeat.tag.dto.TagDto;
import io.restassured.response.ExtractableResponse;
Expand Down Expand Up @@ -414,14 +415,11 @@ class searchProducts_성공_테스트 {
final var 상품1 = 단일_상품_저장(상품_애플망고_가격3000원_평점5점_생성(카테고리));
final var 상품2 = 단일_상품_저장(상품_망고빙수_가격5000원_평점4점_생성(카테고리));

final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(2L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE);

// when
final var 응답 = 상품_자동_완성_검색_요청("망고", FIRST_PAGE);
final var 응답 = 상품_자동_완성_검색_요청("망고", 0L);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
상품_자동_완성_검색_결과를_검증한다(응답, List.of(상품2, 상품1));
}

Expand All @@ -432,14 +430,11 @@ class searchProducts_성공_테스트 {
단일_카테고리_저장(카테고리);
반복_애플망고_상품_저장(2, 카테고리);

final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(0L), 총_페이지(0L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE);

// when
final var 응답 = 상품_자동_완성_검색_요청("김밥", FIRST_PAGE);
final var 응답 = 상품_자동_완성_검색_요청("김밥", 0L);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
상품_자동_완성_검색_결과를_검증한다(응답, Collections.emptyList());
}

Expand All @@ -449,21 +444,18 @@ class searchProducts_성공_테스트 {
final var 카테고리 = 카테고리_간편식사_생성();
단일_카테고리_저장(카테고리);
단일_상품_저장(상품_망고빙수_가격5000원_평점4점_생성(카테고리));
반복_애플망고_상품_저장(10, 카테고리);

final var 예상_응답_페이지1 = 응답_페이지_생성(총_데이터_개수(11L), 총_페이지(2L), 첫페이지O, 마지막페이지X, FIRST_PAGE, PAGE_SIZE);
final var 예상_응답_페이지2 = 응답_페이지_생성(총_데이터_개수(11L), 총_페이지(2L), 첫페이지X, 마지막페이지O, SECOND_PAGE, PAGE_SIZE);
반복_애플망고_상품_저장(15, 카테고리);

// when
final var 응답1 = 상품_자동_완성_검색_요청("망고", FIRST_PAGE);
final var 응답2 = 상품_자동_완성_검색_요청("망고", SECOND_PAGE);
final var 응답1 = 상품_자동_완성_검색_요청("망고", 0L);

final var result = 응답1.as(SearchProductsResponse.class).getProducts();
final var lastId = result.get(result.size() - 1).getId();
final var 응답2 = 상품_자동_완성_검색_요청("망고", lastId);

// then
STATUS_CODE를_검증한다(응답1, 정상_처리);
페이지를_검증한다(응답1, 예상_응답_페이지1);

STATUS_CODE를_검증한다(응답2, 정상_처리);
페이지를_검증한다(응답2, 예상_응답_페이지2);

결과값이_이전_요청_결과값에_중복되는지_검증(응답1, 응답2);
}
Expand All @@ -477,14 +469,11 @@ class searchProducts_성공_테스트 {
반복_애플망고_상품_저장(9, 카테고리);
단일_상품_저장(상품_망고빙수_가격5000원_평점4점_생성(카테고리));

final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(11L), 총_페이지(2L), 첫페이지O, 마지막페이지X, FIRST_PAGE, PAGE_SIZE);

// when
final var 응답 = 상품_자동_완성_검색_요청("망고", FIRST_PAGE);
final var 응답 = 상품_자동_완성_검색_요청("망고", 0L);

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
페이지를_검증한다(응답, 예상_응답_페이지);
상품_자동_완성_검색_결과를_검증한다(응답, List.of(상품11, 상품1, 상품10, 상품9, 상품8, 상품7, 상품6, 상품5, 상품4, 상품3));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static io.restassured.RestAssured.given;


import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;

Expand Down Expand Up @@ -36,10 +35,10 @@ public class ProductSteps {
.extract();
}

public static ExtractableResponse<Response> 상품_자동_완성_검색_요청(final String query, final Long page) {
public static ExtractableResponse<Response> 상품_자동_완성_검색_요청(final String query, final Long lastId) {
return given()
.queryParam("query", query)
.queryParam("page", page)
.queryParam("lastId", lastId)
.when()
.get("/api/search/products")
.then()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.PageRequest;

@SuppressWarnings("NonAsciiCharacters")
class ProductRepositoryTest extends RepositoryTest {
Expand Down Expand Up @@ -102,7 +103,7 @@ class findAllByAverageRatingGreaterThan3_성공_테스트 {
}

@Nested
class findAllByNameContaining_성공_테스트 {
class findAllByNameContainingFirst_성공_테스트 {

@Test
void 상품명에_검색어가_포함된_상품들을_조회한다() {
Expand All @@ -114,12 +115,36 @@ class findAllByNameContaining_성공_테스트 {
final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category);
복수_상품_저장(product1, product2);

final var page = 페이지요청_기본_생성(0, 10);
final var expected = List.of(product2, product1);

// when
final var actual = productRepository.findAllByNameContainingFirst("망고", PageRequest.of(0, 2));

// then
assertThat(actual).usingRecursiveComparison()
.isEqualTo(expected);
}
}

@Nested
class findAllByNameContaining_성공_테스트 {

@Test
void 상품명에_검색어가_포함된_상품들을_조회한다() {
// given
final var category = 카테고리_간편식사_생성();
단일_카테고리_저장(category);

final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category);
final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category);
final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category);
final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category);
복수_상품_저장(product1, product2, product3, product4);

final var expected = List.of(product2, product1);

// when
final var actual = productRepository.findAllByNameContaining("망고", page).getContent();
final var actual = productRepository.findAllByNameContaining("망고", 3L, PageRequest.of(0, 4));

// then
assertThat(actual).usingRecursiveComparison()
Expand Down