Рубрики
Без рубрики

Простая реализация тегов с помощью Elasticsearch

Узнайте, как вы можете расширить свой проект Elasticsearch с помощью реализации тегов

Автор оригинала: baeldung.

1. Обзор

Пометки-это распространенный шаблон проектирования, который позволяет нам классифицировать и фильтровать элементы в нашей модели данных.

В этой статье мы реализуем маркировку с помощью Spring и Elasticsearch. Мы будем использовать как данные Spring, так и API Elasticsearch.

Прежде всего, мы не собираемся описывать основы получения данных Elasticsearch и Spring – вы можете изучить их здесь .

2. Добавление Тегов

Самая простая реализация тегирования-это массив строк. Мы можем реализовать это, добавив новое поле в нашу модель данных следующим образом:

@Document(indexName = "blog", type = "article")
public class Article {

    // ...

    @Field(type = Keyword)
    private String[] tags;

    // ...
}

Обратите внимание на использование типа поля Ключевое слово . Нам нужно только точное совпадение наших тегов для фильтрации результата. Это позволяет нам использовать похожие, но отдельные теги, такие как elasticsearch-это потрясающе и elasticsearch ужасен .

Анализируемые поля возвращали бы частичные попадания, что в данном случае является неправильным поведением.

3. Построение запросов

Теги позволяют нам интересным образом манипулировать нашими запросами. Мы можем выполнять поиск по ним, как по любому другому полю, или использовать их для фильтрации результатов по запросам match_all . Мы также можем использовать их с другими запросами, чтобы улучшить наши результаты.

3.1. Поиск По Тегам

Новое поле tag , которое мы создали в нашей модели, похоже на любое другое поле в нашем индексе. Мы можем искать любую сущность, у которой есть определенный тег, подобный этому:

@Query("{\"bool\": {\"must\": [{\"match\": {\"tags\": \"?0\"}}]}}")
Page
findByTagUsingDeclaredQuery(String tag, Pageable pageable);

В этом примере для построения нашего запроса используется хранилище данных Spring, но мы можем так же быстро использовать шаблон Rest для запроса кластера Elasticsearch вручную.

Аналогично, мы можем использовать API Elasticsearch:

boolQuery().must(termQuery("tags", "elasticsearch"));

Предположим, что в нашем индексе мы используем следующие документы:

[
    {
        "id": 1,
        "title": "Spring Data Elasticsearch",
        "authors": [ { "name": "John Doe" }, { "name": "John Smith" } ],
        "tags": [ "elasticsearch", "spring data" ]
    },
    {
        "id": 2,
        "title": "Search engines",
        "authors": [ { "name": "John Doe" } ],
        "tags": [ "search engines", "tutorial" ]
    },
    {
        "id": 3,
        "title": "Second Article About Elasticsearch",
        "authors": [ { "name": "John Smith" } ],
        "tags": [ "elasticsearch", "spring data" ]
    },
    {
        "id": 4,
        "title": "Elasticsearch Tutorial",
        "authors": [ { "name": "John Doe" } ],
        "tags": [ "elasticsearch" ]
    },
]

Теперь мы можем использовать этот запрос:

Page
articleByTags = articleService.findByTagUsingDeclaredQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

3.2. Фильтрация Всех Документов

Общим шаблоном проектирования является создание Отфильтрованного представления списка в пользовательском интерфейсе, которое отображает все сущности, но также позволяет пользователю фильтровать на основе различных критериев.

Допустим, мы хотим вернуть все статьи, отфильтрованные по любому выбранному пользователем тегу:

@Query("{\"bool\": {\"must\": " +
  "{\"match_all\": {}}, \"filter\": {\"term\": {\"tags\": \"?0\" }}}}")
Page
findByFilteredTagQuery(String tag, Pageable pageable);

Еще раз, мы используем данные Spring для построения нашего объявленного запроса.

Следовательно, запрос, который мы используем, разделен на две части. Запрос подсчета очков является первым термином, в данном случае match_all . Запрос фильтра следующий и сообщает Elasticsearch, какие результаты следует отбросить.

Вот как мы используем этот запрос:

Page
articleByTags = articleService.findByFilteredTagQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

Важно понимать, что, хотя это возвращает те же результаты, что и в нашем примере выше, этот запрос будет работать лучше.

3.3. Фильтрация запросов

Иногда поиск возвращает слишком много результатов, чтобы их можно было использовать. В этом случае неплохо предоставить механизм фильтрации, который может повторно выполнить тот же поиск, только с суженными результатами.

Вот пример, в котором мы сужаем список статей, написанных автором, до тех, которые имеют определенный тег:

@Query("{\"bool\": {\"must\": " + 
  "{\"match\": {\"authors.name\": \"?0\"}}, " +
  "\"filter\": {\"term\": {\"tags\": \"?1\" }}}}")
Page
findByAuthorsNameAndFilteredTagQuery( String name, String tag, Pageable pageable);

Опять же, Spring Data делает всю работу за нас.

Давайте также рассмотрим, как построить этот запрос самостоятельно:

QueryBuilder builder = boolQuery().must(
  nestedQuery("authors", boolQuery().must(termQuery("authors.name", "doe")), ScoreMode.None))
  .filter(termQuery("tags", "elasticsearch"));

Конечно, мы можем использовать ту же технику для фильтрации по любому другому полю документа. Но теги особенно хорошо подходят для этого варианта использования.

Вот как использовать приведенный выше запрос:

SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder)
  .build();
List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); // articles contains [ 1, 4 ] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(4))) );

4. Контекст фильтрации

Когда мы строим запрос, нам нужно различать Контекст запроса и Контекст фильтра. Каждый запрос в Elasticsearch имеет контекст запроса, поэтому мы должны привыкнуть видеть их.

Не каждый тип запроса поддерживает контекст фильтра. Поэтому, если мы хотим фильтровать по тегам, нам нужно знать, какие типы запросов мы можем использовать.

Запрос bool имеет два способа доступа к контексту фильтра . Первый параметр, фильтр , мы используем выше. Мы также можем использовать параметр must_not для активации контекста.

Следующий тип запроса, который мы можем отфильтровать, – constant_score . Это полезно, когда вы хотите заменить контекст запроса результатами фильтра и присвоить каждому результату одинаковую оценку.

Последний тип запроса, который мы можем отфильтровать на основе тегов, – это агрегация фильтров . Это позволяет нам создавать группы агрегирования на основе результатов нашего фильтра. Другими словами, мы можем сгруппировать все статьи по тегам в нашем результате агрегирования.

5. Расширенная Маркировка

До сих пор мы говорили только о маркировке с использованием самой базовой реализации. Следующим логическим шагом является создание тегов, которые сами являются парами ключ-значение . Это позволило бы нам стать еще более изобретательными с нашими запросами и фильтрами.

Например, мы могли бы изменить поле нашего тега на это:

@Field(type = Nested)
private List tags;

Затем мы просто изменили бы наши фильтры, чтобы использовать вложенные типы запросов .

Как только мы поймем, как использовать пары ключ-значение , это будет небольшой шаг к использованию сложных объектов в качестве нашего тега. Не многим реализациям потребуется полный объект в качестве тега, но приятно знать, что у нас есть эта опция, если она нам потребуется.

6. Заключение

В этой статье мы рассмотрели основы реализации тегов с помощью Elasticsearch.

Как всегда, примеры можно найти на GitHub .