본문 바로가기

프로젝트/booktalk(책 중고 거래 서비스)

QueryDSL product 조회 코드 구현(페이징 처리,카테고리처리, 조회 결과 정렬)

QueryDSL 이란?

정적 타입을 이용하여, SQL과 같은 쿼리를 코드 형태로 생성할 수 있도록 해주는 오픈소스 빌더 API

즉, SQL문을 직접 하드코딩 하는 것이 아닌, 코드 형태로 작성하는 것이 특징이다.

 

 

이 컨트롤러는 다양한 REST API 엔드포인트를 정의하고, 상품 관련 요청을 처리하여 클라이언트에게 응답합니다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/products")
public class ProductController {

    private final ProductService productService;

    // 새 상품 생성 엔드포인트
    @PostMapping
    public ResponseEntity<ProductCreateRes> createProduct(
        @AuthenticationPrincipal UserDetailsImpl userDetails, // 현재 사용자 정보
        @Valid @RequestPart("req") ProductCreateReq req, // 새 상품 생성 요청
        @RequestParam(value = "upload", required = false) List<MultipartFile> files // 선택적 파일 업로드
    ) throws IOException {
        // HTTP 상태코드와 생성된 상품 정보 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.createProduct(userDetails.getUser().getId(), req, files));
    }

    // 상품 업데이트 엔드포인트
    @PutMapping("/{productId}")
    public ResponseEntity<ProductUpdateRes> updateProduct(
        @AuthenticationPrincipal UserDetailsImpl userDetails, // 현재 사용자 정보
        @PathVariable Long productId, // 업데이트할 상품 ID
        @Valid @RequestPart("req") ProductUpdateReq req, // 업데이트 요청
        @RequestParam(value = "upload", required = false) List<MultipartFile> files // 선택적 파일 업로드
    ) throws IOException {
        // HTTP 상태코드와 업데이트된 상품 정보 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.updateProduct(userDetails.getUser().getId(), productId, req, files));
    }

    // 상품 삭제 엔드포인트
    @DeleteMapping("/{productId}")
    public ResponseEntity<ProductDeleteRes> deleteProduct(
        @AuthenticationPrincipal UserDetailsImpl userDetails, // 현재 사용자 정보
        @PathVariable Long productId // 삭제할 상품 ID
    ) {
        // HTTP 상태코드와 삭제된 상품 정보 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.deleteProduct(userDetails.getUser().getId(), productId));
    }

    // 단일 상품 조회 엔드포인트
    @GetMapping("/{productId}")
    public ResponseEntity<ProductGetRes> getProduct(
        @PathVariable Long productId // 조회할 상품 ID
    ) {
        // HTTP 상태코드와 조회된 상품 정보 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.getProduct(productId));
    }

    // 상품 리스트 조회 엔드포인트
    @GetMapping
    public ResponseEntity<Page<ProductListRes>> getProductList(
        @RequestParam("page") int page, // 페이지 번호
        @RequestParam("size") int size, // 페이지 크기
        @RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy, // 정렬 기준
        @RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc // 오름차순 여부
    ) {
        // HTTP 상태코드와 페이지네이션된 상품 리스트 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.getProductList(page - 1, size, sortBy, isAsc));
    }

    // 메인 화면 상품 리스트 조회 엔드포인트
    @GetMapping("/main")
    public ResponseEntity<List<ProductTopLikesListRes>> getProductListTopThree() {
        // HTTP 상태코드와 메인 화면에 출력할 Top 3 상품 리스트 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.getProductListByLikesTopThree());
    }

    // 상품 검색 리스트 조회 엔드포인트
    @GetMapping("/search")
    public ResponseEntity<Page<ProductSerachListRes>> getProductSearchList(
        @RequestParam("page") int page, // 페이지 번호
        @RequestParam("size") int size, // 페이지 크기
        @RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy, // 정렬 기준
        @RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc, // 오름차순 여부
        @RequestParam(value = "query") String search // 검색어
    ) {
        // HTTP 상태코드와 검색 결과를 페이지네이션하여 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.getProductSearchList(page - 1, size, sortBy, isAsc, search));
    }

