Backend · Infra

[Error Log] Java High Level REST Client 6.3

devhyen 2024. 7. 17. 10:06

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