Spring Boot로 Elasticsearch 검색하는 API 서버 만들기

quokka·2022년 4월 23일
1

.

목록 보기
3/5

클라이언트 앱에서 Elasticsearch의 검색 결과를 가져오는 방법은 여러가지가 있다.

클라이언트 앱에서 URI 검색으로 직접 Elasticsearch에 검색할 수도 있고, 서버에서 Elasticsearch API Client를 사용해 Elasticsearch에서 원하는 값을 검색해올 수도 있다.

이 글에서는 Spring Boot로 Elasticsearch에 데이터를 검색하는 API 서버를 만들고, 클라이언트 앱에서는 retrofit으로 API 서버와 통신해 원하는 값을 받아오려고 한다.

프로젝트 생성

먼저 spring initializr로 API 서버를 개발할 프로젝트를 생성했다.

나는 Gradle Project, Java, 2.6.7, Jar, Java8으로 설정했는데, 본인이 원하는대로 바꿔도 상관없다. Dependencies도 나중에 gradle 파일이나 pom 파일에서 추가하면되니 Lombok, Spring Web 정도만 미리 추가했다.

Generate 버튼을 눌러 다운로드 받아서 압축을 풀고, IDE에서 프로젝트를 열면 준비 완료! (나는 Intellij를 사용한다.)

Elasticsearch Java Api Client

Elasticsearch에서는 다양한 플랫폼에서 사용할 수 있도록 여러 언어의 API client를 제공한다. ( Elasticsearch langauage clients )
API client를 사용하면 코드 몇 줄로 ES와 연결할 수 있고, 검색에 필요한 쿼리 빌드도 매우 쉽다.

아래 링크에 가이드와 예시 코드도 나와있으니 이후에 버전이 바뀌면 공식 문서 코드 참고하기! (이 글은 8.1 기준)

Spring Boot에서는 Java API Client를 사용해 Elasticsearch에 검색한다.

전체 코드는 아래에서 다루고, 먼저 Elasticsearch JAVA API client 주요 코드를 보자.

  1. Dependencies
    spring boot에서 사용할 것이니까 마지막 dependency까지 모두 포함한다.
dependencies {
    implementation 'co.elastic.clients:elasticsearch-java:8.1.2'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'

    // Needed only if you use the spring-dependency-management and spring-boot Gradle plugins
    implementation 'jakarta.json:jakarta.json-api:2.0.1' 
}
  1. 연결
// Create the low-level client
RestClient restClient = RestClient.builder(
		new HttpHost("localhost", 9200)).build();

// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
		restClient, new JacksonJsonpMapper());

// And create the API client
ElasticsearchClient client = new ElasticsearchClient(transport);
  1. 검색
    검색하는 쿼리는 kibana dev tool에서 작성하는 방식과 유사하다. 빌더 패턴으로 index, query 등 검색에 필요한 값을 작성한다.
    아래는 “Items”라는 인덱스에서 “_id” 필드가 “2”인 것을 검색하는 코드이다.
SearchResponse<Item> search = client.search(s -> s
				.index("Items")
				.query(q -> q
						.term(t -> t
								.field("_id")
								.value(v -> v.stringValue("2"))
						)),
		Item.class);
        
for (Hit<Item> hit: search.hits().hits()) {
	title = hit.source().getTitle();
	body = hit.source().getBody();
}

서버 코드

build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// Elasticsearch API Client
	implementation 'co.elastic.clients:elasticsearch-java:8.1.2'
	implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
	implementation 'jakarta.json:jakarta.json-api:2.0.1'

	implementation group: 'org.json', name: 'json', version: '20090211'
	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch
	implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-elasticsearch', version: '1.2.5.RELEASE'
}

ApiController.java

RestController를 사용해서 localhost:8080/item?id=2 이런식으로 서버에 요청하면 요청된 값을 검색해서 결과값을 클라이언트로 전송하도록 했다.

package com.example.ElasticsearchAPI;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.json.JSONException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.json.JSONObject;

import java.io.IOException;

@RestController
public class ApiController {
    // Create the low-level client
    RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)).build();

    // Create the transport with a Jackson mapper
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());

    // And create the API client
    ElasticsearchClient client = new ElasticsearchClient(transport);

    @GetMapping(value = "/item")
    public @ResponseBody
    ResponseEntity<String> search(@RequestParam(name = "id") String id) throws IOException, JSONException {

        String title = "";
        String body = "";

        SearchResponse<Item> search = client.search(s -> s
                        .index("Items")
                        .query(q -> q
                                .term(t -> t
                                        .field("_id")
                                        .value(v -> v.stringValue(id))
                                )),
                Item.class);

        for (Hit<Item> hit: search.hits().hits()) {
            title = hit.source().getTitle();
            body = hit.source().getBody();
        }

        JSONObject msg = new JSONObject();
        msg.put("title", title);
        msg.put("body", body);

        return new ResponseEntity<>(msg.toString(), HttpStatus.OK);
    }
}

Item.java

Item이라는 데이터 전송 객체를 만들어서 Item 단위로 데이터를 가져오도록 했다.

package com.example.ElasticsearchAPI;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;

@Setter
@Getter
@Document(indexName = "items")
public class Item {
    @Id
    private String title;
    private String body;
}

0개의 댓글