    // 태그별 상품 리스트 조회 엔드포인트
    @GetMapping("/tag")
    public ResponseEntity<Page<ProductTagListRes>> getProductListByTag(
        @RequestParam("page") int page, // 페이지 번호
        @RequestParam("size") int size, // 페이지 크기
        @RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy, // 정렬 기준
        @RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc, // 오름차순 여부
        @RequestParam(value = "tag") String search // 태그
    ) {
        // HTTP 상태코드와 태그별 검색 결과를 페이지네이션하여 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.getProductSearchTagList(page - 1, size, sortBy, isAsc, search));
    }

    // 사용자별 상품 리스트 조회 엔드포인트
    @GetMapping ("/user/{userId}")
    public ResponseEntity<Page<UserProductListRes>> getUserProductList(
        @PathVariable Long userId, // 사용자 ID
        @RequestParam("page") int page, // 페이지 번호
        @RequestParam("size") int size, // 페이지 크기
        @RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy, // 정렬 기준
        @RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc // 오름차순 여부
    ) {
        // HTTP 상태코드와 사용자별 상품 리스트를 페이지네이션하여 응답
        return ResponseEntity.status(HttpStatus.OK)
            .body(productService.getUserProductList(userId,page - 1, size, sortBy, isAsc));
    }
}

 

 

product레퍼지토리에 ProductRepositoryCustom을 상속한다.이렇게 함으로써 ProductRepositoryProductRepositoryCustom의 메서드도 사용할 수 있게 됩니다. 필요에 따라 ProductRepositoryCustom에 추가한 사용자 지정 메서드를 ProductRepository에서 사용할 수 있습니다.

// 이 인터페이스는 데이터베이스에서 Product 엔티티와 상호작용하는 메서드를 정의합니다.
public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {

    // 삭제되지 않은 모든 제품을 페이지네이션하여 찾습니다.
    Page<Product> findAllByDeletedFalse(Pageable pageable);

    // 삭제되지 않은 상위 3개 제품을 좋아요 수를 기준으로 내림차순으로 찾습니다.
    List<Product> findTop3ByDeletedFalseOrderByProductLikeCntDesc();

    // 특정 사용자가 생성한 제품을 찾습니다.
    List<Product> findProductsByUserId(Long userId);

    // 제품의 ID로 제품을 찾는 기본 메서드로, 찾지 못하면 NotFoundProductException을 던집니다.
    default Product findProductByIdWithThrow(Long id) {
        return findById(id).orElseThrow(() ->
            new NotFoundProductException(ProductErrorCode.NOT_FOUND_PRODUCT));
    }

}

 

 

 

이 인터페이스는 사용자 지정 쿼리 메서드를 정의하며, 각 메서드는 페이지네이션과 추가적인 매개변수를 사용하여 제품 목록을 검색하거나 필터링합니다.

public interface ProductRepositoryCustom {

    // 페이지네이션과 검색어를 이용하여 제품 목록을 이름으로 검색하여 가져옵니다.
    Page<Product> getPostListByName(Pageable pageable, String search);

    // 페이지네이션과 태그를 이용하여 제품 목록을 태그로 필터링하여 가져옵니다.
    Page<Product> getProductListByTag(Pageable pageable, String tag);
}

 

 

 

이 클래스는 ProductRepositoryCustom 인터페이스의 메서드를 구현합니다. getPostListByName 메서드는 이름으로 제품 목록을 검색하고, getProductListByTag 메서드는 태그로 제품 목록을 필터링합니다. 필요에 따라 쿼리 생성과 페이지네이션을 수행하며, 결과를 반환합니다.

@Repository
@RequiredArgsConstructor
public class ProductRepositoryCustomImpl implements ProductRepositoryCustom {

    private final JPAQueryFactory jpaQueryFactory;
    QProduct product = QProduct.product;

    // 이름으로 제품 목록을 검색하여 페이지네이션하여 반환합니다.
    @Override
    public Page<Product> getPostListByName(Pageable pageable, String search) {

        // JPAQueryFactory를 사용하여 쿼리 생성
        JPAQuery<Product> query = jpaQueryFactory
            .selectFrom(product)
            .where(product.deleted.eq(false)) // deleted가 false인 제품만 검색
            .where(product.name.contains(search)); // 제품 이름에 검색어가 포함된 경우 검색

        // 정렬 적용
        if (pageable.getSort().isSorted()) {
            for (Sort.Order order : pageable.getSort()) {
                PathBuilder<Product> pathBuilder = new PathBuilder<>(Product.class,
                    product.getMetadata());
                // 정렬 방향에 따라 제품 이름으로 정렬
                query.orderBy(new OrderSpecifier<>(order.isAscending() ? Order.ASC : Order.DESC,
                    pathBuilder.get(order.getProperty(), Comparable.class)));
            }
        }

        // 쿼리 실행하여 페이지네이션된 결과를 가져옴
        List<Product> productList = query
            .offset(pageable.getOffset()) // 페이지네이션 시작 오프셋 설정
            .limit(pageable.getPageSize()) // 페이지 크기 설정
            .fetch();

        // 페이지 결과 반환
        return PageableExecutionUtils.getPage(productList, pageable,
            () -> query.fetchCount()); // 총 결과 수를 반환하는 함수 제공
    }

