자바 언어를 위해 제공되는 클라이언트에는 두 가지 종류가 있다. 내부적으로 HTTP REST API를 사용해 통신하는 방식과 네티(Netty)모듈을 이용해 네이티브 클라이언트를 통해 통신하는 방식이다.

 

REST Client Transport Client(Netty)
  • Java High Level REST Client라고 불린다.
  • HTTP방식을 이용해 엘라스틱서치와 통신한다.
  • 내부적으로는 HttpClient 모듈을 사용한다.
  • HTTPS 사용이 가능하다.
  • Java Client라고도 불린다.
  • 초기부터 제공되던 클라이언트 방식으로, 상대적으로 빠른 속도를 보장한다.
  • 소켓을 이용해 엘라스틱서치와 통신한다.
  • 내부적으로는 Netty모듈을 사용한다.

 

초기버전의 엘라스틱서치에서는 소켓을 이용하는 Transport 클라이언트만 제공됬다. 일종의 엘라스틱서치 노드와 비슷한 방식으로 동작하기 때문에 속도는 빠르지만 엘라스틱서치가 버전업될 때마다 제공되는 기능이나 API의 명세에 따라 클래스나 메서드가 바뀌는 문제점이 있었다. 이러한 문제점을 해결하기 위해서 새로운 모듈인 REST 클라이언트가 도입됐다.

 

아직까지 Transport방식이 많이 사용되기는 하지만 엘라스틱서치 7.0부터는 해당 방식이 deprecated됬으므로, REST 클라이언트 방식을 사용하는 것이 나을 듯 싶지만, 최근 버전에서는 어떻게 될지 모르겠지만, REST 클라이언트에 지원이 안되는 Transport방식의 기능도 있으므로 용도에 따라 적절히 혼합하여 사용해야할 듯하다. 하지만 이번 포스팅에서는 High-Level Rest Client만 다룰 예정이다.

 

 

Elasticsearch connection

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                    new HttpHost("host",9200,"http")
                    ,new HttpHost("host2",9200,"http")
                )
        );
        
        /*
         * 비지니스로직
         */
        
        /*
         * 사용후 꼭 close
         */
        client.close();
cs

 

High-Level Rest Client는 위와 같이 RestHighLevelClient 객체로 엘라스틱서치 노드와 연결한다. 그리고 매개변수로 빌더패턴 객체가 들어간다. 엘라스틱서치는 위와같이 여러개의 노드 연결이 가능하다. 중요한것은 반드시 사용후 close해주어야 한다는 것이다. 혹은 아래와 같이 자바1.7에 나온 기능인 try 괄호 안에 넣어주어서 auto close하는 방법을 사용해도 될듯하다.(try-resource-with) 따로 close를 호출해도 않아도 되니, 해당 기능은 Closeable을 구현하고 있는 클래스라면 습관적으로 써주는 것이 좋을 듯하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
        try(RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                    new HttpHost("host",9200,"http")
                    ,new HttpHost("host2",9200,"http")
                )
        );){
            /*
             * 비지니스로직
             */
        }
cs

 

우선 앞에서 대충 이야기는 했지만 현재 최신버전은 모르지만 최소한 6버전대에선 transport에서 지원하는 모든 기능을 high-level rest client에서 모두 지원하지는 않는다. 하지만 사용에 크게 무리없는 대부분의 기능은 지원하니 오늘은 그러한 기능 위주로 다루어볼 것이다.

그리고 편의상 모든 요청은 GET 방식으로 만들었다. 실 서비스 목적으로 만드려면 용도에 맞는 METHOD 방식으로 만드는 것이 좋을 듯싶다.

 

 

인덱스 생성 API

