Метаданные Spring-data-elasticsearch _score

При запросе Elasticsearch из Spring-Data я хотел бы получить _score

Если мы возьмем простой следующий класс:

@Document(indexName = "test", type = "el_test")
public static class ElTest{
    private long id;
    private String myField;
}

С тестом JUnit

@Test
public void testScore() throws Exception {
    elasticsearchTemplate.index(new IndexQueryBuilder()
            .withIndexName("test")
            .withObject(new ElTest("first value"))
            .build());
    elasticsearchTemplate.index(new IndexQueryBuilder()
            .withIndexName("test")
            .withObject(new ElTest("second value"))
            .build());
    elasticsearchTemplate.index(new IndexQueryBuilder()
            .withIndexName("test")
            .withObject(new ElTest("third"))
            .build());

    SearchQuery query = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchQuery("myField","second value"))
            .build();

    List<ElTest> els = elasticsearchTemplate.queryForList(query, ElTest.class);
    assertEquals(2, els.size());
}

Это создаст 3 записи в Elasticsearch. Запрос извлечет два значения с разными оценками.

Если мы поместим запрос прямо в Elasticsearch:

POST /test/_search
{
  "query": { 
    "match" : {
      "myField" : {
        "query" : "second value",
         "type" : "boolean"
      }
    }
  }
}

И ответ от Elasticsearch:

{
  "took": 15,
  "timed_out": false,
  "_shards": {
     "total": 5,
     "successful": 5,
     "failed": 0
   },
"hits": {
  "total": 2,
  "max_score": 0.8838835,
  "hits": [
     {
        "_index": "test",
        "_type": "el_test",
        "_id": "AVKmmYCL3xnXT_BGRA3T",
        "_score": 0.8838835,
        "_source": {
           "id": 0,
           "myField": "second value"
        }
     },
     {
        "_index": "test",
        "_type": "el_test",
        "_id": "AVKmmYCI3xnXT_BGRA3S",
        "_score": 0.028130025,
        "_source": {
           "id": 0,
           "myField": "first value"
        }
     }
     ]
  }
  }

Получив список объектов ElTest, я не могу получить значение _score (0,8838835 и 0,028130025). Есть ли способ получить значение в Spring-данных?

Я бы представил что-то вроде

@Document(indexName = "test", type = "el_test")
public static class ElTest{
    private long id;
    private String myField;
    @Score
    private double score;
}

Где Score будет аннотацией, указывающей, что поле заполнено Elasticsearch.

Причина, по которой мне нужен счет, состоит в том, чтобы отсортировать список в графическом интерфейсе.

Из-за множества сопоставлений dto от Elasticsearch до графического интерфейса список в каком-то месте становится несортированным. Поэтому он не надежен.

Я мог бы, конечно, добавить значение оценки вручную, но это потребовало бы перебора списка. Что не очень приятно.

Я использую Spring-data-elasticsearch версии 1.2.0 и, следовательно, Elasticsearch 1.4.4.


person fan    schedule 03.02.2016    source источник


Ответы (1)


Я искал то же самое.

Я обнаружил, что в классе ElasticsearchTemplate для этого используется экземпляр DefaultResultMapper:

@Override
public <T> FacetedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
    long totalHits = response.getHits().totalHits();
    List<T> results = new ArrayList<T>();
    for (SearchHit hit : response.getHits()) {
        if (hit != null) {
            T result = null;
            if (!Strings.isNullOrEmpty(hit.sourceAsString())) {
                result = mapEntity(hit.sourceAsString(), clazz);
            } else {
                result = mapEntity(hit.getFields().values(), clazz);
            }
            setPersistentEntityId(result, hit.getId(), clazz);
            results.add(result);
        }
    }
    List<FacetResult> facets = new ArrayList<FacetResult>();
    if (response.getFacets() != null) {
        for (Facet facet : response.getFacets()) {
            FacetResult facetResult = DefaultFacetMapper.parse(facet);
            if (facetResult != null) {
                facets.add(facetResult);
            }
        }
    }

    return new FacetedPageImpl<T>(results, pageable, totalHits, facets);
}

Оценка хранится внутри экземпляра SearchHit, однако игнорируется в hit.sourceAsString()...

В качестве обходного пути я создал простой интерфейс:

/**
 * Object to have score from elastic search
 */
public interface Scoreable {

    float getScore();

    void setScore(float score);
}

и расширенный DefaultResultMapper:

/**
 * Results mapper with score support
 */
public class ScoreResultsMapper extends DefaultResultMapper {

    public ScoreResultsMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
        super(mappingContext);
    }

    @Override
    public <T> FacetedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
        FacetedPage<T> resultPage = super.mapResults(response, clazz, pageable);
        Iterator<T> it = resultPage.getContent().iterator();
        for (SearchHit hit : response.getHits()) {
            if (hit != null) {
                T next = it.next();
                if (next instanceof  Scoreable) {
                    ((Scoreable) next).setScore(hit.score());
                }
            }
        }
        return resultPage;
    }
}

Здесь я просто проверяю, является ли возвращаемый тип экземпляром Scoreable, и если да, то я ставлю в него оценку.

Итак, теперь я могу настроить ElasticsearchTemplate с помощью нового картографа (я использую spring-boot):

@Bean
public ElasticsearchTemplate elasticsearchTemplate() {
    MappingElasticsearchConverter converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
    ScoreResultsMapper mapper = new ScoreResultsMapper(converter.getMappingContext());
    return new ElasticsearchTemplate(client(), converter, mapper);
}

Конечно, все мои документы с оценкой должны расширять Scorable. Возможно, нам придется перезаписать некоторые другие методы DefaultResultMapper, чтобы иметь поддержку оценки в других типах запросов.

person Konstantin    schedule 25.02.2016
comment
Отличный ответ - спасибо. Очень помог. Вы также сталкивались со сценарием, в котором каждое попадание имело 1,0 балла? Я вижу это, но когда я использую клиент REST и иду напрямую, я получаю более значимые оценки. - person Mike; 05.08.2016