Category Enum -> Entity

김창모·2023년 9월 19일
0

Project

목록 보기
13/13

Intro

초기에 카테고리 변경사항이 없을것으로 생각하고 Enum 으로 처리했었다.
하지만 관리자기능 등을 새로 구현하려고 보니 카테고리의 추가 제거 수정 등이 필요해보였다.
기존의 Enum 으로 처리했을때는 이것이 불가능하여 새로 Category Entity 를 만들고 모두 변경해주었다.

Product Entity 변경

package com.hello.foreverpet.domain.entity;

import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import com.hello.foreverpet.auditing.BaseTimeEntity;
import com.hello.foreverpet.domain.dto.request.UpdateProductRequest;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Product extends BaseTimeEntity {


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productId;
    @NotNull
    private String productName;
    @NotNull
    @Column(length = 500)
    private String productDescription;

//    @NotNull
//    @Enumerated(EnumType.STRING)
//    private Categories categories;


    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    private Category category;

    @NotNull
    private Long productPrice;

    private Long numberOfSold;

    private String productImage;
    @NotNull
    private String brandName;

    @ManyToOne(fetch = FetchType.LAZY)
    private Wish wish;

//    @Builder
//    public Product(String productName, String productDescription, Categories categories, Long productPrice,
//                   String productImage, String brandName) {
//        this.productName = productName;
//        this.productDescription = productDescription;
//        this.categories = categories;
//        this.productPrice = productPrice;
//        this.numberOfSold = 0L;
//        this.productImage = productImage;
//        this.brandName = brandName;
//    }

    @Builder
    public Product(String productName, String productDescription, Category category, Long productPrice,
                   String productImage, String brandName) {
        this.productName = productName;
        this.productDescription = productDescription;
        this.category = category;
        this.productPrice = productPrice;
        this.numberOfSold = 0L;
        this.productImage = productImage;
        this.brandName = brandName;
    }

    public Product updateProductByUpdateRequest(UpdateProductRequest updateProductRequest) {
        this.productName = updateProductRequest.getProductName();
        this.productDescription = updateProductRequest.getProductDescription();
        this.category = new Category();
        this.productPrice = updateProductRequest.getProductPrice();
        this.productImage = updateProductRequest.getProductImage();
        this.brandName = updateProductRequest.getBrandName();
        return this;
    }


    public void setWish(Wish wish) {
        this.wish = wish;
        if (wish != null && !wish.getProducts().contains(this)) {
            wish.getProducts().add(this);
        }

    }

    // 주문시 판매수량 증가
    public void soldProducts() {
        this.numberOfSold++;
    }

    // 주문 취소시 판매수량 감소
    public void cancelOrder() {
        if (numberOfSold > 1) {
            this.numberOfSold--;
        }

    }
}

Category 관련 메서드추가

    @Operation(summary = "카테고리 추가", description = "상품 카테고리를 추가합니다.")
    @PostMapping("/admin/category/add")
    public void newCategory(@RequestParam String category) {
        categoryService.addCategory(category);
    }

    @Operation(summary = "카테고리 수정", description = "상품 카테고리를 수정합니다.")
    @PostMapping("/admin/category/update/{id}")
    public void updateCategory(@PathVariable Long id, @RequestParam String category) {
        categoryService.updateCategory(id, category);
    }

    @Operation(summary = "카테고리 삭제", description = "상품 카테고리를 삭제합니다.")
    @PostMapping("/admin/category/delete")
    public void deleteCategory(@RequestParam String category) {
        categoryService.deleteCategory(category);
    }

Init 메서드 수정

    public void categoryInit() {

        String[] categories = {"SNACK", "BITA", "FOOD"};

        for (String category : categories) {
            Category byName = categoryService.findByName(category);

            if (byName == null) {
                categoryService.addCategory(category);
            }
        }

    }

Request , Response 변경

NewProducRequest

기존 존재하던 세개의 카테고리는 InitMethod 를 변경하며 추가해주었다.
Entity 로 존재하니 새로운 상품을 추가할때 String 으로 그 값을 입력받아 findByName 으로 찾도록 했고
프론트에서 상품 카테고리를 입력할때 모든 카테고리 리스트를 보내주어 존재하는 카테고리만 입력받게 작업하고
한번더 백엔드에서도 존재하는 카테고리 인지 여부를 체크해주면 좋을거같다.

package com.hello.foreverpet.domain.dto.request;

import com.hello.foreverpet.domain.entity.Category;
import com.hello.foreverpet.domain.entity.Product;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;