직접 postman tool이나 curl로 요청했었던 인덱스 생성 API를 클라이언트를 통하여 생성해볼 것이다. 생성할 인덱스는 커스텀 한글 형태소분석기 설정을 포함하며 매핑정보를 가진 인덱스 생성 API이다. 우선 이전에 설정하였던 것을 JSON으로 표현해본 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
PUT -> http://localhost:9200/sample-index
{
    "settings":{
        "number_of_shards": 5,
        "number_of_replicas": 1,
        "analysis":{
            "tokenizer":{
                "korean_nori_tokenizer":{
                    "type":"nori_tokenizer",
                    "decompound_mode":"mixed",
                    "user_dictionary":"user_dictionary.txt"
                }
            },
            "analyzer":{
                "nori_analyzer":{
                    "type":"custom",
                    "tokenizer":"korean_nori_tokenizer",
                    "filter":[
                        "nori_posfilter",
                        "nori_readingform",
                        "synonym_filtering",
                        "stop_filtering"
                    ]
                }
            },
            "filter":{
                "nori_posfilter":{
                    "type":"nori_part_of_speech",
                    "stoptags":[
                        "E","IC","J","MAG","MM","NA","NR","SC",
                        "SE","SF","SH","SL","SN","SP","SSC","SSO",
                        "SY","UNA","UNKNOWN","VA","VCN","VCP","VSV",
                        "VV","VX","XPN","XR","XSA","XSN","XSV"
                    ]
                },
                "synonym_filtering":{
                    "type":"synonym"
                    ,"synonyms_path":"synonymsFilter.txt"
                },
                "stop_filtering":{
                    "type":"stop"
                    ,"stopwords_path":"stopFilter.txt"
                }
            }
        }
    },"mappings":{
        "_doc":{
            "properties":{
                "answer":{
                    "type":"keyword"
                }
                ,"question":{
                    "type":"text"
                    ,"analyzer":"nori_analyzer"
                }
            }
        }
    }
}
cs

 

간략히 설명하자면 우선 샤드와 복제본 수를 설정에 포함시켰고 다음에 한글 형태소 분석기 및 여러 필터를 적용한 분석기 설정을 포함하였다. 그리고 최종적으로 해당 분석기를 사용하는 매핑필드를 포함한 매핑설정을 포함시킨 인덱스 생성 요청이다. 다음은 이것을 클라이언트를 이용하여 설정한 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@RestController
@RequestMapping("/es/index")
public class IndexAPIController {
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
 
    @RequestMapping("/create")
    public Object createIndex() {
        
        boolean acknowledged = false;
        
        try(
                RestHighLevelClient client = createConnection()
        ){
            //index name
            String indexName = "sample-korean-index";
            //type name
            String typeName = "_doc";
            
            //settings
            XContentBuilder settingsBuilder = XContentFactory.jsonBuilder()
                    .startObject()
                        .field("number_of_shards",5)
                        .field("number_of_replicas",1)
                        
                        .startObject("analysis")
                            .startObject("tokenizer")
                                .startObject("sample-nori-tokenizer")
                                    .field("type","nori_tokenizer")
                                    .field("decompound_mode","mixed")
                                    .field("user_dictionary","user_dictionary.txt")
                                .endObject()
                            .endObject()
                            
                            .startObject("analyzer")
                                .startObject("sample-nori-analyzer")
                                    .field("type","custom")
                                    .field("tokenizer","sample-nori-tokenizer")
                                    .array("filter",new String[]{
                                        "sample-nori-posfilter",
                                        "nori_readingform",
                                        "sample-synonym-filter",
                                        "sample-stop-filter"
                                        }
                                    )
                                .endObject()
                            .endObject()
                            
                            .startObject("filter")
                                .startObject("sample-nori-posfilter")
                                    .field("type","nori_part_of_speech")
                                    .array("stoptaags",new String[] {
                                            "E","IC","J","MAG","MM","NA","NR","SC",
                                            "SE","SF","SH","SL","SN","SP","SSC","SSO",
                                            "SY","UNA","UNKNOWN","VA","VCN","VCP","VSV",
                                            "VV","VX","XPN","XR","XSA","XSN","XSV"
                                        }
                                    )
                                .endObject()
                                
                                .startObject("sample-synonym-filter")
                                    .field("type","synonym")
                                    .field("synonyms_path","synonymsFilter.txt")
                                .endObject()
                                
                                .startObject("sample-stop-filter")
                                    .field("type","stop")
                                    .field("stopwords_path","stopFilter.txt")
                                .endObject()
                            .endObject()
                        .endObject()
                    .endObject();
            
            //mapping info
            XContentBuilder indexBuilder = XContentFactory.jsonBuilder()
            .startObject()
                .startObject(typeName)
                    .startObject("properties")
                        .startObject("question")
                            .field("type","text")
                            .field("analyzer","sample-nori-analyzer")
                        .endObject()
                        .startObject("answer")
                            .field("type","keyword")
                        .endObject()
                    .endObject()
                .endObject()
            .endObject();
            
            //인덱스생성 요청 객체
            CreateIndexRequest request = new CreateIndexRequest(indexName);
            //세팅 정보
            request.settings(settingsBuilder);
            //매핑 정보
            request.mapping(typeName, indexBuilder);
            
            //별칭설정
            String aliasName = "chatbotInstance";
            request.alias(new Alias(aliasName));
            
            //인덱스생성
            CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
            
            acknowledged = response.isAcknowledged();
            
        }catch (Exception e) {
            e.printStackTrace();
            return "인덱스 생성에 실패하였습니다. - catch";
        }
        
        
        return acknowledged == true ? "인덱스가 생성되었습니다.":"인덱스생성에 실패하였습니다.";
    }
}
 
