💻 [B2C 플랫폼 설계] 22편 - Elasticsearch 상품 검색 + 페이징 처리 구현하기
2025. 6. 4. 21:04

🎯 개요

지난 글에서는 Elasticsearch를 연동하여 상품 등록 시 색인 작업까지 구현했습니다.
이번 글은 그 확장 기능으로, 실제 사용자 검색 흐름에 맞춘 검색 API 구현과 페이징 처리를 중심으로 정리하였습니다.

현재는 수십 건의 상품만 테스트 중이지만, 실무에서는 수천~수만 건의 데이터 검색이 일상이므로,
정확한 검색과 함께 페이지 단위 응답 처리는 필수입니다.


✔️ 주요 기능 요약

기능 설명
키워드 검색 상품명(name) 또는 설명(description)에 키워드 포함 여부 검색
페이징 처리 page, size 파라미터를 통한 페이지 응답 구성
결과 정렬 Elasticsearch 기본 스코어(score) 기준 자동 정렬

⚙️ 예제 흐름 (핵심 로직 중심 설명)

실제 구현에서는 ProductResponse, SearchResponse 등의 DTO를 사용하여 계층 간 데이터를 전달하고 있습니다.
하지만 이 글에서는 전체 처리 흐름을 쉽게 파악하기 위해 DTO 관련 코드는 생략하고, 핵심 로직 중심으로 정리하였습니다.


1️⃣ Controller 계층에서 요청 처리

@GetMapping("/search")
public ResponseEntity<ApiResponse<?>> searchProducts(
        @RequestParam(required = false) String keyword,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size) {

    Page<ProductDocument> result = productSearchService.searchProducts(keyword, page, size);
    return ResponseEntity.ok(ApiResponse.ok(result));
}

 

  • 검색어(keyword)는 선택적으로 받습니다.
  • page와 size는 기본값을 통해 페이징 처리합니다.

 


2️⃣ Service 계층에서 검색 처리

 
public Page<ProductDocument> searchProducts(String keyword, int page, int size) {
    Pageable pageable = PageRequest.of(page, size);

    if (!StringUtils.hasText(keyword)) {
        return productSearchRepository.findAll(pageable);
    }

    return productSearchRepository.findByNameContainingOrDescriptionContaining(keyword, keyword, pageable);
}

 

  • 검색어가 비어있으면 findAll(pageable)로 전체 상품을 페이징 처리해 반환합니다.
  • 검색어가 있을 경우, name 또는 description에 포함되는 값을 기준으로 검색합니다.

 

3️⃣ Elasticsearch Repository 쿼리 메서드

 
Page<ProductDocument> findByNameContainingOrDescriptionContaining(String name, String description, Pageable pageable);
  • Spring Data Elasticsearch의 쿼리 메서드 기반으로 구현하였습니다.

📌 결과 예시

Request

GET /product/search?keyword=운동화&page=0&size=2
 

 

Response

{
  "success": true,
  "data": {
    "content": [
      {
        "id": 3,
        "name": "운동화 X",
        "description": "러닝용",
        "category": "shoes",
        "price": 80000
      },
      {
        "id": 6,
        "name": "운동화 Y",
        "description": "기능성 운동화",
        "category": "shoes",
        "price": 95000
      }
    ],
    "totalPages": 2,
    "totalElements": 4,
    "number": 0,
    "size": 2,
    "first": true,
    "last": false
  },
  "message": null
}

🤔 회고 및 팁

  • 단순 검색만으로는 사용자에게 만족스러운 경험을 줄 수 없습니다.
  • 페이징 처리를 통해 응답 용량을 제한하고, 클라이언트 UX 설계도 유연해집니다.
  • 이후에는 정렬 옵션, 필터링 조건(카테고리, 가격대 등)을 추가해 더 풍부한 검색 경험을 제공할 수 있도록 확장할 계획입니다.

📚 추가 자료