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

Введение в спящий режим поиска

Быстрое и практичное введение в Hibernate Search.

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

1. Обзор

В этой статье мы обсудим основы поиска Hibernate, как его настроить, и реализуем несколько простых запросов.

2. Основы поиска в спящем режиме

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

В случае, если мы уже используем Hibernate и JPA для ORM, мы находимся всего в одном шаге от поиска Hibernate.

Hibernate Search интегрирует Apache Lucene, высокопроизводительную и расширяемую полнотекстовую поисковую библиотеку, написанную на Java . Это сочетает в себе мощь Lucene с простотой Hibernate и JPA.

Проще говоря, нам просто нужно добавить некоторые дополнительные аннотации к нашим доменным классам, и инструмент позаботится о таких вещах, как синхронизация базы данных/индекса.

Hibernate Search также обеспечивает интеграцию Elasticsearch; однако, поскольку он все еще находится на экспериментальной стадии, мы сосредоточимся здесь на Lucene.

3. Конфигурации

3.1. Зависимости Maven

Прежде чем приступить к работе, нам сначала нужно добавить необходимые зависимости к вашему pom.xml :


    org.hibernate
    hibernate-search-orm
    5.8.2.Final

Для простоты мы будем использовать H2 в качестве нашей базы данных:


    com.h2database 
    h2
    1.4.196

3.2. Конфигурации

Мы также должны указать, где Lucene должен хранить индекс.

Это можно сделать с помощью свойства hibernate.search.default.directory_provider .

Мы выберем файловую систему , которая является наиболее простым вариантом для нашего варианта использования. Дополнительные параметры перечислены в официальной документации . File system-master /| filesystem-slave и infinispan примечательны для кластеризованных приложений, где индекс должен быть синхронизирован между узлами.

Мы также должны определить базовый каталог по умолчанию, в котором будут храниться индексы:

hibernate.search.default.directory_provider = filesystem
hibernate.search.default.indexBase = /data/index/default

4. Классы Моделей

После настройки мы теперь готовы указать нашу модель.

Поверх аннотаций JPA @Entity и @Table мы должны добавить аннотацию @Indexed . Он сообщает Hibernate Search, что сущность Product должна быть проиндексирована.

После этого мы должны определить необходимые атрибуты как доступные для поиска, добавив @Field annotation :

@Entity
@Indexed
@Table(name = "product")
public class Product {

    @Id
    private int id;

    @Field(termVector = TermVector.YES)
    private String productName;

    @Field(termVector = TermVector.YES)
    private String description;

    @Field
    private int memory;

    // getters, setters, and constructors
}

термВектор.YES атрибут будет необходим для запроса “More Like This” позже.

5. Построение индекса Lucene

Прежде чем начать фактические запросы, мы должны запустить Lucene для первоначального построения индекса :

FullTextEntityManager fullTextEntityManager 
  = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();

После этой первоначальной сборки Hibernate Search позаботится о том, чтобы поддерживать индекс в актуальном состоянии . То есть мы можем создавать, манипулировать и удалять объекты через EntityManager как обычно.

Примечание: мы должны убедиться, что сущности полностью зафиксированы в базе данных, прежде чем они могут быть обнаружены и проиндексированы Lucene (кстати, именно по этой причине начальный импорт тестовых данных в нашем примере кода тестовых случаев поставляется в выделенном тестовом случае JUnit, аннотированном @Commit ).

6. Построение и выполнение запросов

Теперь мы готовы к созданию вашего первого запроса.

В следующем разделе мы покажем общий рабочий процесс подготовки и выполнения запроса.

После этого мы создадим несколько примеров запросов для наиболее важных типов запросов.

6.1. Общий рабочий процесс создания и выполнения запроса

Подготовка и выполнение запроса в общем случае состоит из четырех этапов :

На шаге 1 мы должны получить JPA FullTextEntityManager и из него Query Builder :

FullTextEntityManager fullTextEntityManager 
  = Search.getFullTextEntityManager(entityManager);

QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory() 
  .buildQueryBuilder()
  .forEntity(Product.class)
  .get();

На шаге 2 мы создадим запрос Lucene с помощью DSL Hibernate query:

org.apache.lucene.search.Query query = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();

На шаге 3 мы обернем запрос Lucene в запрос Hibernate:

org.hibernate.search.jpa.FullTextQuery jpaQuery
  = fullTextEntityManager.createFullTextQuery(query, Product.class);

Наконец, на шаге 4 мы выполним запрос:

List results = jpaQuery.getResultList();

Примечание : по умолчанию Lucene сортирует результаты по релевантности.

Шаги 1, 3 и 4 одинаковы для всех типов запросов.

Далее мы сосредоточимся на шаге 2, то есть на том, как создавать различные типы запросов.

6.2. Запросы по ключевым словам

Самый простой вариант использования-это поиск определенного слова .

Это то, что мы на самом деле сделали уже в предыдущем разделе:

Query keywordQuery = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();

Здесь ключевое слово() указывает, что мы ищем одно конкретное слово, on Field() сообщает Lucene, где искать, и matching() что искать.

6.3. Нечеткие запросы

Нечеткие запросы работают как запросы ключевых слов, за исключением того , что мы можем определить предел “нечеткости” , выше которого Lucene примет два термина как совпадающие.

С помощью с Расстоянием редактирования До() , мы можем определить, насколько термин может отклоняться от другого . Он может быть установлен в 0, 1 и 2, при этом значение по умолчанию равно 2 ( примечание : это ограничение исходит из реализации Lucene).