cs

 

이미 생성된 인덱스명이 존재해서 각종 이름만 변경된 것을 제외하면 동일한 설정값을 갖는다. 그리고 마지막에 인덱스 별칭을 주기 위하여 alias를 추가하였다. XContentBuilder가 우리가 JSON으로 표현할 설정 정보를 세팅하는 객체라고 보면 된다. 빌더패턴을 마치 JSON과 비슷한 느낌으로 사용하고 있으므로 설정에 어려운 부분은 없다고 생각이 든다. 마지막으로 CreateIndexRequest 객체를 이용하여 인덱스 생성 정보를 담은 요청객체를 만들고 해당 객체에 settings정보와 mapping정보 그리고 alias설정등을 넣어주고 마지막으로 CreateIndexResponse 객체로 요청의 결과를 받아온다. 처음에도 강조하였지만 중요한 것은 RestHighLevelClient을 닫아주는 것이다. 여기서는 try-resource-with 구문을 사용하여 자동으로 닫아주었다.

 

 

인덱스 삭제 API

 

1
DELETE -> http://localhost:9200/sample-korean-index
cs

 

위와 같이 인덱스 삭제요청을 클라이언트를 이용하면 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RestController
@RequestMapping("/es/index")
public class IndexAPIController {
    
    @RequestMapping("/delete")
    public Object delete() {
        
        boolean acknowledged = false;
        
        try(
                RestHighLevelClient client = createConnection()
        ){
            
            //index name
            String indexName = "sample-korean-index";
            
            //인덱스 삭제 요청 객체
            DeleteIndexRequest request = new DeleteIndexRequest(indexName);
            
            DeleteIndexResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
            
            acknowledged = response.isAcknowledged();
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return "인덱스 삭제에 실패하였습니다. - catch";
        }
        
        return acknowledged == true ? "인덱스 삭제가 완료되었습니다.":"인덱스 삭제에 실패하였습니다.";
    }
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
}
 
cs

 

인덱스 open/close

운영중인 인덱스의 설정 정보를 변경하려면 다음과 같은 프로세스를 거쳐야한다.

인덱스 close -> 설정변경 요청 -> 인덱스 open 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
POST -> http://localhost:9200/sample-korean-index/_close
POST -> http://localhost:9200/sample-korean-index/_open
 
@RestController
@RequestMapping("/es/index")
public class IndexAPIController {
    
    @RequestMapping("/open")
    public Object open() {
        
        boolean acknowledged = false;
        
        try(
                RestHighLevelClient client = createConnection();
        ){
            //index name
            String indexName = "sample-korean-index";
            
            //인덱스 open
            OpenIndexRequest request = new OpenIndexRequest(indexName);
            
            OpenIndexResponse response = client.indices().open(request, RequestOptions.DEFAULT);
            
            acknowledged = response.isAcknowledged();
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return "인덱스 open에 실패하였습니다. - catch";
        }
        
        return acknowledged == true ? "인덱스를 open 하였습니다.":"인덱스 open에 성공하였습니다.";
    }
    
    @RequestMapping("/close")
    public Object close() {
        boolean acknowledged = false;
        
        try(
                RestHighLevelClient client = createConnection();
        ){
            //index name
            String indexName = "sample-korean-index";
            
            //인덱스 close
            CloseIndexRequest request = new CloseIndexRequest(indexName);
            
            CloseIndexResponse response = client.indices().close(request, RequestOptions.DEFAULT);
            
            acknowledged = response.isAcknowledged();
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return "인덱스 close에 실패하였습니다. - catch";
        }
        
        return acknowledged == true ? "인덱스를 close 하였습니다.":"인덱스 close에 성공하였습니다.";
    }
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
}
 
cs

 

