Автор оригинала: 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 мы выполним запрос:
Listresults = 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
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 .