@Data
@Schema(description = "상품 등록 요청DTO")
public class NewProductRequest {
    @Schema(description = "등록할 상품 이름",example = "상품1")
    @NotNull
    private String productName;
    @Schema(description = "등록할 상품 설명",example = "이 상품을 구매하세요")
    @NotNull
    private String productDescription;
    @Schema(description = "등록할 상품 카테고리",defaultValue = "SNACK",allowableValues = {"SNACK","BITA","FOOD"})
    @NotNull
    private String categories;
    @Schema(description = "등록할 상품 가격",example = "5000")
    @NotNull
    private Long productPrice;
    @Schema(description = "등록할 상품 이미지 주소",example = "http://forevepet/image/1")
    @NotNull
    private String productImage;
    @Schema(description = "등록할 상품 브랜드",example = "애플")
    @NotNull
    private String brandName;

    public Product toEntity(Category category) {
        return Product.builder().productName(this.productName)
                .productDescription(this.productDescription)
                .category(category)
                .productPrice(this.productPrice)
                .productImage(this.productImage)
                .brandName(this.brandName)
                .build();
    }

    @Builder
    public NewProductRequest(String productName, String productDescription, String categories, Long productPrice,
                             String productImage,String brandName) {
        this.productName = productName;
        this.productDescription = productDescription;
        this.categories = categories;
        this.productPrice = productPrice;
        this.productImage = productImage;
        this.brandName = brandName;
    }
}

ProductResponse

Response 는 String 으로 Category 의 Name 을 조회하여 보내주도록 하였다.

package com.hello.foreverpet.domain.dto.response;

import com.hello.foreverpet.domain.entity.Product;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Data;

@Data
@Schema(description = "상품 응답")
public class ProductResponse {
    private Long id;
    private String productName;
    private String productDescription;
    private String categoryName;
    private Long productPrice;
    private Long numberOfSold;
    private String productImage;
    private String brandName;
    private LocalDateTime createdDate;
    private LocalDateTime modifiedDate;

    public ProductResponse(Product product) {
        this.id = product.getProductId();
        this.productName = product.getProductName();
        this.productDescription = product.getProductDescription();
        this.categoryName = product.getCategory().getName();
        this.productPrice = product.getProductPrice();
        this.numberOfSold = product.getNumberOfSold();
        this.productImage = product.getProductImage();
        this.brandName = product.getBrandName();
        this.createdDate = product.getCreateDate();
        this.modifiedDate = product.getModifiedDate();
    }


}

ProductService

package com.hello.foreverpet.service;