    // 태그로 제품 목록을 필터링하여 페이지네이션하여 반환합니다.
    @Override
    public Page<Product> getProductListByTag(Pageable pageable, String tag) {

        QProductCategory productCategory = QProductCategory.productCategory;

        // JPAQueryFactory를 사용하여 쿼리 생성
        JPAQuery<Product> query = jpaQueryFactory
            .selectFrom(product)
            .leftJoin(product.productCategoryList, productCategory).fetchJoin() // 제품 카테고리 정보를 조인
            .where(product.deleted.eq(false)) // deleted가 false인 제품만 검색
            .where(hasTag(tag)); // 특정 태그를 가진 제품 검색

        // 정렬 적용
        if (pageable.getSort().isSorted()) {
            for (Sort.Order order : pageable.getSort()) {
                PathBuilder<Product> pathBuilder = new PathBuilder<>(Product.class,
                    product.getMetadata());
                // 정렬 방향에 따라 제품 이름으로 정렬
                query.orderBy(new OrderSpecifier<>(order.isAscending() ? Order.ASC : Order.DESC,
                    pathBuilder.get(order.getProperty(), Comparable.class)));
            }
        }

        // 쿼리 실행하여 페이지네이션된 결과를 가져옴
        List<Product> productList = query
            .offset(pageable.getOffset()) // 페이지네이션 시작 오프셋 설정
            .limit(pageable.getPageSize()) // 페이지 크기 설정
            .fetch();


        // distinct()를 통해 중복 제거된 결과를 반환
        return PageableExecutionUtils.getPage(productList, pageable,
            () -> query.distinct().fetchCount()); // 총 결과 수를 반환하는 함수 제공
    }

    // 태그가 포함된 제품을 검색하는 조건을 반환합니다.
    private BooleanExpression hasTag(String tagName) {
        return product.productCategoryList.any()
            .category.name.eq(tagName); // 제품의 카테고리 중 태그 이름이 일치하는 경우
    }

}

 

 

 

@Service
@RequiredArgsConstructor
@Transactional
public class ProductService {

    // 필드 선언
    private final ProductRepository productRepository; // 제품 리포지토리
    private final UserRepository userRepository; // 사용자 리포지토리
    private final CategoryRepository categoryRepository; // 카테고리 리포지토리
    private final ProductCategoryRepository productCategoryRepository; // 제품 카테고리 매핑 리포지토리
    private final ImageFileService imageFileService; // 이미지 파일 서비스

    // 제품 생성 메서드
    public ProductCreateRes createProduct(Long userId, ProductCreateReq req,
        List<MultipartFile> files) throws IOException {

        // 사용자 조회
        User user = userRepository.findUserByIdWithThrow(userId);

        // 제품 생성
        Product product = Product.builder()
            .name(req.name())
            .price(req.price())
            .quantity(req.quantity())
            .region(req.region())
            .content(req.content())
            .user(user)
            .build();
        product = productRepository.save(product);

        // 카테고리 추가
        addCategory(req.categoryList(), product);

        List<ImageCreateRes> imageCreateResList;
        UserRes userRes = new UserRes(user.getId(), user.getNickname());
        // 파일이 존재하는 경우 이미지 생성 서비스 호출
        if (files != null) {
            imageCreateResList = imageFileService.createImage(userId,
                product.getId(), files);
        } else {
            // 파일이 없는 경우 예외 처리
            throw new NotFoundImageFileException(UserErrorCode.NOT_IMAGE_FILE);
        }

        // 제품 생성 응답 반환
        return new ProductCreateRes(product.getId(), product.getName(), product.getQuantity(),
            product.getPrice(), product.getRegion(), product.getFinished(), userRes,
            product.getContent(), req.categoryList(), imageCreateResList);
    }

