본문 바로가기
프로그래밍/ElasticSearch

[elasticsearch + Spring] elasticsearch를 Java Spring에서 사용해보자 - 기본적인 검색 만들기

by 사바라다 2021. 4. 23.

안녕하세요. 오늘 실습해볼 내용은 elasticsearch를 이용하여 Spring에 빠른 검색을 적용해 보는 것입니다. 이전시간까지 우리는 아래 포스팅을 통해서 환경설정, index를 만들고 document의 CRUD를 적용해보는 것에 대해서 실습해 보았습니다.

[elasticsearch + Spring] elasticsearch를 Java Spring에서 사용해보자 - 환경설정과 Index 만들기

[elasticsearch + Spring] elasticsearch를 Java Spring에서 사용해보자 - Document CRUD 만들기

ElasticSearch를 사용한다는 것은 일반적으로 빠른 검색 서비스를 이용하기 위한 목적을 가지고 계실 것입니다. 이전까지는 검색을 하기위해 데이터를 쌓는 부분에 대해서 알아보았으니 이번시간에는 검색을 만들어보도록 하겠습니다.

환경 구축

환경을 구축하는 방법에 대해서는 [elasticsearch + Spring] elasticsearch를 Java Spring에서 사용해보자 - Document CRUD 만들기 포스팅을 참고해 주시기 바랍니다.

사용하는 인덱스의 이름은 game이며 해당 포스팅에서 사용하는 통신은 RestHighLevelClient입니다.

검색 기본

기본적인 검색에는 아래와 같은 명령어가 사용됩니다. {json body} 부분은 일반적으로 조건을 나타내며 이 부분은 옵셔널이며 생략이 가능합니다. 생략했을 때는 해당 인덱스의 모든 document 중 먼저 입력되었던 값 10개를 가져오게 됩니다.

POST /<index>/_serach

{json body}

아래는 응답값 샘플입니다. hits에 array 형식으로 값이 들어오게 되며, _source에 입력했던 document가 들어가있는것을 확인할 수 있습니다.

{
    "took": 51,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "1",
                "_score": 1.0,
                "_source": {
                    "title": "[한글]Injustice™ 2 - Ultimate Pack"
                }
            },
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "2",
                "_score": 1.0,
                "_source": {
                    "title": "[한글]MegaRace 2"
                }
            }
        ]
    }
}

위의 search 요청을 SDK를 이용하면 어떻게 만들 수 있는지 확인해보도록 하겠습니다. 검색 요청 객체는 아래의 단계로 만들 수 있습니다.

  1. SearchSourceBuilder 객체를 생성합니다.
  2. BoolQueryBuilder 객체를 생성합니다. ( 옵션, 조건을 추가할 때 사용 )
  3. 생성한 BoolQueryBuilder를 SearchSourceBuilder에 추가합니다.
  4. SearchRequest를 생성하며 검색을 진행할 index와 SearchSourceBuilder를 추가합니다.
  5. RestHighLevelClient로 searchRequest를 이용하여 검색을 진행합니다.
SearchRequest searchRequest =
    new SearchRequest(index)
        .source(new SearchSourceBuilder(
            // QueryBuilders 추가 가능
            QueryBuilders.boolQuery()
              .should(QueryBuilders.wildcardQuery("title", "*" + parameters.getKeyword() + "*"))
              .should(QueryBuilders.wildcardQuery("content", "*" + parameters.getKeyword() + "*"))
        ));

return client.search(searchRequest, RequestOptions.DEFAULT);

이렇게 검색을 진행하면 SearchResponse 객체로 응답값을 받습니다. 이 객체는 위에서 봤던 응답 json을 객체로 변환한 형태로 되어있어 실제 데이터를 확인하기 위해서는 hits의 sources를 확인하여야합니다.

검색 조건

http 요청에 body를 추가하여 조건을 줘보도록 하겠습니다. 다양한 조건이 있지만 검색에 대한 조건은 query 부분을 확인해주시면됩니다. 아래 요청은 title 필드에 퀘스트라는 단어가 들어가 있는 조건을 만족하는 경우를 리턴하라는 요청입니다.

