Search code examples
elasticsearchspring-data-elasticsearch

Spring data elasticsearch how to create repository method for keyword field


Let's say I have mapping like this, and I want to search by the "requestId.keyword" field to fetch the exact match requests. How can I implement it with the Spring Data Elasticsearch repository without using @Query annotation?

 "requestId": {
      "type": "text",
      "analyzer": "1_to_15_analyzer_without_space",
      "search_analyzer": "all_symbols_and_fold_analyzer",
      "fields": {
        "keyword": {
          "type": "keyword"
        }
      }
    }

Solution

  • This is not possible with the mechanism to build queries by introspecting the method name. The first idea is to have something like (I am using a Foo entity here):

    SearchHits<Foo> searchByRequestId_Keyword(String keyword);
    

    The analysis of the method name is done in the spring-data-common module which only uses the property names of the Java properties of an entity (might be nested). But the keyword subfield only exists in Elasticsearch and - if not autocreated - in the @MultiField annotation. But the code to parse the methodname does not use store-specific information and so an approach like this will not work and fail with the error that keyword is not a property of text - which is right for the Java object.

    What you can do is to first add a custom repository fragment interface:

    public interface FooKeywordRepository {
        SearchHits<Foo> searchByRequestIdKeyword(String keyword);
    }
    

    and provide an implementation that must be named like the interface with Impl as suffix:

    import org.elasticsearch.index.query.QueryBuilders;
    import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
    import org.springframework.data.elasticsearch.core.SearchHits;
    import org.springframework.data.elasticsearch.core.query.Criteria;
    import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.data.elasticsearch.core.query.Query;
    
    public class FooKeywordRepositoryImpl implements FooKeywordRepository {
    
        private final ElasticsearchOperations operations;
    
        public FooKeywordRepositoryImpl(ElasticsearchOperations operations) {
            this.operations = operations;
        }
    
        @Override
        public SearchHits<Foo> searchByRequestIdKeyword(String keyword) {
    
            Query query1 = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.termQuery("requestId.keyword", keyword))
                .build();
    
            Query query2 = new CriteriaQuery(Criteria.where("requestId.keyword").is(keyword));
    
            return operations.search(query1, Foo.class); // could be query2 as well
        }
    }
    

    You have an ElasticsearchOperations injected and use that to execute a query that you build. I have put in two ways to build the query, both work.

    Your repository definition to use would then be:

    public interface FooRepository extends ElasticsearchRepository<Foo, String>, FooKeywordRepository {
        // other custom methods if needed
    }