    // 제품 업데이트 메서드
    public ProductUpdateRes updateProduct(Long userId, Long productId, ProductUpdateReq req,
        List<MultipartFile> files) throws IOException {

        // 사용자 및 제품 조회
        User user = userRepository.findUserByIdWithThrow(userId);
        Product product = productRepository.findProductByIdWithThrow(productId);

        // 사용자 권한 검증
        validateProductUser(user, product);
        List<ImageCreateRes> imageCreateResList;

        // 파일이 존재하고 비어 있지 않은 경우 이미지 업데이트 서비스 호출
        if(files != null && !files.isEmpty()) {
            imageCreateResList = imageFileService.updateImage(userId, productId, files);
        } else {
            // 파일이 없는 경우 이미지 리스트 조회 후 업데이트
            List<ImageListRes> imageList = imageFileService.getImages(productId);
            imageCreateResList = imageList.stream()
                    .map(image -> new ImageCreateRes(image.imagePathUrl()))
                    .collect(Collectors.toList());
        }
        
        // 제품 업데이트
        product.update(req);
        updateCategory(req.categoryList(), product);
        UserRes userRes = new UserRes(user.getId(), user.getNickname());

        // 제품 업데이트 응답 반환
        return new ProductUpdateRes(product.getId(), product.getName(), product.getQuantity(),
            product.getPrice(), product.getRegion(), product.getFinished(), userRes,
            product.getProductLikeCnt(), product.getContent(), req.categoryList(),
            imageCreateResList);
    }
    
    @param productId 조회할 상품의 ID
	@return 조회된 상품 정보를 담은 ProductGetRes 객체
	@throws ResourceNotFoundException 상품이 존재하지 않는 경우 발생
    
	public ProductGetRes getProduct(Long productId) {

		// 상품 정보 조회
		Product product = productRepository.findProductByIdWithThrow(productId); // 지정한 ID에 해당하는 상품을 찾고, 없으면 예외 발생

		// 판매자 정보 추출
		User user = product.getUser();
		UserRes userRes = new UserRes(user.getId(), user.getNickname()); // 판매자의 ID와 닉네임으로 판매자 정보 생성

		// 상품 카테고리 이름 추출
		List<String> categories = product.getProductCategoryList().stream()
		.map(productCategory -> productCategory.getCategory().getName()) // 각 카테고리의 이름만 추출
		.toList();

		// 상품 이미지 조회
		List<ImageListRes> imageListRes = imageFileService.getImages(productId); // 상품 ID에 해당하는 이미지 목록 조회

		// 조회된 상품 정보를 담은 ProductGetRes 객체 생성 및 반환
		return new ProductGetRes(
		product.getId(),product.getName(),product.getPrice(),product.getQuantity(),
		userRes,product.getRegion(),categories,product.getProductLikeCnt(),product.getContent(),
		product.getFinished(),imageListRes
		);
	}
    
    상품 목록 조회 함수
	@param page 조회할 페이지 번호
	@param size 페이지당 조회할 상품 개수
	@param sortBy 정렬 기준 필드
	@param isAsc 오름차순 여부 (true: 오름차순, false: 내림차순)
	@return 조회된 상품 목록을 담은 Page 객체

	@Transactional(readOnly = true) // 읽기 전용 트랜잭션으로 처리
	public Page<ProductListRes> getProductList(int page, int size, String sortBy, boolean isAsc) {

		// 정렬 방향 설정
		Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
		Sort sort = Sort.by(direction, sortBy); // 정렬 기준과 방향으로 정렬 객체 생성
		Pageable pageable = PageRequest.of(page, size, sort); // 페이지 정보와 정렬 객체를 페이징 객체에 담음

		// 상품 목록 조회 (삭제되지 않은 상품만 조회)
		Page<Product> productList = productRepository.findAllByDeletedFalse(pageable);

		// 조회된 상품 정보를 ProductListRes 객체로 변환하여 반환
		return productList.map(product -> {
		List<ImageListRes> imageListRes = imageFileService.getImages(product.getId()); // 상품 이미지 조회
		List<String> categories = product.getProductCategoryList().stream()
		.map(productCategory -> productCategory.getCategory().getName()) // 카테고리 이름 추출
		.toList();
		ImageListRes imageGetRes = imageListRes.isEmpty() ? null : imageListRes.get(0); // 첫 번째 이미지만 사용

 		return new ProductListRes(product.getId(),product.getName(),product.getPrice(),
         product.getQuantity(),product.getProductLikeCnt(),categories,product.getRegion(),
         imageGetRes);
		});
	}
    
