@RestController @RequestMapping("products") public class ProductController { ... @GetMapping("/search") public ResponseEntity<List<ProductDocument>> searchProducts( @RequestParam String query, @RequestParam(required = false) String category, @RequestParam(defaultValue = "0") double minPrice, @RequestParam(defaultValue = "1000000000") double maxPrice, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "5") int size ) { List<ProductDocument> products = productService.searchProducts(query, category, minPrice, maxPrice, page, size); return ResponseEntity.ok(products); } ... }
@Service public class ProductService { ... public List<ProductDocument> searchProducts( String query, String category, double minPrice, double maxPrice, int page, int size ) { // multi_match 쿼리 Query multiMatchQuery = MultiMatchQuery.of(m -> m .query(query) .fields("name^3", "description^1", "category^2") .fuzziness("AUTO") )._toQuery(); // term filter 쿼리 : 카테고리가 정확히 일치하는 것만 필터링 List<Query> filters = new ArrayList<>(); if (category != null && !category.isEmpty()) { Query categoryFilter = TermQuery.of(t -> t .field("category.raw") .value(category) )._toQuery(); filters.add(categoryFilter); } // range filter: 가격 범위 필터 Query priceRangeFilter = NumberRangeQuery.of(r -> r .field("price") .gte(minPrice) .lte(maxPrice) )._toRangeQuery()._toQuery(); filters.add(priceRangeFilter); // should: rating > 4.0 Query ratingShould = NumberRangeQuery.of(r -> r .field("rating") .gt(4.0) )._toRangeQuery()._toQuery(); // bool query 조합 Query boolQuery = BoolQuery.of(b -> b .must(multiMatchQuery) .filter(filters) .should(ratingShould) )._toQuery(); // Highlight Query 생성 HighlightParameters highlightParameters = HighlightParameters.builder() .withPreTags("<b>") .withPostTags("</b>") .build(); Highlight highlight = new Highlight(highlightParameters, List.of(new HighlightField("name"))); HighlightQuery highlightQuery = new HighlightQuery(highlight, ProductDocument.class); // NativeQuery 생성 NativeQuery nativeQuery = NativeQuery.builder() .withQuery(boolQuery) .withHighlightQuery(highlightQuery) .withPageable(PageRequest.of(page - 1, size)) .build(); // 쿼리 실행 SearchHits<ProductDocument> searchHits = this.elasticsearchOperations.search(nativeQuery, ProductDocument.class); return searchHits.getSearchHits().stream() .map(hit -> { ProductDocument productDocument = hit.getContent(); String highlightedName = hit.getHighlightField("name").get(0); productDocument.setName(highlightedName); return productDocument; }) .toList(); } ... }

DELETE /propducts
{ "name": "삼성 노트북 13인치", "description": "최신형 삼성 노트북입니다.", "price": 1200000, "rating": 4.6, "category": "전자제품" } { "name": "삼성 노트북 최신형 15인치", "description": "2025년 최신 15인치 노트북입니다.", "price": 2200000, "rating": 4.8, "category": "전자제품" } { "name": "삼성 스마트폰 A17", "description": "최신형 스마트폰, 휴대전화", "price": 1500000, "rating": 4.5, "category": "휴대폰" }

요구사항의 모든 내용을 다 테스트해보진 않고 몇 가지만 간단하게 테스트해보자.
삼성으로 검색해보기http://localhost:8080/products/search?query=삼성
http://localhost:8080/products/search?category=전자제품&query=삼성
http://localhost:8080/products/search?category=전자제품&minPrice=0&maxPrice=1500000&query=삼성
samsung으로 검색해보기http://localhost:8080/products/search?category=전자제품&minPrice=0&maxPrice=1500000&query=samsung