ES에서 Search 쿼리를 연습해보던 중
기존 프로젝트에 Java Low Level REST Client 6.3이 셋팅 되어 있었지만,
LowLevel에서는 Query DSL을 직접 JSON형식으로 작성하여 요청해야 하기 때무에 불편함이 있었다.
Java High Level REST Client 6.3 를 셋팅 해보고 싶다는 생각이 들어, 기존 프로젝트에서 branch를 하나 따서 High Level을 셋팅 해봤다.
High Level vs Low Level
Java High Level REST Client | Java Low Level REST Client |
REST 요청을 추상화하여 간단한 인터페이스를 제공하여 간편한 사용이 가능하다. | ElasticSearch의 REST API와 직접적으로 상호작용하여 요청을 전송하고 응답처리를 한다. |
내부적인 REST API 호출 | ElasticSearch의 모든 REST 엔드포인트와 상호작용 할 수 있다. 유연성이 있으며, 정밀한 제어가 필요할 때 유용하다. |
Java 객체로 ES문서를 처리하고, 문서 검색 및 색인 기능 제공 | HTTP 클라이언트 라이브러리인 Apache HttpClient기반으로 구현되어 있다. |
보통 High Level Client에서도, 대부분 기능을 지원하며 간단하고 사용하기 쉬운 인터페이스를 제공하기 때문에 HighLevel을 사용하는 것이 좋지만, 특정 요구사항이나 ES의 최신 기능을 활용해야 할 때는 Low Level Client 을 사용하는 것이 좋다. 또한, High Level에서 지원하지 않는 특정 API를 사용해야 할 때 좋다.
Initialize
1. maven dependency 추가
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.3.2</version>
</dependency>
2. config
기존 코드는 Bean등록을 하지 않고, 싱글톤 패턴을 이용하여 직접 인스턴스를 등록 하는 방식으로 구현되어 있었다.
나는 간편하게 Spring의 Bean을 등록하여initialize을 진행했다.
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.http.client.config.RequestConfig;
import org.elasticsearch.client.RestClientBuilder;
@Configuration
public class ESConfig {
private String esHost =
private int esPort =
private String esSchema =
final private int DEFAULT_CONNECTION_TIMEOUT =
final private int DEFAULT_SOCKET_TIMEOUT =
final private int DEFAULT_REQUEST_TIMEOUT =
@Bean
public RestClientBuilder lowLevelClient() {
return RestClient.builder(new HttpHost(esHost, esPort, esSchema))
.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
@Override
public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {
return requestConfigBuilder.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT)
.setSocketTimeout(DEFAULT_SOCKET_TIMEOUT).setConnectionRequestTimeout(DEFAULT_REQUEST_TIMEOUT);
}
});
}
@Bean
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(this.lowLevelClient());
}
}
3. service
...
@Autowired
private ESConfig esConfig;
...
RestHighLevelClient client = esConfig.restHighLevelClient();
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
...
이런식으로 간단하게 matchAllQuery를 보낼 수 있다.
Query를 작성한 예
HashMap<String,Object> resultHashMap = new HashMap<>();
RestHighLevelClient client = esConfig.restHighLevelClient();
SearchRequest searchRequest = new SearchRequest("인덱스");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// "_source" 필드 설정
String[] includeFields = {
"GroupName",
"TypeName",
};
searchSourceBuilder.fetchSource(includeFields, null);
// "size" 설정
searchSourceBuilder.size(9999);
// "sort" 설정
searchSourceBuilder.sort("LastSessionTime", SortOrder.DESC);
searchSourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.ASC));
// "query" 설정
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// "must_not" 설정
boolQuery.mustNot(QueryBuilders.termQuery("tags", "web"));
// "must" 설정
boolQuery.must(QueryBuilders.termQuery("GroupID", "E"));
// "@timestamp" 범위 설정
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("@timestamp")
.from("now/d")
.to("now+1d/d")
.format("strict_date_optional_time");
boolQuery.must(rangeQuery);
// boolQuery를 searchSourceBuilder에 설정
searchSourceBuilder.query(boolQuery);
// request queryDSL 결과 출력
System.out.println(searchSourceBuilder.toString());
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
// 파싱
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
resultHashMap.put("result",new ArrayList<>());
int idx = 0;
for(SearchHit hit : searchHits) {
idx ++;
Map<String,Object> map = hit.getSourceAsMap();
map.put("idx",idx);
ArrayList<Map<String, Object>> resultList = (ArrayList<Map<String, Object>>) resultHashMap.get("result");
resultList.add(map);
}
Error
1. 의존성 충돌 문제
java.lang.ClassNotFoundException: com.fasterxml.jackson.core.filter.TokenFilter
- 문제: ClassNotFoundException이 발생한 이유는 Spring Cloud Starter Eureka가 Jackson의 2.4.3 버전을 사용하는 반면, Spring Cloud Config Server가 Jackson의 2.8.7 버전을 사용하기 때문입니다. 이는 클래스가 찾을 수 없다는 예외를 일으키는 주요 원인입니다.
- 해결 방법: 이 문제를 해결하기 위해서는 두 라이브러리가 사용하는 Jackson 라이브러리의 버전을 통일하거나, 더 높은 버전으로 업그레이드하는 것이 좋습니다. 예를 들어, 둘 다 Jackson의 최신 버전인 2.8.8을 사용하도록 설정할 수 있습니다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
<scope>compile</scope>
</dependency>
기존 버전 2.5.3에서 2.8.8으로 올려줬다.
그랬더니 새로운 에러가 발생한다.
java.lang.NoSuchMethodError: com.fasterxml.jackson.core.JsonGenerator.writeStartObject(Ljava/lang/Object;)V
- 문제: jackson-databind 버전 2.8.3을 사용하는데, 이 버전은 jackson-core 버전 2.8.0 이상을 필요로 합니다. 그러나 여기서 발생한 문제는 jackson-core 버전이 수동으로 설정되어 있어서, 필요한 것보다 오래된 버전이라는 점 입니다.
- 해결 방법: 라이브러리 버전 업데이트가 필요합니다. jackson-core의 버전을 최신으로 업데이트하여 jackson-databind 버전과 일치하도록 만드세요. 예를 들어, jackson-databind 2.8.3을 사용하는 경우, jackson-core도 적어도 2.8.0 이상이어야 합니다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
jackson-databind도 2.8.3으로 버전을 올려줬더니 문제가 해결되었다.
'Backend · Infra' 카테고리의 다른 글
[Jenkins] Linux에 jenkins 설치 (0) | 2024.07.26 |
---|---|
[ElasticSearch] Paging (0) | 2024.07.22 |
[Spring] DI : Dependency Injection (0) | 2024.07.15 |
[Java] 추상 클래스와 인터페이스의 차이 (0) | 2024.07.12 |
Error 와 Exception (0) | 2024.07.11 |