С помощью with Prefix Length () мы можем определить длину префикса , которая будет игнорироваться нечеткостью:

Query fuzzyQuery = queryBuilder
  .keyword()
  .fuzzy()
  .withEditDistanceUpTo(2)
  .withPrefixLength(0)
  .onField("productName")
  .matching("iPhaen")
  .createQuery();

6.4. Подстановочные запросы

Hibernate Search также позволяет нам выполнять подстановочные запросы, то есть запросы, для которых часть слова неизвестна.

Для этого мы можем использовать ” ?” для одного символа и ” *” для любой последовательности символов:

Query wildcardQuery = queryBuilder
  .keyword()
  .wildcard()
  .onField("productName")
  .matching("Z*")
  .createQuery();

6.5. Запросы фраз

Если мы хотим найти более одного слова, мы можем использовать запросы фраз. Мы можем либо искать точные или приблизительные предложения , используя фразу() и с помоями () , если это необходимо. Коэффициент наклона определяет количество других слов, разрешенных в предложении:

Query phraseQuery = queryBuilder
  .phrase()
  .withSlop(1)
  .onField("description")
  .sentence("with wireless charging")
  .createQuery();

6.6. Простые Строковые запросы Запросов

С предыдущими типами запросов мы должны были явно указать тип запроса.

Если мы хотим дать пользователю больше возможностей, мы можем использовать простые запросы строки запроса: таким образом, он может определять свои собственные запросы во время выполнения .

Поддерживаются следующие типы запросов:

  • логическое значение (И использование “+”, ИЛИ использование “|”, А НЕ использование “-“)
  • префикс (prefix*)
  • фраза (“какая-то фраза”)
  • приоритет (с использованием круглых скобок)
  • нечеткий (нечеткий~2)
  • оператор near для запросов фраз (“some phrase”~3)

В следующем примере будут объединены нечеткие, фразеологические и логические запросы:

Query simpleQueryStringQuery = queryBuilder
  .simpleQueryString()
  .onFields("productName", "description")
  .matching("Aple~2 + \"iPhone X\" + (256 | 128)")
  .createQuery();

6.7. Запросы диапазона

Запросы диапазона ищут значение | между заданными границами . Это может быть применено к числам, датам, меткам времени и строкам:

Query rangeQuery = queryBuilder
  .range()
  .onField("memory")
  .from(64).to(256)
  .createQuery();

6.8. Больше Похоже На Это.

Наш последний тип запроса – ” More Like This ” – query. Для этого мы предоставляем сущность, и Hibernate Search возвращает список с похожими сущностями , каждая из которых имеет оценку сходства.

Как уже упоминалось ранее, TermVector.YES атрибут в нашем классе моделей необходим для этого случая: он говорит Lucene хранить частоту для каждого термина во время индексации.

Исходя из этого, сходство будет вычислено во время выполнения запроса:

Query moreLikeThisQuery = queryBuilder
  .moreLikeThis()
  .comparingField("productName").boostedTo(10f)
  .andField("description").boostedTo(1f)
  .toEntity(entity)
  .createQuery();
List results = (List) fullTextEntityManager
  .createFullTextQuery(moreLikeThisQuery, Product.class)
  .setProjection(ProjectionConstants.THIS, ProjectionConstants.SCORE)
  .getResultList();

6.9. Поиск По Нескольким Полям

До сих пор мы создавали запросы только для поиска одного атрибута, используя on Field() .

В зависимости от варианта использования, мы также можем искать два или более атрибутов :

Query luceneQuery = queryBuilder
  .keyword()
  .onFields("productName", "description")
  .matching(text)
  .createQuery();

Кроме того, мы можем указать каждый атрибут для поиска отдельно , например, если мы хотим определить повышение для одного атрибута:

Query moreLikeThisQuery = queryBuilder
  .moreLikeThis()
  .comparingField("productName").boostedTo(10f)
  .andField("description").boostedTo(1f)
  .toEntity(entity)
  .createQuery();

6.10. Объединение запросов

Наконец, Hibernate Search также поддерживает объединение запросов с использованием различных стратегий:

  • SHOULD: запрос должен содержать соответствующие элементы подзапроса
  • MUST: запрос должен содержать соответствующие элементы подзапроса
  • MUST NOT : запрос не должен содержать совпадающих элементов подзапроса

Агрегации аналогичны логическим И, ИЛИ и НЕ . Однако названия разные, чтобы подчеркнуть, что они также оказывают влияние на релевантность.

Например, a SHOULD между двумя запросами аналогично boolean ИЛИ: если один из двух запросов имеет совпадение, то это совпадение будет возвращено.

Однако если оба запроса совпадают, то совпадение будет иметь более высокую релевантность по сравнению с совпадением только одного запроса:

Query combinedQuery = queryBuilder
  .bool()
  .must(queryBuilder.keyword()
    .onField("productName").matching("apple")
    .createQuery())
  .must(queryBuilder.range()
    .onField("memory").from(64).to(256)
    .createQuery())
  .should(queryBuilder.phrase()
    .onField("description").sentence("face id")
    .createQuery())
  .must(queryBuilder.keyword()
    .onField("productName").matching("samsung")
    .createQuery())
  .not()
  .createQuery();

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

В этой статье мы обсудили основы Hibernate Search и показали, как реализовать наиболее важные типы запросов. Более продвинутые темы можно найти в официальной документации .

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