import com.hello.foreverpet.domain.dto.request.NewProductRequest;
import com.hello.foreverpet.domain.dto.request.UpdateProductRequest;
import com.hello.foreverpet.domain.dto.response.LoginUserProductResponse;
import com.hello.foreverpet.domain.dto.response.ProductResponse;
import com.hello.foreverpet.domain.entity.CartProduct;
import com.hello.foreverpet.domain.entity.Category;
import com.hello.foreverpet.domain.entity.Product;
import com.hello.foreverpet.domain.entity.UserInfo;
import com.hello.foreverpet.domain.exception.AlreadyExistProductException;
import com.hello.foreverpet.domain.exception.CategoryNotFoundException;
import com.hello.foreverpet.domain.exception.ProductNotFoundException;
import com.hello.foreverpet.domain.exception.UserNotFoundException;
import com.hello.foreverpet.handler.ErrorCode;
import com.hello.foreverpet.jwt.TokenProvider;
import com.hello.foreverpet.repository.CategoryRepository;
import com.hello.foreverpet.repository.CustomProductRepository;
import com.hello.foreverpet.repository.ProductJpaRepository;
import com.hello.foreverpet.repository.UserInfoJpaRepository;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductJpaRepository productJpaRepository;
    private final CustomProductRepository customProductRepository;
    private final UserInfoJpaRepository userInfoJpaRepository;
    private final TokenProvider tokenProvider;
    private final CategoryRepository categoryRepository;

    @Transactional
    public ProductResponse createProduct(NewProductRequest newProductRequest) {
        if (productJpaRepository.findByProductName(newProductRequest.getProductName()).isPresent()) {
            throw new AlreadyExistProductException(ErrorCode.ALREADY_EXIST_PRODUCT_EXCEPTION);
        }

        Category category = categoryRepository.findByName(newProductRequest.getCategories())
                .orElseThrow(() -> new CategoryNotFoundException(ErrorCode.CATEGORY_NOT_FOUND));

        Product newProduct = newProductRequest.toEntity(category);

        productJpaRepository.save(newProduct);

        return new ProductResponse(newProduct);
    }

    public List<ProductResponse> getAllProducts() {
        return productJpaRepository.findAll().stream().map(ProductResponse::new).toList();
    }

    @Transactional
    public ProductResponse updateProduct(Long id, UpdateProductRequest updateProductRequest) {
        return productJpaRepository.findById(id)
                .map(product -> {
                    Product updateProduct = product.updateProductByUpdateRequest(updateProductRequest);
                    return new ProductResponse(updateProduct);
                })
                .orElseThrow(() -> new ProductNotFoundException(ErrorCode.PRODUCT_FOUND_ERROR));
    }


    public Long deleteProduct(Long id) {
        return productJpaRepository.findById(id)
                .map(product -> {
                    productJpaRepository.delete(product);
                    return id;
                })
                .orElseThrow(() -> new ProductNotFoundException(ErrorCode.PRODUCT_FOUND_ERROR));
    }

    public ProductResponse findProductById(Long id) {
        return productJpaRepository.findById(id).map(ProductResponse::new).orElseThrow(IllegalArgumentException::new);
    }

    public List<ProductResponse> searchProduct(String search) {
        return customProductRepository.findProductBySearch(search).stream()
                .map(ProductResponse::new).toList();
    }

    public List<ProductResponse> orderBySold() {
        return customProductRepository.findProductOrderBySold().stream()
                .map(ProductResponse::new).toList();
    }

    public List<ProductResponse> orderByNew() {
        return customProductRepository.findProductOrderByNew().stream()
                .map(ProductResponse::new).toList();
    }

    public List<ProductResponse> productByCategories(String categoryName) {
        return customProductRepository.findProductByCategories(categoryName).stream()
                .map(ProductResponse::new).toList();
    }

    public List<LoginUserProductResponse> loginUserGetAllProducts(HttpServletRequest httpServletRequest) {

        UserInfo userInfo = getUserInfo(httpServletRequest);

        List<Product> cart = getCartProducts(userInfo.getUserId()).stream().map(CartProduct::getProduct)
                .toList();

        List<Product> wish = getWishProducts(userInfo.getUserId());

        return productJpaRepository.findAll().stream()
                .map(product -> {
                    LoginUserProductResponse loginUserProductResponse = new LoginUserProductResponse(product);

                    if (cart.contains(product)) {
                        loginUserProductResponse.reverseInCart();
                    }

                    if (wish.contains(product)) {
                        loginUserProductResponse.reverseInWish();
                    }
                    return loginUserProductResponse;
                })
                .toList();
    }

    private List<CartProduct> getCartProducts(Long userId) {
        
        return userInfoJpaRepository.findById(userId)
                .map(userInfo -> userInfo.getCart().getCartProducts())
                .orElse(Collections.emptyList());
    }

    private List<Product> getWishProducts(Long userId) {

        return userInfoJpaRepository.findById(userId)
                .map(userInfo -> userInfo.getWish().getProducts())
                .orElse(Collections.emptyList());
    }

    private UserInfo getUserInfo(HttpServletRequest httpServletRequest) {
        String token = httpServletRequest.getHeader("Authorization");

        return userInfoJpaRepository.findById(
                Long.valueOf(tokenProvider.getAuthentication(token).getName())).orElseThrow(() -> new UserNotFoundException(ErrorCode.USER_NOT_FOUND));
    }
}

Exception

CategoryNotFound

package com.hello.foreverpet.domain.exception;

import com.hello.foreverpet.handler.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class CategoryNotFoundException extends IllegalArgumentException{
    private final ErrorCode errorCode;
}

AlreadyExistCategoryException

package com.hello.foreverpet.domain.exception;

import com.hello.foreverpet.handler.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class AlreadyExistCategoryException extends IllegalArgumentException{
    private final ErrorCode errorCode;
}

ExceptionHandler

@ExceptionHandler(CategoryNotFoundException.class)
    public ResponseEntity<ErrorResponse> categoryNotFoundExceptionHandler(CategoryNotFoundException e) {
        ErrorResponse response = new ErrorResponse(ErrorCode.CATEGORY_NOT_FOUND);

        return new ResponseEntity<>(response, HttpStatusCode.valueOf(e.getErrorCode().getStatus()));
    }

    @ExceptionHandler(AlreadyExistCategoryException.class)
    public ResponseEntity<ErrorResponse> alreadyExistCategoryExceptionHandler(AlreadyExistCategoryException e) {
        
        ErrorResponse response = new ErrorResponse(ErrorCode.CATEGORY_ALREADY_EXIST);

        return new ResponseEntity<>(response, HttpStatusCode.valueOf(e.getErrorCode().getStatus()));
    }

So

카테고리 관련 처리는 모두 잘 되었다.
하지만 한가지 문제가 남아있었는데
카테고리를 삭제할 경우 해당 카테고리의 상품들을 어떻게 처리해야할까 ??
기존 카테고리가 삭제되었을 경우 상품에 포함되어 있는 카테고리도 사라지게 된다.
그래서 생각난 해결방법은 카테고리를 삭제하지 않고 활성 / 비활성 으로 나누어서 관리하면 어떨까 였다.
어짜피 각 상품에 대한 카테고리가 변경된다면 관리자가 수정해주어야 하기때문에 이방법으로 우선 수정해두어야겠다.

0개의 댓글