직접 엘라스틱서치에 요청을 보낼때는 맨위 두개의 요청이다. 해당 요청들을 클라이언트를 이용하여 요청하면 아래 소스코드와 같아진다.

 

 

문서 생성

문서 색인 요청이다. 색인 요청에는 인덱스명, 타입명, 문서 ID가 필요하다. 필자가 검색엔진을 이용하여 개발을 하였을 때는 문서 ID는 따로 RDB에서 시퀀스로 관리하였고 DB에서 다음 ID 값을 불러와 문서를 생성하였었다. 예제는 해당 내용이 포함되어 있지 않지만 나중에 활용하면 좋을 듯 하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
POST -> http://localhost:9200/sample-korean-index/_doc/2
 
{
    "question":"사용자 발화 예상문입니다.",
    "answer":"사용자에게 보여질 답변 혹은 의도명입니다."
}
 
 
<Client Code>
 
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서추가
     */
    @RequestMapping("/create")
    public Object create() {
        
        
        try(RestHighLevelClient client = createConnection();){
            
            //인덱스명 대신 별칭으로 색인한다.
            String indexName = "sample-korean-index";
            //타입명
            String typeName = "_doc";
            
            //문서 ID(대게 RDB에 문서 키값정도는 가져와서 저장한다. 만약 자동생성을 하게 된다면 엘라스틱서치는 UUID로 ID를 생성)
            String docId = "DOC_1";
            
            //문서 색인
            IndexRequest request = new IndexRequest(indexName,typeName,docId);
            
            request.source(
                XContentFactory.jsonBuilder()
                    .startObject()
                        .field("question","사용자 발화 예상문입니다.")
                        .field("answer","사용자에게 보여질 답변 혹은 의도명입니다.")
                    .endObject()
            );
            
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
            
            return response.status() ;
            
        }catch (ElasticsearchException e) {
            // TODO: handle exception
            e.printStackTrace();
            if(e.status().equals(RestStatus.CONFLICT)) {
                return "동일한 DOC_ID 문서가 존재합니다.";
            }
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return "문서 생성에 실패하였습니다.";
        }
        
        return null;
    }
}
 
 
cs

 

만약 응답결과로 "CREATED"가 오면 정상적으로 문서색인이 완료된 것이다. 예외처리의 CONFLICT는 문서 아이디가 중복된 것이 존재할 경우에 예외 발생을 시키기위한 처리이다.

 

 

문서조회

문서의 ID값을 이용해 조회하는 예제이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
GET -> http://localhost:9200/chatbotInstance/_doc/DOC_1
 
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서 조회
     */
    @RequestMapping("/search")
    public Object search() {
        
        //인덱스 별칭
        String aliasName = "chatbotInstance";
        //문서 타입
        String typeName ="_doc";
        //문서 ID
        String docId = "DOC_1";
        
        //문서조회 요청
        GetRequest request = new GetRequest(aliasName,typeName,docId);
        
        //문서조회 결과
        GetResponse response = null;
        try(RestHighLevelClient client = createConnection();){
            
            response = client.get(request, RequestOptions.DEFAULT);
            
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return "문서조회에 실패하였습니다.";
        }
        Map<String, Object> sourceAsMap = null;
        if(response.isExists()) {
            long version = response.getVersion();
            sourceAsMap = response.getSourceAsMap();
        }
        
        return gson.toJson(sourceAsMap);
    }
}
 
cs

 

문서존재여부

문서가 존재하는 지 여부를 알기위한 exist query이다. 결과는 true & false 이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서 존재 여부, exist
     */
    @RequestMapping("/exist")
    public Object exist() {
        
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        String docId = "DOC_1";
        
        GetRequest request = new GetRequest(aliasName,typeName,docId);
        boolean exist = false;
        try(RestHighLevelClient client = createConnection();){
            
            exist = client.exists(request, RequestOptions.DEFAULT);
            
        }catch (Exception e) {
            /*
             * 예외처리
             */
        }
        
        return exist;
    }
}
 
cs

 

문서 삭제

문서삭제 요청이다. 결과값으로 "DELETED"가 나온다면 성공적으로 삭제된 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서삭제
     */
    @RequestMapping("/delete")
    public Object delete() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        String docId = "DOC_1";
        
        DeleteRequest request = new DeleteRequest(aliasName,typeName,docId);
        DeleteResponse response = null;
        try(RestHighLevelClient client = createConnection();){
            
            response = client.delete(request, RequestOptions.DEFAULT);
            
        }catch (Exception e) {
            /*
             * 예외처리
             */
        }
        
        return response.getResult();
    }
}
 
