클라이언트 앱에서 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에서는 다양한 플랫폼에서 사용할 수 있도록 여러 언어의 API client를 제공한다. ( Elasticsearch langauage clients )
API client를 사용하면 코드 몇 줄로 ES와 연결할 수 있고, 검색에 필요한 쿼리 빌드도 매우 쉽다.
아래 링크에 가이드와 예시 코드도 나와있으니 이후에 버전이 바뀌면 공식 문서 코드 참고하기! (이 글은 8.1 기준)
Spring Boot에서는 Java API Client를 사용해 Elasticsearch에 검색한다.
전체 코드는 아래에서 다루고, 먼저 Elasticsearch JAVA API client 주요 코드를 보자.
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'
}
// 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);
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();
}
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'
}
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이라는 데이터 전송 객체를 만들어서 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;
}