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

Feature/refactor paging: MongoDB 페이징 방식 변경 #80

Merged
merged 3 commits into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -19,23 +21,24 @@ public class EventProductController {

private final EventProductService eventProductService;

@Operation(summary = "이벤트 상품 필터 조회", description = "이벤트 상품을 필터링 조회합니다.")
@Operation(summary = "행사 상품 필터 조회", description = "행사 상품을 필터링 조회합니다.")
@PostMapping("/products/event-products")
public ResponseEntity<Page<EventProductResponseDto>> getEventProductsByFilter(
@RequestParam int pageNumber,
@RequestParam int pageSize,
@RequestParam String storeType,
@RequestBody ProductFilterRequestDto productFilterRequestDto
) {
Page<ProductResponseDto> products = eventProductService.getFilteredProducts(
pageNumber, pageSize, storeType, productFilterRequestDto);
Pageable pageable = PageRequest.of(pageNumber, pageSize);
Page<ProductResponseDto> products = eventProductService.getPagedProductsDtoByFilter(
pageable, storeType, productFilterRequestDto);
return new ResponseEntity(products, HttpStatus.OK);
}

@Operation(summary = "이벤트 상품 단건 조회", description = "id 바탕으로 이벤트 상품을 조회합니다.")
@Operation(summary = "행사 상품 단건 조회", description = "id로 행사 상품을 조회합니다.")
@GetMapping("/products/event-products/{id}")
public ResponseEntity<EventProductResponseDto> getEventProduct(@PathVariable String id) {
ProductResponseDto responseDto = eventProductService.getProduct(id);
ProductResponseDto responseDto = eventProductService.getProductById(id);
return new ResponseEntity(responseDto, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -27,15 +29,16 @@ public ResponseEntity<Page<PbProductResponseDto>> getPbProductsByFilter(
@RequestParam String storeType,
@RequestBody ProductFilterRequestDto productFilterRequestDto
) {
Page<ProductResponseDto> products = pbProductService.getFilteredProducts(
pageNumber, pageSize, storeType, productFilterRequestDto);
Pageable pageable = PageRequest.of(pageNumber, pageSize);
Page<ProductResponseDto> products = pbProductService.getPagedProductsDtoByFilter(
pageable, storeType, productFilterRequestDto);
return new ResponseEntity(products, HttpStatus.OK);
}

@Operation(summary = "PB 상품 단건 조회", description = "id 바탕으로 PB 상품을 조회합니다.")
@Operation(summary = "PB 상품 단건 조회", description = "id로 PB 상품을 조회합니다.")
@GetMapping("/products/pb-products/{id}")
public ResponseEntity<PbProductResponseDto> getPbProduct(@PathVariable String id) {
ProductResponseDto product = pbProductService.getProduct(id);
ProductResponseDto product = pbProductService.getProductById(id);
return new ResponseEntity(product, HttpStatus.OK);
}
}
27 changes: 16 additions & 11 deletions src/main/java/com/pyonsnalcolor/product/enumtype/Sorted.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.pyonsnalcolor.product.entity.BaseProduct;
import lombok.Getter;
import org.springframework.data.domain.Sort;

import java.util.Arrays;
import java.util.Comparator;
Expand All @@ -13,6 +14,7 @@ public enum Sorted implements Filter {
LATEST(1,
"최신순",
null,
Sort.by("updatedTime").descending().and(Sort.by("_id").ascending()),
Comparator.comparing(BaseProduct::getCreatedDate).reversed().thenComparing(BaseProduct::getId)),
// VIEW(2,
// "조회순",
Expand All @@ -21,10 +23,12 @@ public enum Sorted implements Filter {
LOW_PRICE(3,
"낮은가격순",
null,
Sort.by("price").ascending().and(Sort.by("_id").ascending()),
Comparator.comparing(BaseProduct::getPrice).thenComparing(BaseProduct::getId)),
HIGH_PRICE(4,
"높은가격순",
null,
Sort.by("price").descending().and(Sort.by("_id").ascending()),
Comparator.comparing(BaseProduct::getPrice).reversed().thenComparing(BaseProduct::getId));
// REVIEW(5,
// "리뷰순",
Expand All @@ -34,14 +38,16 @@ public enum Sorted implements Filter {
private final int code;
private final String korean;
private final String image;
private final Sort sort;
private final Comparator<BaseProduct> comparator;

private static final String FILTER_TYPE = "sort";

Sorted(int code, String korean, String image, Comparator<BaseProduct> comparator) {
Sorted(int code, String korean, String image, Sort sort, Comparator<BaseProduct> comparator) {
this.code = code;
this.korean = korean;
this.image = image;
this.sort = sort;
this.comparator = comparator;
}

Expand All @@ -52,16 +58,6 @@ private static Sorted matchSortedByCode(int code) {
.orElse(null);
}

public static Comparator<BaseProduct> getCategoryFilteredComparator(List<Integer> filterList) {
Comparator<BaseProduct> filter = Sorted.findComparatorByFilterList(filterList);

if (Sorted.LATEST.getComparator() == filter) {
return BaseProduct.getCategoryComparator()
.thenComparing(filter);
}
return filter;
}

public static Comparator<BaseProduct> findComparatorByFilterList(List<Integer> filterList) {
return filterList.stream()
.map(Sorted::matchSortedByCode)
Expand All @@ -71,6 +67,15 @@ public static Comparator<BaseProduct> findComparatorByFilterList(List<Integer> f
.getComparator();
}

public static Sort findSortByFilterList(List<Integer> filterList) {
return filterList.stream()
.map(Sorted::matchSortedByCode)
.filter(Objects::nonNull)
.findFirst()
.orElse(LATEST)
.getSort();
}

@Override
public String getFilterType() {
return FILTER_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

import com.pyonsnalcolor.exception.PyonsnalcolorProductException;
import com.pyonsnalcolor.product.dto.ProductFilterRequestDto;
import com.pyonsnalcolor.product.dto.ProductResponseDto;
import com.pyonsnalcolor.product.entity.BaseEventProduct;
import com.pyonsnalcolor.product.enumtype.*;
import com.pyonsnalcolor.product.repository.EventProductRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand All @@ -23,37 +27,39 @@
@Service
public class EventProductService extends ProductService{

public EventProductService(EventProductRepository eventProductRepository) {
super(eventProductRepository);
}

@Override
public Page<ProductResponseDto> getFilteredProducts(
int pageNumber, int pageSize, String storeType, ProductFilterRequestDto productFilterRequestDto
) {
List<Integer> filterList = productFilterRequestDto.getFilterList();
validateProductFilterCodes(filterList);

Comparator comparator = Sorted.getCategoryFilteredComparator(filterList);
List<BaseEventProduct> eventProducts = getProductsListByFilter(storeType, filterList, BaseEventProduct.class);
eventProducts.sort(comparator);
private static final List<Integer> FILTER_CODES = Stream.of(
Filter.getCodes(Category.class),
Filter.getCodes(Sorted.class),
Filter.getCodes(EventType.class))
.flatMap(Collection::stream)
.collect(Collectors.toList());

List<ProductResponseDto> responseDtos = convertToResponseDtoList(eventProducts);
PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);
return convertToPage(responseDtos, pageRequest);
public EventProductService(EventProductRepository eventProductRepository, MongoTemplate mongoTemplate) {
super(eventProductRepository, mongoTemplate);
}

@Override
protected void validateProductFilterCodes(List<Integer> filterList) {
List<Integer> codes = Stream.of(
Filter.getCodes(Category.class),
Filter.getCodes(Sorted.class),
Filter.getCodes(EventType.class))
.flatMap(Collection::stream)
.collect(Collectors.toList());

if (!codes.containsAll(filterList)) {
if (!FILTER_CODES.containsAll(filterList)) {
throw new PyonsnalcolorProductException(INVALID_FILTER_CODE);
}
}

@Override
protected Page<BaseEventProduct> getPagedProductsByFilter(
Pageable pageable, String storeType, ProductFilterRequestDto productFilterRequestDto
) {
Aggregation aggregation = getAggregation(pageable, storeType, productFilterRequestDto);

AggregationResults<BaseEventProduct> aggregationResults = mongoTemplate.aggregate(
aggregation, "event_product", BaseEventProduct.class
);

Criteria criteria = createCriteriaByFilter(storeType, productFilterRequestDto.getFilterList());
return PageableExecutionUtils.getPage(
aggregationResults.getMappedResults(),
pageable,
() -> mongoTemplate.count(new Query(criteria), BaseEventProduct.class)
);
}
}
103 changes: 77 additions & 26 deletions src/main/java/com/pyonsnalcolor/product/service/PbProductService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,104 @@
import com.pyonsnalcolor.exception.PyonsnalcolorProductException;
import com.pyonsnalcolor.exception.model.CommonErrorCode;
import com.pyonsnalcolor.product.dto.ProductFilterRequestDto;
import com.pyonsnalcolor.product.dto.ProductResponseDto;
import com.pyonsnalcolor.product.entity.BasePbProduct;
import com.pyonsnalcolor.product.enumtype.*;
import com.pyonsnalcolor.product.repository.PbProductRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

@Service
public class PbProductService extends ProductService {

public PbProductService(PbProductRepository pbProductRepository) {
super(pbProductRepository);
private static final String CATEGORY_GOODS_FIELD = "categoryGoods";
private static final List<Integer> FILTER_CODES = Stream.of(
Filter.getCodes(Category.class),
Filter.getCodes(Sorted.class),
Filter.getCodes(Recommend.class),
Filter.getCodes(EventType.class))
.flatMap(Collection::stream)
.collect(Collectors.toList());

public PbProductService(PbProductRepository pbProductRepository, MongoTemplate mongoTemplate) {
super(pbProductRepository, mongoTemplate);
}

@Override
public Page<ProductResponseDto> getFilteredProducts(
int pageNumber, int pageSize, String storeType, ProductFilterRequestDto productFilterRequestDto
protected void validateProductFilterCodes(List<Integer> filterList) {
if (!FILTER_CODES.containsAll(filterList)) {
throw new PyonsnalcolorProductException(CommonErrorCode.INVALID_FILTER_CODE);
}
}

@Override
protected Page<BasePbProduct> getPagedProductsByFilter(
Pageable pageable, String storeType, ProductFilterRequestDto productFilterRequestDto
) {
Aggregation aggregation = getAggregationBySortCondition(pageable, storeType, productFilterRequestDto);

AggregationResults<BasePbProduct> aggregationResults = mongoTemplate.aggregate(
aggregation, "pb_product", BasePbProduct.class
);

Criteria criteria = createCriteriaByFilter(storeType, productFilterRequestDto.getFilterList());
return PageableExecutionUtils.getPage(
aggregationResults.getMappedResults(),
pageable,
() -> mongoTemplate.count(new Query(criteria), BasePbProduct.class)
);
}

private Aggregation getAggregationBySortCondition(
Pageable pageable, String storeType, ProductFilterRequestDto productFilterRequestDto
) {
Sort sort = Sorted.findSortByFilterList(productFilterRequestDto.getFilterList());

if (sort == Sorted.LATEST.getSort()) {
return getAggregationSortCategoryOfGoodsLast(pageable, storeType, productFilterRequestDto);
} return getAggregation(pageable, storeType, productFilterRequestDto);
}

private Aggregation getAggregationSortCategoryOfGoodsLast(
Pageable pageable, String storeType, ProductFilterRequestDto productFilterRequestDto
) {
List<Integer> filterList = productFilterRequestDto.getFilterList();
validateProductFilterCodes(filterList);
Criteria criteria = createCriteriaByFilter(storeType, filterList);
Sort sortByFilterList = Sorted.findSortByFilterList(filterList);

Comparator comparator = Sorted.getCategoryFilteredComparator(filterList);
List<BasePbProduct> responseDtos = getProductsListByFilter(storeType, filterList, BasePbProduct.class);
responseDtos.sort(comparator);
SortOperation sortStage = Aggregation.sort(Sort.Direction.ASC, CATEGORY_GOODS_FIELD);
sortStage.and(sortByFilterList);

List<ProductResponseDto> result = convertToResponseDtoList(responseDtos);
PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);
return convertToPage(result, pageRequest);
return newAggregation(
match(criteria),
addGoodsCategoryField(),
sort(Sort.Direction.ASC, CATEGORY_GOODS_FIELD).and(sortByFilterList),
project().andExclude(CATEGORY_GOODS_FIELD),
skip(pageable.getOffset()),
limit(pageable.getPageSize())
);
}

@Override
protected void validateProductFilterCodes(List<Integer> filterList) {
List<Integer> codes = Stream.of(
Filter.getCodes(Category.class),
Filter.getCodes(Sorted.class),
Filter.getCodes(Recommend.class),
Filter.getCodes(EventType.class))
.flatMap(Collection::stream)
.collect(Collectors.toList());

if (!codes.containsAll(filterList)) {
throw new PyonsnalcolorProductException(CommonErrorCode.INVALID_FILTER_CODE);
}
private AggregationOperation addGoodsCategoryField() {
return addFields()
.addField(CATEGORY_GOODS_FIELD)
.withValue(
ConditionalOperators.when(
ComparisonOperators.Eq.valueOf("category").equalToValue("GOODS"))
.then(1)
.otherwise(0))
.build();
}
}
Loading
Loading