cs

 

문서 업데이트

문서 업데이트 요청은 여러가지 종류가 존재한다. 스크립트를 이용한 업데이트, Upsert, 부분 문서 수정 등 요구사항에 맞는 업데이트 기능을 사용하면 될듯하다.

 

문서 업데이트 - script

스크립트를 이용하여 문서를 수정하는 예제이다. 쿼리를 이용하여 매치된 문서에 대한 일괄수정도 가능하고 예제처럼 특정 문서 ID의 문서만 수정가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서 수정 - script
     */
    @RequestMapping("/update-1")
    public Object update_1() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        String docId = "DOC_1";
        
        UpdateRequest request = new UpdateRequest(aliasName, typeName, docId);
        Map<String, Object> parameters = Collections.singletonMap("string"" - update");
        
        Script inline = new Script(ScriptType.INLINE,"painless","ctx._source.answer += params.string",parameters);
        
        request.script(inline);
        
        UpdateResponse response = null;
        
        try(RestHighLevelClient client = createConnection();){
            
            response = client.update(request, RequestOptions.DEFAULT);
            
        }catch (ElasticsearchException e) {
            e.printStackTrace();
            if(e.status().equals(RestStatus.NOT_FOUND)) {
                return "수정할 문서가 존재하지 않습니다.";
            }
        }catch (Exception e) {
            /*
             * 기타 예외처리
             */
        }
        
        return response.getResult();
    }
}
 
cs

 

문서 업데이트 - 부분 내용 수정

문서의 필드값 일부분을 수정하는 예제이다. 이 기능을 이용하면 기존 문서를 검색해 새로 생성한 문서로 덮어쓴다. 결과적으로 변경이 있는 칼럼만 변경되고 나머지는 그대로 유지된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서 수정 - 부분문서수정
     */
    @RequestMapping("/update-2")
    public Object update_2() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        String docId = "DOC_1";
        
        
        
        UpdateRequest request = null;
        
        UpdateResponse response = null;
        
        try(RestHighLevelClient client = createConnection();){
            XContentBuilder builder = XContentFactory.jsonBuilder()
                    .startObject()
                        .field("answer","수정된 답변입니다.")
                    .endObject();
            
            request = new UpdateRequest(aliasName, typeName, docId).doc(builder);
            
            response = client.update(request, RequestOptions.DEFAULT);
            
        }catch (ElasticsearchException e) {
            e.printStackTrace();
            if(e.status().equals(RestStatus.NOT_FOUND)) {
                return "수정할 문서가 존재하지 않습니다.";
            }
        }catch (Exception e) {
            /*
             * 기타 예외처리
             */
        }
        
        return response.getResult();
    }
}
 
cs

 

문서 업데이트 - upsert

만약 문서가 존재하면 update, 존재하지 않을 시 insert하는 요청이다. 만약 문서가 존재하지 않으면 IndexRequest에 담긴 Builder에 담긴 문서가 생성되고 문서가 존재한다면 UpsertRequest에 담긴 Builder 내용으로 수정된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    /*
     * 문서 수정 - upsert
     */
    @RequestMapping("/update-3")
    public Object update_3() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        String docId = "DOC_3";
        
        
        IndexRequest index = null;
        UpdateRequest request = null;
        UpdateResponse response = null;
        
        try(RestHighLevelClient client = createConnection();){
            index = new IndexRequest(aliasName,typeName,docId);
            index.source(XContentFactory.jsonBuilder()
                    .startObject()
                        .field("question","주문할게요")
                        .field("answer","어떤걸 주문하시게요?")
                    .endObject()
            );
            XContentBuilder builder = XContentFactory.jsonBuilder()
                    .startObject()
                        .field("answer","어떤걸 주문하시게요?")
                    .endObject();
            
            request = new UpdateRequest(aliasName, typeName, docId).doc(builder).upsert(index);
            
            response = client.update(request, RequestOptions.DEFAULT);
            
        }catch (ElasticsearchException e) {
            e.printStackTrace();
            if(e.status().equals(RestStatus.NOT_FOUND)) {
                return "수정할 문서가 존재하지 않습니다.";
            }
        }catch (Exception e) {
            /*
             * 기타 예외처리
             */
        }
        
        return response.getResult();
    }
}
 
