πŸ” Controller

/** 메인 νŽ˜μ΄μ§€ */
@GetMapping("/")
public ResponseEntity<List<List<?>>> mainPage(){
    List<List<?>> returnList = productService.mainPageProductList();
    return (!returnList.isEmpty()) ?
            ResponseEntity.status(HttpStatus.OK).body(returnList) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

/** μƒν’ˆλͺ…μœΌλ‘œ 검색 */
@GetMapping("/shop/search/{keyword}")
public ResponseEntity<List<ResponseProductSummary>> findByProductName(@PathVariable String keyword, @RequestParam(value = "sort", defaultValue = "hits") String sort){
    List<ResponseProductSummary> productList = productService.findByProductName(keyword, sort);
    return (!productList.isEmpty()) ?
            ResponseEntity.status(HttpStatus.OK).body(productList) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

/** μƒν’ˆ 전체 쑰회 */
@GetMapping("/shop")
public ResponseEntity<List<ResponseProductSummary>> findAllProduct(@RequestParam(value = "sort", defaultValue = "hits") String sort){
    List<ResponseProductSummary> productList = productService.findAllProduct(sort);
    return (!productList.isEmpty()) ?
            ResponseEntity.status(HttpStatus.OK).body(productList) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

/** μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬λ³„ 쑰회 */
@GetMapping("/shop/category/{category}")
public ResponseEntity<List<ResponseProductSummary>> findByCategory(@PathVariable String category, @RequestParam(value = "sort", defaultValue = "hits") String sort){
    List<ResponseProductSummary>productList = productService.findByCategory(category, sort);
    return (!productList.isEmpty()) ?
            ResponseEntity.status(HttpStatus.OK).body(productList) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

/** μƒν’ˆ 상세 νŽ˜μ΄μ§€ 쑰회 */
@Transactional
@GetMapping("/shop/detail/{id}")
public ResponseEntity<ResponseProduct> findById(@PathVariable Long id){
    productService.increaseHits(id);
    ResponseProduct product = productService.findById(id);
    return (product != null) ?
            ResponseEntity.status(HttpStatus.OK).body(product) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

/** νŒλ§€λ“±λ‘ν•œ μƒν’ˆ λͺ©λ‘ 쑰회 */
@GetMapping("/register/product")
public ResponseEntity<List<ResponseProductSummary>> findProductByUsername(HttpServletRequest request){
    User user = (User) request.getAttribute("user");
    List<ResponseProductSummary> productList = productService.findProductByUsername(user.getId());
    return (!productList.isEmpty()) ?
            ResponseEntity.status(HttpStatus.OK).body(productList) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

ProductService의 각 μ‘°νšŒμ— λ§žλŠ” ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κ³  결과에 따라 200 λ˜λŠ” 400의 status codeλ₯Ό λ°˜ν™˜

μƒν’ˆλͺ…μœΌλ‘œ 검색, μƒν’ˆ 전체 쑰회, μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬λ³„ μ‘°νšŒλŠ” μ›ν•˜λŠ” sort λ°©μ‹μœΌλ‘œ μ‚¬μš© κ°€λŠ₯

πŸ” Service

@Override
public List<List<?>> mainPageProductList() {
    List<Product> productList = productRepository.findTop8ByOrderByIdDesc();
    List<Banner> bannerList = bannerRepository.findAll();

    return entityToDtoMainPageList(productList, bannerList);
}

@Override
public List<ResponseProductSummary> findByProductName(String keyword, String sort) {
    List<Product> productList = new ArrayList<>();
    switch (sort) {
        case "hits" -> productList = productRepository.findByNameContainingAndStockGreaterThanOrderByHitsDesc(keyword, 0);
        case "date" -> productList = productRepository.findByNameContainingAndStockGreaterThanOrderByDateDesc(keyword, 0);
        case "favorite"-> productList = productRepository.findByNameContainingAndStockGreaterThanOrderByFavoriteDesc(keyword, 0);
        case "purchase" -> {
            return purchaseSort(productRepository.findSearchProductPurchase(keyword));
        }
    }

    return entityToDtoResponseProductSummary(productList);
}

@Override
public List<ResponseProductSummary> findAllProduct(String sort) {
    List<Product> productList = new ArrayList<>();
    switch (sort) {
        case "hits" -> productList = productRepository.findByStockGreaterThanOrderByHitsDesc(0);
        case "date" -> productList = productRepository.findByStockGreaterThanOrderByDateDesc(0);
        case "favorite"-> productList = productRepository.findByStockGreaterThanOrderByFavoriteDesc(0);
        case "purchase" -> {
            return purchaseSort(productRepository.findAllProductPurchase());
        }
    }

    return entityToDtoResponseProductSummary(productList);
}

@Override
public List<ResponseProductSummary> findByCategory(String category, String sort) {
    List<Product> productList = new ArrayList<>();
    switch (sort) {
        case "hits" -> productList = productRepository.findByCategoryAndStockGreaterThanOrderByHitsDesc(category, 0);
        case "date" -> productList = productRepository.findByCategoryAndStockGreaterThanOrderByDateDesc(category, 0);
        case "favorite"-> productList = productRepository.findByCategoryAndStockGreaterThanOrderByFavoriteDesc(category, 0);
        case "purchase" -> {
            return purchaseSort(productRepository.findCategoryProductPurchase(category));
        }
    }

    return entityToDtoResponseProductSummary(productList);
}

@Override
public ResponseProductDetails findById(User user, Long id) {
    Product product = productRepository.findById(id).orElse(null);
    if (product == null){
        return null;
    }

    Boolean status = false;
    if(user != null) {
        status = favoriteRepository.existUserProductByFavorite(user.getId(), id);
    }

    return ResponseProductDetails.builder().product(product).status(status).build();
}

@Override
public List<ResponseProductSummary> findProductByUsername(Long userId) {
    List<Product> productList = productRepository.findByUserId(userId);

    return entityToDtoResponseProductSummary(productList);
}

/** Entity to Dto */
public List<ResponseProductSummary> entityToDtoResponseProductSummary(List<Product> productList) {
    List<ResponseProductSummary> responseProductList = new ArrayList<>();
    if (!productList.isEmpty()){
        for(Product product : productList){
            responseProductList.add(ResponseProductSummary.builder().product(product).build());
        }
    }

    return responseProductList;
}

public List<ResponseProductSummary> purchaseSort(List<ResponseProductPurchase> productPurchaseList) {
    Collections.sort(productPurchaseList);

    return productPurchaseList.stream().map(productPurchase -> ResponseProductSummary
            .dtoBuilder()
            .responseProductPurchase(productPurchase)
            .dtoBuild()).toList();
}

/** Entity to Dto */
public List<List<?>> entityToDtoMainPageList(List<Product> productList, List<Banner> bannerList) {
    List<ResponseProductMain> responseProductList = new ArrayList<>();
    List<ResponseBanner> responseBannerList = new ArrayList<>();
    List<List<?>> returnList = new ArrayList<>();

    if (!productList.isEmpty() && !bannerList.isEmpty()) {
        for(Product product : productList){
            responseProductList.add(ResponseProductMain.builder().product(product).build());
        }
        for (Banner banner : bannerList) {
            responseBannerList.add(ResponseBanner.builder().banner(banner).build());
        }
        returnList.add(responseProductList);
        returnList.add(responseBannerList);
    }

    return returnList;
}

각 μ›ν•˜λŠ” 쑰건을 μ»¨νŠΈλ‘€λŸ¬μ—μ„œ 받아와 μ μ ˆν•œ 쿼리λ₯Ό μˆ˜ν–‰

메인 νŽ˜μ΄μ§€λŠ” μƒν’ˆκ³Ό λ² λ„ˆλ₯Ό 같이 전달, entityToDtoMainPageList ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ Entity to Dto μˆ˜ν–‰

λ‹€λ₯Έ λͺ¨λ“  μ‘°νšŒλŠ” Product λΌλŠ” Entityλ§Œμ„ μ‘°νšŒν•˜λ―€λ‘œ 메인 νŽ˜μ΄μ§€μ™€λŠ” λ‹€λ₯Έ entityToDtoResponseProductSummary ν•¨μˆ˜λ‘œ Entity to Dto μˆ˜ν–‰

ꡬ맀 순 정렬은 쿼리 νŠΉμ„± 상 WAS λ‹¨μ—μ„œ μ •λ ¬ 둜직 μˆ˜ν–‰

πŸ” Repository

/**메인 νŽ˜μ΄μ§€ μƒν’ˆ 쑰회*/
List<Product> findTop8ByOrderByIdDesc();

// =================================================================
/**μƒν’ˆλͺ…μœΌλ‘œ 검색 μ‘°νšŒμˆ˜λ†’μ€μˆœμœΌλ‘œ 쑰회*/
List<Product> findByNameContainingAndStockGreaterThanOrderByHitsDesc(String name, int stock);

/**μƒν’ˆλͺ…μœΌλ‘œ 검색 μ΅œμ‹ μˆœμœΌλ‘œ 쑰회*/
List<Product> findByNameContainingAndStockGreaterThanOrderByDateDesc(String name, int stock);

/**μƒν’ˆλͺ…μœΌλ‘œ 검색 μ’‹μ•„μš”μˆœμœΌλ‘œ 쑰회*/
List<Product> findByNameContainingAndStockGreaterThanOrderByFavoriteDesc(String name, int stock);

// =================================================================
/**μƒν’ˆ 전체 μ‘°νšŒμˆ˜λ†’μ€μˆœμœΌλ‘œ 쑰회*/
List<Product> findByStockGreaterThanOrderByHitsDesc(int stock);

/**μƒν’ˆ 전체 μ΅œμ‹ μˆœμœΌλ‘œ 쑰회*/
List<Product> findByStockGreaterThanOrderByDateDesc(int stock);

/**μƒν’ˆ 전체 μ’‹μ•„μš”μˆœμœΌλ‘œ 쑰회*/
List<Product> findByStockGreaterThanOrderByFavoriteDesc(int stock);

// =================================================================
/**μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬λ³„ μ‘°νšŒμˆ˜λ†’μ€μˆœμœΌλ‘œ 쑰회*/
List<Product> findByCategoryAndStockGreaterThanOrderByHitsDesc(String category, int stock);

/**μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬λ³„ μ΅œμ‹ μˆœμœΌλ‘œ 쑰회*/
List<Product> findByCategoryAndStockGreaterThanOrderByDateDesc(String category, int stock);

/**μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬λ³„ μ’‹μ•„μš”μˆœμœΌλ‘œ 쑰회*/
List<Product> findByCategoryAndStockGreaterThanOrderByFavoriteDesc(String category, int stock);

// =================================================================

/**νŒλ§€λ“±λ‘ν•œ μƒν’ˆ λͺ©λ‘ 쑰회*/
List<Product> findByUserId(Long userId);

μ •λ ¬ μΏΌλ¦¬λŠ” 쑰회 수, 검색 순, μ’‹μ•„μš” μˆœμ€ JPA κΈ°λ³Έ 문법 μ‚¬μš©

@Override
public List<ResponseProductPurchase> findAllProductPurchase() {

    return queryFactory.select(Projections.fields(ResponseProductPurchase.class,
										product.id,
										product.name,
										product.price,
										product.favorite,
										product.imgKey,
                    ExpressionUtils.as(
                            JPAExpressions.select(orderProduct.count.sum())
                                    .from(orderProduct)
                                    .groupBy(orderProduct.product.id)
                                    .orderBy(OrderByNull.DEFAULT)
                                    .where(orderProduct.product.eq(product)), "count"
                    )))
            .from(product)
            .where(product.stock.gt(0))
            .fetch();
}

@Override
public List<ResponseProductPurchase> findSearchProductPurchase(String keyword) {
    BooleanExpression status = null;
    status = containKeyword(keyword);

    if (status == null) {
        return null;
    }

    return queryFactory.select(Projections.fields(ResponseProductPurchase.class,
										product.id,
										product.name,
										product.price,
										product.favorite,
										product.imgKey,
                    ExpressionUtils.as(
                            JPAExpressions.select(orderProduct.count.sum())
                                    .from(orderProduct)
                                    .groupBy(orderProduct.product.id)
                                    .orderBy(OrderByNull.DEFAULT)
                                    .where(orderProduct.product.eq(product)), "count"
                    )))
            .from(product)
            .where(status,product.stock.gt(0))
            .fetch();
}

@Override
public List<ResponseProductPurchase> findCategoryProductPurchase(String category) {
    BooleanExpression status = null;
    status = eqCategory(category);

    if (status == null) {
        return null;
    }

    return queryFactory.select(Projections.fields(ResponseProductPurchase.class,
										product.id,
										product.name,
										product.price,
										product.favorite,
										product.imgKey,
                    ExpressionUtils.as(
                            JPAExpressions.select(orderProduct.count.sum())
                                    .from(orderProduct)
                                    .groupBy(orderProduct.product.id)
                                    .orderBy(OrderByNull.DEFAULT)
                                    .where(orderProduct.product.eq(product)), "count"
                    )))
            .from(product)
            .where(status,product.stock.gt(0))
            .fetch();
}

ꡬ맀 μˆœμ€ μ„œλΈŒ 쿼리둜 OrderProduct의 count값을 product_idλ₯Ό κΈ°μ€€μœΌλ‘œ GroupBy둜 κ°€μ Έμ™€μ„œ μ§„ν–‰

μ΅œμ’… μΏΌλ¦¬μ—μ„œ stock이 0이라면 μž¬κ³ κ°€ μ—†λ‹€λŠ” 뜻이기 λ•Œλ¬Έμ— 0 초과일 λ•Œλ§Œ 쑰회