POST /<index>/_serach

{
  "from": 0,
  "size": 20,
  "sort": {
    "_score": "desc"
  },
  "query" : {
      "bool" : {
          "must" : [
              {
                  "term" : {"title" : "퀘스트"}
              }
          ]
      }
  }
}

위 요청에 대한 샘플 응답값은 아래와 같습니다. 아래 응답의 hits_source 부분을 보시면 document의 전체 항목을 확인할 수 있습니다. 이곳에서 title퀘스트가 포함되어있는 리스트 2개를 가져온 것을 알 수 있습니다.

{
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 5.733677,
        "hits": [
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "111",
                "_score": 5.0272303,
                "_source": {
                    "title": "[한글]포켓몬 퀘스트"
                }
            },
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "403",
                "_score": 4.0333357,
                "_source": {
                    "title": "드래곤 퀘스트 III 전설의 시작"
                }
            }
        ]
    }
}

위의 요청을 SDK를 이용하여 Java에서 활용한다고 하면 아래와 같이 사용할 수 있습니다.

SearchRequest searchRequest =
    new SearchRequest(index)
        .source(new SearchSourceBuilder(
            QueryBuilders.boolQuery()
              .must(QueryBuilders.termQuery("title", "퀘스트"))
        ));

return client.search(searchRequest, RequestOptions.DEFAULT);

또 하나의 예제를 보도록 하겠습니다. 위 검색은 퀘스트라는 단어가 포함 되어있어야하는데 와일드카드 형식은 아닙니다. 즉, 퀘스트하나 이런식으로 퀘스트라는 단어에 다른 단어가 이어져 있으면 토크나이즈되어있지 않아 가져오지 못합니다. 따라서 DB의 like 검색 처럼 와일드카드를 사용할 수 있습니다. http 요청은 아래와 같습니다.

POST /<index>/_serach

{
  "from": 0,
  "size": 20,
  "sort": {
    "_score": "desc"
  },
  "query" : {
      "wildcard" : {
          "title" : {
              "value" : "*스트"
          }
      }
  }
}

응답값의 hits 영역을 보겠습니다. 요청을 *스트로 하였기 때문에 스트앞에 값이 붙어있다면 모든 것을 가져오게됩니다. 응답을 봤을때 퀘스트 뿐만 아니라 라스트의 결과값도 가져온 것을 알 수 있고 잘 동작 했다는 사실을 알 수 있었습니다.

{
    "took": 35,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 3,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "111",
                "_score": 1.0,
                "_source": {
                    "title": "[한글]포켓몬 퀘스트",
                }
            },
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "249",
                "_score": 1.0,
                "_source": {
                    "title": "라스트 쉘터: 서바이벌"
                }
            },
            {
                "_index": "game",
                "_type": "_doc",
                "_id": "403",
                "_score": 1.0,
                "_source": {
                    "title": "드래곤 퀘스트 III 전설의 시작"
                }
            }
        ]
    }
}

이런 요청 또한 RestHighLevelClient에서 쉽게 만들어서 요청할 수 있습니다. QueryBuilder 부분을 와일드카드를 사용할 수 있는 QueryBuilder로 만들어주면 됩니다.

SearchRequest searchRequest =
    new SearchRequest(index)
        .source(new SearchSourceBuilder(
            QueryBuilders.boolQuery()
              .must(QueryBuilders.wildcardQuery("title", "*스트"))
        ));

return client.search(searchRequest, RequestOptions.DEFAULT);

마무리

오늘은 이렇게 ElasticSearch에서 제공하는 RestHighLevelClient SDK를 이용하여 Spring에서 검색하는 방법에 대해서 실습해 보았습니다.

감사합니다.

참조

elastic_java-rest-high

elastic_query-filter-context

stackoverflow_elasticsearch-bool-query-combine-must-with-or

stackoverflow_howto-create-a-bool-query-with-elasticsearch-java-resthighlevelclient-api

댓글