cs

 

Bulk API

여러건의 문서를 Create,Update,Delete 할 수 있는 요청이다. 만약 여러개의 문서를 반복문으로 하나씩 요청하고 있다면 해당 기능을 이용하면 깔끔하게 하나의 요청으로 완료될 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@RestController
@RequestMapping("/es/doc")
public class DocAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    /*
     * Bulk API
     */
    @RequestMapping("/bulk")
    public Object bulk() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        
        BulkRequest request = new BulkRequest();
        BulkResponse response = null;
        
        try(RestHighLevelClient client = createConnection();){
            
            request.add(new IndexRequest(aliasName,typeName,"DOC_4")
                    .source(XContentFactory.jsonBuilder()
                            .startObject()
                                .field("question","벌크 질문 - add")
                                .field("answer","벌크 답변 - add")
                            .endObject()
                    )
            );
            
            request.add(new IndexRequest(aliasName,typeName,"DOC_5")
                    .source(XContentFactory.jsonBuilder()
                            .startObject()
                                .field("question","벌크 질문 - add2")
                                .field("answer","벌크 답변 - add2")
                            .endObject()
                    )
            );
            
            request.add(new UpdateRequest(aliasName,typeName,"DOC_4")
                    .doc(XContentFactory.jsonBuilder()
                            .startObject()
                                .field("question","벌크 질문 - add2")
                                .field("answer","벌크 답변 - add2")
                            .endObject()
                    )
            );
            
            request.add(new DeleteRequest(aliasName,typeName,"DOC_5"));
            
            response = client.bulk(request, RequestOptions.DEFAULT);
            
        }catch (Exception e) {
            /*
             * 예외처리
             */
            e.printStackTrace();
        }
        
        List<DocWriteResponse> results = new ArrayList<>();
        
        for(BulkItemResponse bulkItemResponse : response) {
            
            DocWriteResponse itemResponse = bulkItemResponse.getResponse();
            
            if(bulkItemResponse.getOpType().equals(DocWriteRequest.OpType.INDEX)) {
                IndexResponse indexResponse = (IndexResponse)itemResponse;
                results.add(indexResponse);
            }else if(bulkItemResponse.getOpType().equals(DocWriteRequest.OpType.UPDATE)) {
                UpdateResponse updateResponse = (UpdateResponse)itemResponse;
                results.add(updateResponse);
            }else if(bulkItemResponse.getOpType().equals(DocWriteRequest.OpType.DELETE)) {
                DeleteResponse deleteResponse = (DeleteResponse)itemResponse;
                results.add(deleteResponse);
            }
        }
        
        List<Object> resultList = results.stream().map(i->i.getResult()).collect(Collectors.toList());
        
        return Arrays.toString(resultList.toArray());
    }
}
 
cs

 

 

검색(Search) API

검색 API는 SearchRequest 클래스를 이용해 검색 요청을 할 수 있다. 쿼리에는 Tranport 클라이언트에서 사용하던 Query DSL 클래스를 동일하게 사용할 수 있다.

 

match-all Query

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@RestController
@RequestMapping("/es/search")
public class SearchAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    @RequestMapping("/match-all")
    public String matchAll(){
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
            
        }catch (Exception e) {
            /*
             * 예외처리
             */
            e.printStackTrace();
        }
        
        return gson.toJson(resultMap); 
    }
    
    private class Answer{
        private String question;
        private String answer;
        
        public String getQuestion() {
            return question;
        }
        public void setQuestion(String question) {
            this.question = question;
        }
        public String getAnswer() {
            return answer;
        }
        public void setAnswer(String answer) {
            this.answer = answer;
        }
    }
}
 
cs

 

Full Text Query API - Match Query

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@RestController
@RequestMapping("/es/search")
public class SearchAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    @RequestMapping("/match")
    public String match(){
        String aliasName = "chatbotInstance";
        String fieldName = "question";
        String typeName = "_doc";
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery(fieldName, "질문"));
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
            
        }catch (Exception e) {
            /*
             * 예외처리
             */
            e.printStackTrace();
        }
        
        return gson.toJson(resultMap); 
    }
    
    private class Answer{
        private String question;
        private String answer;
        
        public String getQuestion() {
            return question;
        }
        public void setQuestion(String question) {
            this.question = question;
        }
        public String getAnswer() {
            return answer;
        }
        public void setAnswer(String answer) {
            this.answer = answer;
        }
    }
}
 
