본문 바로가기

프로젝트/여행지 오픈 API

[ElasticSearch] NativeQuery

목차

     

    도저히 @Query를 사용해서 로우 쿼리를 사용할 수가 없다.

    에러도 많이 나고, 지원하지도 않으며 검사 역시 힘들기 때문이다.

     

    쿼리문이 조금만 길어져도 찾기 힘들다..

     

     

     

    https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.misc.searchtemplates

     

    Spring Data Elasticsearch - Reference Documentation

    The Spring Data infrastructure provides hooks for modifying an entity before and after certain methods are invoked. Those so called EntityCallback instances provide a convenient way to check and potentially modify an entity in a callback fashioned style. A

    docs.spring.io

     

    https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.misc.searchtemplates

     

    Spring Data Elasticsearch - Reference Documentation

    The Spring Data infrastructure provides hooks for modifying an entity before and after certain methods are invoked. Those so called EntityCallback instances provide a convenient way to check and potentially modify an entity in a callback fashioned style. A

    docs.spring.io

     

    내용이 그렇게 자세하지는 않아서, API 문서로 추가로 확인하였다.

    스프링 공식 Docs보다는 ElasticSearch의 QueryDSL에서 더욱 자세한 정보를 확인할 수 있다.

    현재 설치된 elasticsearch 버전이 7.11.2이고, 이는 Spring data elasticsearch 4.2와 호환된다.

     

    https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html

     

    Query DSL | Elasticsearch Guide [8.10] | Elastic

    Elasticsearch provides a full Query DSL (Domain Specific Language) based on JSON to define queries. Think of the Query DSL as an AST (Abstract Syntax Tree) of queries, consisting of two types of clauses: Leaf query clauses Leaf query clauses look for a par

    www.elastic.co

     

    https://docs.spring.io/spring-data/elasticsearch/docs/4.2.0/api/

     

    Spring Data Elasticsearch 4.2.0 API

     

    docs.spring.io

     

    NativeQuery 적용 및 변경

     

     

    SearchHit 타입 객체를 반환하는 쿼리를 조금 변경하였다.

    받는 입장에서 편하게 다루도록 List로 변경해서 보내도록 하자.

     

        public List<Wiki> searchExactAttName(String attName) {
            NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                    // .keyword 붙일 것. 정확한 검색을 위해 필요하다.
                    .withQuery(QueryBuilders.termQuery("attraction_name.keyword", attName))
                    .build();
    
            // Elasticsearch에서 쿼리 실행
            SearchHits<Wiki> result = elasticsearchRestTemplate.search(searchQuery, Wiki.class);
            
            // SearchHits 내부 Content를 List<Wiki>로 변환
            List<Wiki> wikiList = new ArrayList<>();
            for (SearchHit<Wiki> hit : result) {
                wikiList.add(hit.getContent());
            }
            return wikiList;
        }

     

    ..

    이렇게 테스팅을 마치고 Field에 맞춰 모듈화를 진행하였다.

     

    이런 타입으로 리턴된다.

     

    최종적인 코드는 다음과 같다.

     

    package com.sch.sch_elasticsearch.domain.wiki.service;
    
    import com.sch.sch_elasticsearch.domain.wiki.entity.Wiki;
    import com.sch.sch_elasticsearch.domain.wiki.repository.WikiRepository;
    import com.sch.sch_elasticsearch.exception.CommonException;
    import com.sch.sch_elasticsearch.exception.ExceptionType;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
    import org.springframework.data.elasticsearch.core.SearchHit;
    import org.springframework.data.elasticsearch.core.SearchHits;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    @RequiredArgsConstructor
    @Slf4j
    public class WikiService {
        private final ElasticsearchRestTemplate elasticsearchRestTemplate;
    
        /**
         * 정확한 keyword 검색용 쿼리문 : 제목, 타이틀, content_id
         * @param typeNum (타입 넘버)
         * @param inputString (찾을 검색어)
         * @return List<Wiki> 결과값
         */
        public List<Wiki> searchExact(int typeNum, String inputString) {
            String type = getType(typeNum);
    
            NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                    // .keyword 붙일 것. 정확한 검색을 위해 필요하다.
                    .withQuery(QueryBuilders.termQuery(type, inputString))
                    .build();
    
            // Elasticsearch에서 쿼리 실행
            return getListBySearchHits(elasticsearchRestTemplate.search(searchQuery, Wiki.class));
        }
    
        /**
         * 입력 파라미터의 부분 검색(전문 검색) 수행 (attraction_name), (wiki_content)
         * @param typeNum (타입 넘버)
         * @param inputString (찾을 검색어)
         * @return List<Wiki> 결과값
         */
        public List<Wiki> searchPartial(int typeNum, String inputString) {
            String type = getType(typeNum);
            NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.matchQuery(type, inputString))
                    .build();
    
            // Elasticsearch에서 쿼리 실행 후 결과값 가져오기
            return getListBySearchHits(elasticsearchRestTemplate.search(searchQuery, Wiki.class));
        }
    
        /**
         * SearchHits 에서 Content 가져와서 리스트로 변환 후 출력
         * @param SearchHits<Wiki> result
         * @return List<Wiki>
         */
        public List<Wiki> getListBySearchHits(SearchHits<Wiki> result) {
            // SearchHits 내부 Content를 List<Wiki>로 변환
            List<Wiki> wikiList = new ArrayList<>();
            for (SearchHit<Wiki> hit : result) {
                wikiList.add(hit.getContent());
            }
            return wikiList;
        }
    
        /**
         * TypeNum : Keyword change
         * @param typeNum (input num)
         * @return type (String keyword)
         * @throws CommonException 유효하지 않은 typeNum이 입력될 경우
         */
        public String getType(int typeNum) {
            String type = "";
            switch (typeNum) {
                case 0:
                    type = "attraction_name.keyword";
                    break;
                case 1:
                    type = "wiki_title.keyword";
                    break;
                case 2:
                    type = "content_id";
                    break;
                case 3:
                    type = "attraction_name";
                    break;
                case 4:
                    type = "wiki_content";
                    break;
                default:
                    throw new CommonException(ExceptionType.TYPENUM_IS_INVALID);
            }
            return type;
        }
    
    
    }

     

    ElasticsearchRestTemplate 메서드

     

    ElasticsearchRestTemplate을 사용한 메서드들은 Elasticsearch의 다양한 쿼리와 동작을 수행한다.

     

    search()

      • 일반적인 검색 쿼리를 실행하는데 사용된다.
      • 복잡한 검색 조건과 함께 사용하여 다양한 결과를 반환받을 수 있다.
      • 결과를 SearchHits 객체로 받아 각각의 검색 결과를 확인할 수 있다.
    1. multiGet()
      • 여러 ID에 해당하는 문서를 한 번의 요청으로 조회할 때 사용된다.
      • 대량의 데이터를 빠르게 가져와야 할 때 효과적이다.
      • 반환된 객체는 주어진 ID 리스트 순서대로 문서를 포함한다.
    2. multiSearch()
      • 여러 검색 쿼리를 동시에 실행할 때 사용된다.
      • 각각의 쿼리는 독립적으로 수행되며 각 쿼리의 결과는 별도로 반환된다.
      • 대량의 복잡한 쿼리를 병렬로 처리할 때 유용하다.
    3. get()
      • 단일 문서의 ID를 사용하여 해당 문서를 조회할 때 사용된다.
      • 반환되는 객체는 조회된 단일 문서의 정보를 포함한다.
    4. save()
      • 주어진 엔티티를 Elasticsearch에 저장하거나 업데이트할 때 사용된다.
      • 색인 생성 및 갱신을 위해 사용된다.

     

    1. multi?

     

    multiSearch()는 같은 쿼리 형식에 다른 입력 파라미터를 적용할 수도 있지만, 다른 쿼리들을 동시에 실행하는 것이 주된 사용 목적이다.

    즉, 서로 다른 쿼리를 동시에 여러 개 실행하고 각각의 결과를 따로 받아오는 것이다. 

    한 요청에서 다양한 검색 조건이나 다른 색인(indexes)을 대상으로 하는 검색 등을 할 때 사용한다.

    search를 반복문 등으로 여러번 보내는 것보다, 한번에 많은 요청들을 담아서 보내는 것이 더욱 효율적일 것이다.

    2. search와 get 차이

    • search() 메서드는 주어진 쿼리에 맞는 모든 문서를 검색할 때 사용된다. 이 메서드는 엘라스틱서치에서 검색 쿼리를 수행하고, 한 개 이상의 결과를 반환할 수 있다. 결과는 쿼리 조건에 맞는 데이터의 집합이며, 복잡한 쿼리와 함께 사용할 수 있다 (예를 들어, match, term, range 쿼리 등).
    • get() 메서드는 특정 ID를 가진 단일 문서를 가져올 때 사용된다. 즉, 문서의 ID를 알고 있을 때 그 ID에 해당하는 문서를 빠르게 조회할 수 있다. get()은 ID에 의한 조회이기 때문에 오직 한 개의 문서만 반환하고, 검색에 비해 훨씬 빠르다는 특징이 있다.

    간단히 말해, search()는 복잡한 쿼리를 통한 검색에, get()은 ID를 통한 특정 문서 조회에 사용된다.