diff --git a/src/main/java/com/DecodEat/domain/products/repository/ProductSpecification.java b/src/main/java/com/DecodEat/domain/products/repository/ProductSpecification.java index 8fa0577..9e01776 100644 --- a/src/main/java/com/DecodEat/domain/products/repository/ProductSpecification.java +++ b/src/main/java/com/DecodEat/domain/products/repository/ProductSpecification.java @@ -8,6 +8,8 @@ import com.DecodEat.domain.products.entity.RawMaterial.RawMaterialCategory; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; import org.springframework.data.jpa.domain.Specification; import java.util.ArrayList; @@ -39,4 +41,29 @@ public static Specification hasRawMaterialCategories(List hasAllRawMaterialCategories(List categories) { + return (root, query, criteriaBuilder) -> { + + // 1. 서브쿼리를 생성합니다. 이 서브쿼리는 조건에 맞는 Product의 ID(Long)를 반환할 것입니다. + Subquery subquery = query.subquery(Long.class); + Root subRoot = subquery.from(Product.class); // 서브쿼리에서 Product 테이블을 기준으로 삼습니다. + + // 2. 서브쿼리 내에서 필요한 조인을 수행합니다. + Join subProductRawMaterialJoin = subRoot.join("ingredients"); + Join subRawMaterialJoin = subProductRawMaterialJoin.join("rawMaterial"); + + // 3. 서브쿼리의 핵심 로직: '상품별로 카테고리를 묶고(GROUP BY), 그 개수가 일치하는지 확인(HAVING)' + subquery.select(subRoot.get("id")) // 조건에 맞는 Product의 ID를 선택 + .where(subRawMaterialJoin.get("category").in(categories)) // 먼저 카테고리가 검색 대상에 포함되는 제품만 필터링 + .groupBy(subRoot.get("id")) // Product ID 별로 그룹화 + .having(criteriaBuilder.equal( + criteriaBuilder.countDistinct(subRawMaterialJoin.get("category")), // 각 제품의 유니크한 카테고리 개수를 세고 + categories.size() // 그 개수가 우리가 찾는 카테고리 리스트의 전체 개수와 같은지 확인 + )); + + // 4. 메인 쿼리: Product의 ID가 위 서브쿼리 결과 목록에 포함(IN)되는 제품만 최종 선택합니다. + return root.get("id").in(subquery); + }; + } } diff --git a/src/main/java/com/DecodEat/domain/products/service/ProductService.java b/src/main/java/com/DecodEat/domain/products/service/ProductService.java index cf6b9d7..248e5ed 100644 --- a/src/main/java/com/DecodEat/domain/products/service/ProductService.java +++ b/src/main/java/com/DecodEat/domain/products/service/ProductService.java @@ -142,7 +142,7 @@ public PageResponseDto searchProducts(S } if (categories != null && !categories.isEmpty()) { - spec = spec.and(ProductSpecification.hasRawMaterialCategories(categories)); + spec = spec.and(ProductSpecification.hasAllRawMaterialCategories(categories)); } // Specification과 Pageable을 사용하여 데이터 조회