Spring Boot 환경에서 Elasticsearch 고도화 클라이언트 연동 및 활용

Elasticsearch 와의 통합 준비

Spring Boot 애플리케이션 내에서 Elasticsearch 기능을 사용하기 위해서는 적절한 의존성 도입과 클라이언트 객체의 초기화가 필요합니다. 본 가이드에서는 REST 기반의 고수준 클라이언트를 사용하여 인덱스 관리부터 문서 수준의 CRUD 작업까지 구현하는 과정을 설명합니다.

1. 빌드 도구 설정

프로젝트 초기화 시 기본적으로 제공하는 Starter 의존성 외에 Elasticsearch 라이브러리의 버전을 명시적으로 관리해야 합니다. 공식 서버 버전과 라이브러리 버전이 일치하지 않으면 호환성 오류가 발생할 수 있으므로 pom.xml을 수정하여 안정된 버전을 고정하는 것이 좋습니다.

<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.10.0</version>
</dependency>

2. 인프라스트럭처 구성

애플리케이션 컨텍스트에서 사용할 Elasticsearch 인스턴스를 연결하기 위한 빈(Bean) 을 정의합니다. 싱글톤으로 관리되도록 구성 클래스를 작성하며, 향후 클러스터 확장 시 호스트 정보를 유연하게 추가할 수 있도록 구조화합니다.

@Configuration
public class EsConnectionConfig {

    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 9200;
    private static final String PROTOCOL = "http";

    @Bean
    public RestHighLevelClient esClient() {
        return new RestHighLevelClient(
            RestClient.builder(new HttpHost(SERVER_HOST, SERVER_PORT, PROTOCOL))
        );
    }
}

3. 데이터 모델 정의

색인될 데이터를 표현하기 위해 내부 상태만 담는 간단한 도메인 객체를 생성합니다. Lombok 라이브러리를 활용하면 getter/setter 및 생성자 코드를 간결하게 유지할 수 있습니다.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberRecord {
    private String fullName;
    private int yearsOld;
    private String residence;
}

4. 서비스 계층 구현

비즈니스 로직을 테스트 코드와 분리하여 재사용성을 높입니다. 인덱스 생성, 삭제, 문서 저장 등 주요 작업을 메서드로 추출한 서비스 클래스를 작성합니다.

@Service
public class MemberSearchService {

    private final RestHighLevelClient client;
    private final ObjectMapper objectMapper;
    private static final String INDEX_ID = "member_db";

    public MemberSearchService(RestHighLevelClient client) {
        this.client = client;
        this.objectMapper = new ObjectMapper();
    }

    public void initializeIndex() throws IOException {
        IndicesExistsRequest checkReq = new IndicesExistsRequest(INDEX_ID);
        boolean exists = client.indices().exists(checkReq, RequestOptions.DEFAULT);
        if (!exists) {
            System.out.println("Index 생성 중...");
            // 실제로는 mapping 정의를 포함할 수도 있음
        }
    }

    public void storeMember(MemberRecord data) throws IOException {
        IndexRequest req = new IndexRequest(INDEX_ID);
        String jsonData = objectMapper.writeValueAsString(data);
        req.source(jsonData, XContentType.JSON);
        
        IndexResponse res = client.index(req, RequestOptions.DEFAULT);
        System.out.println("저장 상태: " + res.status());
    }

    public Optional<MemberRecord> findMember(String memberId) throws IOException {
        GetRequest getReq = new GetRequest(INDEX_ID, memberId);
        GetResponse getRes = client.get(getReq, RequestOptions.DEFAULT);
        
        if (getRes.isSourceEmpty()) {
            return Optional.empty();
        }
        
        return Optional.of(objectMapper.readValue(getRes.getSourceAsString(), MemberRecord.class));
    }

    public void deleteMember(String memberId) throws IOException {
        DeleteRequest delReq = new DeleteRequest(INDEX_ID, memberId);
        client.delete(delReq, RequestOptions.DEFAULT);
    }

    public List<MemberRecord> getAllMembers() throws IOException {
        SearchRequest searchReq = new SearchRequest(INDEX_ID);
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchAllQuery());
        searchReq.source(builder);

        SearchResponse response = client.search(searchReq, RequestOptions.DEFAULT);
        return Arrays.stream(response.getHits().getHits())
            .map(hit -> objectMapper.readValue(hit.getSourceAsString(), MemberRecord.class))
            .collect(Collectors.toList());
    }
}

5. 기능 검증

구현된 서비스 메서드가 정상 작동하는지 단위 테스트로 확인합니다. 스프링 부트의 테스트 어노테이션을 활용하여 컨텍스트를 로드하고 실제 API 호출을 수행합니다.

@SpringBootTest
class IntegrationTestSuite {

    @Autowired
    private MemberSearchService searchService;

    @Test
    void validateCrudProcess() throws IOException {
        MemberRecord p1 = new MemberRecord("김철수", 30, "서울시");
        MemberRecord p2 = new MemberRecord("박영희", 28, "부산시");

        // 1. 저장
        searchService.storeMember(p1);
        
        // 2. 조회
        var result = searchService.findMember("1");
        assert(result.isPresent());

        // 3. 전체 검색
        List<MemberRecord> list = searchService.getAllMembers();
        assert(!list.isEmpty());

        // 4. 삭제
        searchService.deleteMember("1");
        
        System.out.println("통합 테스트 완료");
    }
}

태그: SpringBoot elasticsearch java rest-client JSON

6월 20일 02:55에 게시됨