    @Transactional(readOnly = true)
    public List<ProductTopLikesListRes> getProductListByLikesTopThree() {
        // 상품 좋아요 수 기준 상위 3개 상품 목록을 가져옵니다.
        List<Product> productList = productRepository.findTop3ByDeletedFalseOrderByProductLikeCntDesc();

        // 상품 목록을 이용하여 응답용 객체로 매핑합니다.
        return productList.stream()
            .map(product -> {
                // 상품 이미지 목록을 가져옵니다.
                List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());

                // 상품 카테고리 목록을 가져와서 카테고리 이름으로 매핑합니다.
                List<String> categories = product.getProductCategoryList().stream()
                    .map(productCategory -> productCategory.getCategory().getName())
                    .toList();

                // 상품 이미지가 없을 경우에 대비하여 처리합니다.
                ImageListRes imageGetRes = imageListRes.isEmpty() ? new ImageListRes(null) : imageListRes.get(0);

                // 응답용 객체를 생성하여 반환합니다.
                return new ProductTopLikesListRes(product.getId(), product.getName(),
                    product.getPrice(), product.getQuantity(), product.getProductLikeCnt(), categories,
                    product.getRegion(), imageGetRes);
            })
            .toList();
    }

    @Transactional(readOnly = true)
    public Page<ProductSerachListRes> getProductSearchList(int page, int size, String sortBy,
        Boolean isAsc, String search) {
        // 페이지네이션 처리를 위한 정보를 설정합니다.
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);

        // 검색어를 이용하여 상품 목록을 가져옵니다.
        Page<Product> productList = productRepository.getPostListByName(pageable, search);

        // 상품 목록을 이용하여 응답용 객체로 매핑합니다.
        return productList.map(product -> {
            // 상품 이미지 목록을 가져옵니다.
            List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());

            // 상품 카테고리 목록을 가져와서 카테고리 이름으로 매핑합니다.
            List<String> categories = product.getProductCategoryList().stream()
                .map(productCategory -> productCategory.getCategory().getName())
                .toList();

            // 상품 이미지가 없을 경우에 대비하여 처리합니다.
            ImageListRes imageGetRes = imageListRes.isEmpty() ? new ImageListRes(null) : imageListRes.get(0);

            // 응답용 객체를 생성하여 반환합니다.
            return new ProductSerachListRes(product.getId(), product.getName(),
                product.getPrice(), product.getQuantity(), product.getProductLikeCnt(), categories,
                product.getRegion(), imageGetRes);
        });
    }

    @Transactional(readOnly = true)
    public Page<ProductTagListRes> getProductSearchTagList(int page, int size, String sortBy,
        Boolean isAsc, String tag) {
        // 페이지네이션 처리를 위한 정보를 설정합니다.
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);

        // 태그를 이용하여 상품 목록을 가져옵니다.
        Page<Product> productList = productRepository.getProductListByTag(pageable, tag);

        // 상품 목록을 이용하여 응답용 객체로 매핑합니다.
        return productList.map(product -> {
            // 상품 이미지 목록을 가져옵니다.
            List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());

            // 상품 카테고리 목록을 가져와서 카테고리 이름으로 매핑합니다.
            List<String> categories = product.getProductCategoryList().stream()
                .map(productCategory -> productCategory.getCategory().getName())
                .toList();

            // 상품 이미지가 없을 경우에 대비하여 처리합니다.
            ImageListRes imageGetRes = imageListRes.isEmpty() ? new ImageListRes(null) : imageListRes.get(0);

            // 응답용 객체를 생성하여 반환합니다.
            return new ProductTagListRes(product.getId(), product.getName(), product.getPrice(),
                product.getQuantity(), product.getProductLikeCnt(), categories,
                product.getRegion(), imageGetRes);
        });
    }

    @Transactional(readOnly = true)
    public Page<UserProductListRes> getUserProductList(Long userId, int page, int size, String sortBy, boolean isAsc) {
        // 페이지네이션 처리를 위한 정보를 설정합니다.
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);

        // 사용자의 상품 목록을 가져옵니다.
        Page<Product> productList = productRepository.findProductsByUserIdAndDeletedFalse(userId, pageable);

        // 상품 목록을 이용하여 응답용 객체로 매핑합니다.
        return productList.map(product -> {
            // 상품 이미지 목록을 가져옵니다.
            List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());

            // 상품 카테고리 목록을 가져와서 카테고리 이름으로 매핑합니다.
            List<String> categories = product.getProductCategoryList().stream()
                .map(productCategory -> productCategory.getCategory().getName())
                .toList();

            // 응답용 객체를 생성하여 반환합니다.
            return new UserProductListRes(product.getId(), product.getName(), product.getPrice(),
                product.getQuantity(), product.getProductLikeCnt(), categories,
                product.getRegion(), imageListRes.isEmpty() ? null : imageListRes.get(0));
        });
    }

    public ProductDeleteRes deleteProduct(Long userId, Long productId) {
        // 사용자와 상품을 가져옵니다.
        User user = userRepository.findUserByIdWithThrow(userId);
        Product product = productRepository.findProductByIdWithThrow(productId);

        // 상품 삭제 권한을 검증합니다.
        validateProductUser(user, product);

        // 상품 이미지를 삭제합니다.
        imageFileService.deleteImage(userId, productId);

        // 상품을 삭제 처리합니다.
        product.deleted();

        return new ProductDeleteRes("삭제가 완료되었습니다.");
    }

    // 사용자와 상품의 권한을 검증합니다.
    private void validateProductUser(User user, Product product) {
        if (!user.getId().equals(product.getUser().getId())
            && !user.getRole().equals(UserRoleType.ADMIN)) {
            throw new NotPermissionAuthority(ProductErrorCode.NOT_PERMISSION_AUTHORITHY);
        }
    }

    // 카테고리 목록을 조회하여 반환합니다.
    private List<Category> findCategoryList(List<String> categoryList) {
        List<Category> categories = categoryRepository.findByNameIn(categoryList);

        // 카테고리가 존재하지 않으면 예외를 발생시킵니다.
        if (categoryList.size() != categories.size()) {
            throw new NotFoundCategoryException(CategoryErrorCode.NOT_FOUND_CATEGORY);
        }

        return categories;
    }

    // 상품에 카테고리를 추가합니다.
    private void addCategory(List<String> categoryList, Product product) {
        List<Category> categories = findCategoryList(categoryList);

        // 상품에 카테고리를 추가합니다.
        categories.forEach(category -> {
            ProductCategory productCategory = ProductCategory.builder()
                .category(category)
                .product(product)
                .build();

            productCategoryRepository.save(productCategory);
            product.addProductCategory(productCategory);
        });
    }

    // 상품에서 카테고리를 제거합니다.
    private void removeCategory(List<String> categoryList, Product product) {
        List<ProductCategory> productCategoryList = productCategoryRepository.findAllByProductAndCategory_NameIn(
            product, categoryList);

        // 상품에서 카테고리를 제거합니다.
        productCategoryList.forEach(productCategory -> productCategoryRepository.delete(productCategory));
    }

    // 상품의 카테고리를 업데이트합니다.
    private void updateCategory(List<String> reqCategoryList, Product product) {
        List<String> currentCategories = product.getProductCategoryList()
            .stream()
            .map(ProductCategory::getCategory)
            .map(Category::getName).toList();

        // 제거 가능한 카테고리를 찾습니다.
        List<String> removeableCategoryList = currentCategories.stream()
            .filter(category -> !reqCategoryList.contains(category))
            .toList();

        // 추가 가능한 카테고리를 찾습니다.
        List<String> addableCategoryList = reqCategoryList.stream()
            .filter(category -> !currentCategories.contains(category))
            .toList();

        // 카테고리를 제거합니다.
        removeCategory(removeableCategoryList, product);

        // 카테고리를 추가합니다.
        addCategory(addableCategoryList, product);
    }
}

'프로젝트 > booktalk(책 중고 거래 서비스)' 카테고리의 다른 글

1/16  (0) 2024.02.26
jwt 토큰 인증 인가  (0) 2024.02.02
product  (1) 2024.01.25
image파일 업로드 할때 리사이징 적용하기  (0) 2024.01.24
[SpringBoot] AWS S3로 이미지 업로드하기  (1) 2024.01.23