cs

 

common term query

많이 검색되는 단어와 적게 검색되는 단어 중 어떤 단어가 더 중요한지를 판단해서 검색 스코어링을 변경하는 알고리즘을 가지고 있는 쿼리이다. 예를 들어 "the red fox"라는 문장이 있을 경우 the,red,fox라는 텀으로 문장을 자르고 검색할때 각각을 검색해서 최종적으로 스코어를 계산한다. the라는 단어는 너무 흔한 단어로서 맨 나중에 검색되도록 가중치를 조절할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@RestController
@RequestMapping("/es/search")
public class SearchAPIController {
    
    private Gson gson = new Gson();
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    @RequestMapping("/commonTerm")
    public Object commmonTerm() {
        String aliasName = "chatbotInstance";
        String fieldName = "question";
        String typeName = "_doc";
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.commonTermsQuery(fieldName, "질문"));
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
        }catch (Exception e) {
            e.printStackTrace();
            /*
             * 예외처리
             */
        }
        return gson.toJson(resultMap); 
    }
    
    private class Answer{
        private String question;
        private String answer;
        private float score;
        
        public String getQuestion() {
            return question;
        }
        public void setQuestion(String question) {
            this.question = question;
        }
        public String getAnswer() {
            return answer;
        }
        public void setAnswer(String answer) {
            this.answer = answer;
        }
        public float getScore() {
            return score;
        }
        public void setScore(float score) {
            this.score = score;
        }
    }
}
 
cs

 

Query String Query

검색할 때 연산자를 따로 지정하는 것이 아니라 쿼리문 자체에 AND, OR 절을 사용하고 싶을때 사용하는 쿼리이다. 혹은 특정 키워드를 필수 조건으로 빼거나 넣거나 할 수도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@RestController
@RequestMapping("/es/search")
public class SearchAPIController {
    
    
    /*
     * connection create method
     */
    public RestHighLevelClient createConnection() {
        return new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
                );
    }
    
    @RequestMapping("/queryString")
    public Object queryString() {
        String aliasName = "chatbotInstance";
        String fieldName = "question";
        String typeName = "_doc";
        String question = "+질문 -벌크";
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//        searchSourceBuilder.query(QueryBuilders.queryStringQuery("질문 OR 벌").field(fieldName));
        searchSourceBuilder.query(QueryBuilders.queryStringQuery(question).field(fieldName));
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
        }catch (Exception e) {
            e.printStackTrace();
            /*
             * 예외처리
             */
        }
        return resultMap; 
    }
    
    private class Answer{
        private String question;
        private String answer;
        private String score;
        
        public String getQuestion() {
            return question;
        }
        public void setQuestion(String question) {
            this.question = question;
        }
        public String getAnswer() {
            return answer;
        }
        public void setAnswer(String answer) {
            this.answer = answer;
        }
        public String getScore() {
            return score;
        }
        public void setScore(String score) {
            this.score = score;
        }
    }
}
 
cs

 

Term Query

지정된 필드에 정확한 텀이 들어있는 문서를 찾을 때 사용한다. Keyword 타입으로 설정된 필드에서만 검색가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@RequestMapping("/term")
    public Object term() {
        String aliasName = "chatbotInstance";
        String fieldName = "question";
        String typeName = "_doc";
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termQuery("keywordFiled","keyword"));
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
        }catch (Exception e) {
            e.printStackTrace();
            /*
             * 예외처리
             */
        }
        return resultMap; 
    }
cs

 

Bool Query

must, mustNot, should, filter 등을 조합해서 쿼리를 구성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@RequestMapping("/bool")
    public Object bool() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("keywordField""value"))
                .mustNot(QueryBuilders.termQuery("keywordField2""value2"))
                .should(QueryBuilders.termQuery("keywordField3""value3"))
                .filter(QueryBuilders.termQuery("keywordField4""value4")));
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
        }catch (Exception e) {
            e.printStackTrace();
            /*
             * 예외처리
             */
        }
        return resultMap; 
    }
cs

 

기타 많은 쿼리들을 있지만 이번 포스팅은 여기까지만 다룬다. 이번 포스팅의 목적은 엘라스틱서치의 기능이 목적은 아니고 엘라스틱서치 클라이언트를 적응하는 것이 목적이었다.

 

posted by 여성게
: