ElasticSearch/오류

SpringBoot(ElasticSearch) - : Cannot constructQuery '*"첫 번째"*'. Use expression or multiple clauses instead.

jaycheol 2024. 11. 13. 13:57
반응형
org.springframework.dao.InvalidDataAccessApiUsageException: Cannot constructQuery '*"첫 번째"*'. Use expression or multiple clauses instead.
	at org.springframework.data.elasticsearch.core.query.Criteria.assertNoBlankInWildcardQuery(Criteria.java:852) ~[spring-data-elasticsearch-5.3.5.jar:5.3.5]
	at org.springframework.data.elasticsearch.core.query.Criteria.contains(Criteria.java:409) ~[spring-data-elasticsearch-5.3.5.jar:5.3.5]
	at org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator.from(ElasticsearchQueryCreator.java:111) ~[spring-data-elasticsearch-5.3.5.jar:5.3.5]
	at org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator.create(ElasticsearchQueryCreator.java:68) ~[spring-data-elasticsearch-5.3.5.jar:5.3.5]
	at org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator.create(ElasticsearchQueryCreator.java:49) ~[spring-data-elasticsearch-5.3.5.jar:5.3.5]
	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:119) ~[spring-data-commons-3.3.5.jar:3.3.5]

게시판 검색을 하던 도중 이와같은 오류를 만나게 되었다.

 

이 오류는 Elasticsearch에서 findByTitleContainingOrContentContainingOrAuthorContaining 메서드로 검색할 때 발생하며, Spring Data Elasticsearch가 contains와 같은 연산을 적절히 변환하지 못하는 상황에서 발생할 수 있습니다. 특히, Elasticsearch는 기본적으로 SQL에서 사용하는 contains와 다르게 작동하므로, Spring Data의 Containing 메서드로 문자열 검색 시 예기치 않은 동작을 할 수 있습니다.

 

 

원인

Spring Data Elasticsearch에서는 Containing 연산자를 사용하는 경우 내부적으로 wildcard 또는 match 쿼리를 사용하는데, Elasticsearch는 일부 경우 wildcard 쿼리에 대해 빈 문자열이나 특수문자를 허용하지 않거나, 복잡한 쿼리에서 오류를 발생시킬 수 있습니다. 이 경우, wildcard 연산 대신 match 쿼리나 직접 쿼리를 구성해야 합니다.

 

해결 방법

다양한 필드에서 검색을 수행하기 위해, Custom Query를 사용하는 방식을 추천합니다. Spring Data Elasticsearch의 @Query 어노테이션을 사용해 직접 쿼리를 정의할 수 있습니다.

1. PostRepository에서 @Query 어노테이션으로 쿼리 정의

findByTitleContainingOrContentContainingOrAuthorContaining 대신 @Query 어노테이션을 사용하여 검색 쿼리를 정의할 수 있습니다. 이 경우, match를 사용하여 보다 안정적으로 여러 필드에 대해 검색이 가능합니다.

import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface PostRepository extends ElasticsearchRepository<Post, String> {

    List<Post> findByTitleContaining(String title);
    List<Post> findByContentContaining(String content);
    List<Post> findByAuthorContaining(String author);

    @Query("{ \"multi_match\": { \"query\": \"?0\", \"fields\": [ \"title\", \"content\", \"author\" ] } }")
    List<Post> searchByAllFields(String keyword);
}

 

  • multi_match 쿼리: multi_match는 여러 필드에서 동일한 키워드를 검색할 때 사용하는 쿼리로, fields 파라미터에 검색할 필드들을 지정합니다.
  • ?0: @Query에서 ?0은 첫 번째 매개변수를 의미하며, 여기서는 keyword 값이 전달됩니다.

이렇게 정의하면, searchByAllFields 메서드가 title, content, author 필드 모두에 대해 키워드를 포함하는 문서를 검색할 수 있습니다.

 

 


 

다른 해결 방법으로는 CriteriaQuery를 사용해 프로그래밍 방식으로 쿼리를 빌드하거나, NativeSearchQueryBuilder를 활용하는 방법이 있습니다. 이 방법들은 직접 쿼리 객체를 생성하여 Elasticsearch에 전달하므로, 보다 유연하게 검색 로직을 작성할 수 있습니다.

 

2. CriteriaQuery 사용

CriteriaQuery는 Spring Data Elasticsearch에서 제공하는 쿼리 생성기로, 프로그래밍 방식으로 필터와 조건을 설정하여 쿼리를 구성할 수 있습니다.

 

PostService에서 CriteriaQuery를 사용한 검색 메서드 추가

import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PostService {

    private final PostRepository postRepository;
    private final ElasticsearchRestTemplate elasticsearchRestTemplate;

    public PostService(PostRepository postRepository, ElasticsearchRestTemplate elasticsearchRestTemplate) {
        this.postRepository = postRepository;
        this.elasticsearchRestTemplate = elasticsearchRestTemplate;
    }

    public List<Post> searchByAllFields(String keyword) {
        Criteria criteria = new Criteria("title").contains(keyword)
            .or(new Criteria("content").contains(keyword))
            .or(new Criteria("author").contains(keyword));
        
        CriteriaQuery query = new CriteriaQuery(criteria);
        return elasticsearchRestTemplate.queryForList(query, Post.class);
    }
}

 

설명

  • Criteria 객체: title, content, author 필드에 대해 contains 조건을 추가하여 검색 기준을 정의합니다.
  • CriteriaQuery: Criteria 객체를 기반으로 CriteriaQuery를 생성합니다.
  • ElasticsearchRestTemplate 사용: elasticsearchRestTemplate.queryForList(query, Post.class)를 사용해 쿼리를 실행하고 결과를 반환합니다.

3. NativeSearchQueryBuilder 사용

NativeSearchQueryBuilder는 더 직접적인 방식으로 Elasticsearch의 원본 쿼리 빌더 API를 활용할 수 있게 해줍니다. 이 방법을 사용하면 Elasticsearch의 multi_match 쿼리 등을 자유롭게 설정할 수 있습니다.

PostService에서 NativeSearchQueryBuilder를 사용한 검색 메서드 추가

import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PostService {

    private final PostRepository postRepository;
    private final ElasticsearchRestTemplate elasticsearchRestTemplate;

    public PostService(PostRepository postRepository, ElasticsearchRestTemplate elasticsearchRestTemplate) {
        this.postRepository = postRepository;
        this.elasticsearchRestTemplate = elasticsearchRestTemplate;
    }

    public List<Post> searchByAllFields(String keyword) {
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.multiMatchQuery(keyword)
                .field("title")
                .field("content")
                .field("author")
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS))  // BEST_FIELDS는 가장 적합한 필드를 반환하도록 함
            .build();

        return elasticsearchRestTemplate.queryForList(searchQuery, Post.class);
    }
}

설명

  • NativeSearchQueryBuilder: Elasticsearch의 원본 쿼리 빌더를 사용하여 쿼리를 생성합니다.
  • multiMatchQuery: 여러 필드에서 키워드를 검색하도록 multiMatchQuery를 설정합니다.
  • queryForList: NativeSearchQuery 객체를 실행하여 결과를 리스트 형태로 반환합니다.

4. 쿼리를 BoolQuery로 구성하여 필드 검색

특정 필드들에 대해 should 조건을 적용하는 BoolQuery로도 설정할 수 있습니다.

public List<Post> searchByAllFields(String keyword) {
    NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
        .withQuery(QueryBuilders.boolQuery()
            .should(QueryBuilders.matchQuery("title", keyword))
            .should(QueryBuilders.matchQuery("content", keyword))
            .should(QueryBuilders.matchQuery("author", keyword)))
        .build();

    return elasticsearchRestTemplate.queryForList(searchQuery, Post.class);
}

설명

  • BoolQuery: should 조건을 사용하여 title, content, author 필드에서 키워드와 일치하는 결과가 있으면 반환합니다.
  • matchQuery: 각 필드에 대해 matchQuery를 사용해 필드별로 정확한 일치도를 검사합니다.

요약

  • CriteriaQuery: 간결하고 가독성이 좋은 Spring Data Elasticsearch의 기본 제공 쿼리 빌더입니다.
  • NativeSearchQueryBuilder: Elasticsearch의 원본 쿼리 빌더 API를 사용할 수 있어, 보다 세부적인 쿼리 구성이 가능합니다.
  • BoolQuery와 matchQuery: 특정 조건(should, must 등)을 설정하여 필드별 검색을 정확하게 수행할 수 있습니다.

이 방법들을 통해 Spring Data Elasticsearch의 기본 쿼리 메서드 대신 직접 쿼리를 구성함으로써, 검색 요구 사항을 보다 유연하게 처리할 수 있습니다.

반응형