Определения индексов
Поисковые индексы задаются через Java-интерфейсы, которые описывают, какие сущности и атрибуты должны индексироваться. Рекомендуется создавать эти интерфейсы для каждой сущности, требующей функциональности полнотекстового поиска.
Создание определения индекса
Определение индекса создается как Java-интерфейс, аннотированный @JmixEntitySearchIndex. Вы можете создать его вручную или использовать поддержку Studio.
В Studio используйте узел Search Indexes в Окне инструментов Jmix, чтобы создать новое определение индекса с помощью мастера.
Studio группирует все интерфейсы, аннотированные @JmixEntitySearchIndex, в узле Search Indexes. Определения индексов, относящиеся к конкретной сущности, также отображаются в узле этой сущности.
Интерфейс определения индекса
Java-интерфейс, определяющий индекс, должен соответствовать следующим требованиям:
-
Может иметь произвольное имя.
-
Должен быть аннотирован аннотацией
@JmixEntitySearchIndex.
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
}
@JmixEntitySearchIndex
Атрибуты
-
entity- указывает класс сущности, связанный с этим определением индекса. Является обязательным. -
indexName- указывает полное имя индекса, которое будет использоваться для этой сущности. Является опциональным. Если не задано, имя индекса формируется на основе префикса и имени сущности.
Индексируемые свойства сущности задаются с помощью методов внутри интерфейса. Эти методы должны соответствовать следующим правилам:
-
Должны возвращать
void. -
Могут иметь произвольное имя.
-
Не должны принимать параметры.
-
Не должны содержать никакой логики реализации.
-
Каждый метод должен быть аннотирован аннотацией
@AutoMappedFieldдля статических атрибутов или@DynamicAttributesдля динамических атрибутов (см. Динамические атрибуты в поиске).
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
@AutoMappedField(includeProperties =
{"number", "product", "customer.status", "customer.lastName"})
void orderMapping();
}
Аннотация AutoMappedField
Аннотация @AutoMappedField позволяет сопоставлять определенные атрибуты сущности на основе их типа данных (смотрите ниже). Она может включать следующие параметры:
-
includeProperties- список атрибутов сущности для индексации. Принимает точечную нотацию для указания атрибутов связанных сущностей. По умолчанию никакие атрибуты не индексируются для поиска. -
excludeProperties- список атрибутов сущности, которые следует исключить из индексации. Принимает точечную нотацию для указания атрибутов связанных сущностей. По умолчанию никакие атрибуты не исключаются. -
analyzer- имя анализатора, определенного в Elasticsearch/OpenSearch, который будет использоваться в маппинге поля индекса. Если не указано, используется анализаторstandard. -
indexFileContent- логический флаг, определяющий, следует ли индексировать содержимое файла, найденного в сопоставленных файловых свойствах. По умолчанию установлен вtrue.
И includeProperties, и excludeProperties поддерживают подстановочный знак *. Он раскрывается в локальные свойства на соответствующем уровне:
-
*- локальные свойства индексируемой сущности. -
refField.*- локальные свойства сущности, на которую ссылается свойствоrefField.
Подстановочный знак не применяется к атрибутам обратной ссылки и атрибутам черт сущности, таких как version, createdBy и т.д.
excludeProperties полезен только тогда, когда includeProperties содержит подстановочный знак, чтобы ограничить его раскрытие. Например:
@AutoMappedField(
includeProperties = {"*", "customer.*"},
excludeProperties = {"number", "customer.firstName"})
void orderCustomerMapping();
Анализатор используется для изменения входящих текстовых значений различными способами, включая морфологии языков. Указанный анализатор используется как на этапе индексации, так и на этапе поиска.
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@AutoMappedField(
includeProperties = {"firstName", "lastName"},
analyzer = "german")
void customerMapping();
}
Несколько аннотаций маппинга могут быть применены к одному методу или распределены по нескольким методам для некой группировки. Примеры ниже иллюстрируют идентичное определение:
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
@AutoMappedField(includeProperties =
{"number", "product", "customer.status", "customer.lastName"})
void orderMapping();
}
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
@AutoMappedField(includeProperties = {"number", "product"})
@AutoMappedField(includeProperties = {"customer.status", "customer.lastName"})
void orderMapping();
}
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
@AutoMappedField(includeProperties = {"number", "product"})
void orderMapping();
@AutoMappedField(includeProperties = {"customer.status", "customer.lastName"})
void customerMapping();
}
Автоматический маппинг
Автоматический маппинг с помощью аннотации @AutoMappedField поддерживается для атрибутов сущности следующих типов:
Подстановочный знак охватывает все эти типы атрибутов.
Текстовые атрибуты
Эти атрибуты имеют тип String. Это наиболее распространенный случай, значение атрибута используется как индексируемое значение.
Поле в индексе выглядит следующим образом:
"textualFieldName": "value"
В случае нескольких значений:
"textualFieldName": ["value1", "value2"]
Ссылочные атрибуты
Это ссылки на связанные сущности. Индексируемое значение включает только имя экземпляра связанной сущности, опуская любые вложенные свойства. Чтобы индексировать вложенные свойства связанной сущности, обязательно явно включите refProperty.nestedProperty или refProperty.* в ваш маппинг.
Поле в индексе выглядит следующим образом:
"refFieldName": {
"_instance_name": "instanceNameValue"
}
В случае нескольких значений:
"refFieldName": {
"_instance_name": ["instanceNameValue1", "instanceNameValue2"]
}
Файлы
Это атрибуты типа FileRef, ссылающиеся на файлы в Файловом хранилище. По умолчанию и имя файла, и его содержимое используются как индексируемые значения. Если вы хотите индексировать только имя файла, вы должны изменить параметр indexFileContent в @AutoMappedField на false:
Индексация содержимого файлов поддерживается для следующих типов файлов:
-
PDF
-
DOC
-
XLS
-
DOCX
-
XLSX
-
ODT
-
ODS
-
RTF
-
TXT
@AutoMappedField(
includeProperties = {"*"},
indexFileContent = false)
void fileMapping();
Поле в индексе выглядит следующим образом:
"fileRefField": {
"_file_name" : "File name",
"_content" : "File content if enabled"
}
В случае нескольких значений:
"fileRefField": [
{
"_file_name" : "File name 1",
"_content" : "File content 1"
},
{
"_file_name" : "File name 2",
"_content" : "File content 2"
}
]
Атрибуты перечислений
В случае атрибутов перечислений индексируемые значения включают локализованные значения для всех доступных локалей.
Запись в индексе выглядит как:
"enumFieldName": ["enValue", "ruValue"]
Если присутствует несколько значений перечисления, каждое значение на всех доступных локалях включается, что приводит к умножению значений:
"enumFieldName": ["enValue1", "ruValue1", "enValue2", "ruValue2"]
Встроенные атрибуты
Это ссылки на встроенные JPA-сущности. Добавление встроенного атрибута подразумевает включение всех его вложенных атрибутов ("someEmbeddedProperty" = "someEmbeddedProperty.*"). Значение индекса определяется типом вложенного атрибута, а неподдерживаемые типы будут игнорироваться.
Например, представьте корневую сущность с атрибутом customer, связанным со встроенной сущностью Customer, которая содержит атрибуты firstName и lastName. Если вы решите включить атрибут customer, это автоматически приведет к включению атрибутов customer.firstName и customer.lastName.
Вложенные атрибуты и коллекции
Вы можете указывать вложенные свойства, используя точечную нотацию: refProperty.nestedRefProperty.targetDataProperty.
Кроме того, система поддерживает атрибуты-коллекции, включая вложенные коллекции на различных уровнях. В таких случаях индекс объединяет все значения атрибутов на самом нижнем уровне. Например, свойство типа collectionOfReferences.nestedCollectionOfAnotherReferences.name сохраняется в следующем формате:
"collectionOfEntityA": {
"nestedCollectionOfEntityB": {
"name": ["value1", ..., "valueN"]
}
}
Внутри массива вы найдете значения атрибута name из всех экземпляров EntityB внутри всех экземпляров EntityA в корневой сущности.
Динамические атрибуты
В дополнение к статическим атрибутам, заданным с помощью @AutoMappedField, дополнение Search может индексировать динамические атрибуты сущности. Добавьте аннотацию @DynamicAttributes к методу интерфейса определения индекса. См. Динамические атрибуты в поиске для параметров аннотации, поддерживаемых типов атрибутов и ограничений.
Программный маппинг
Вместо использования аннотаций вы можете построить определение маппинга программно.
Для этого вам нужно определить метод в вашем интерфейсе определения индекса. Этот метод должен удовлетворять следующим условиям:
-
Должен быть методом по умолчанию (default).
-
Может иметь произвольное имя.
-
Может принимать Spring-бины в качестве параметров для пользовательской конфигурации.
-
Должен возвращать тип
MappingDefinition. -
Должен быть аннотирован
@ManualMappingDefinition.
В теле метода вы можете создать определение маппинга, используя MappingDefinition.builder().
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition (1)
default MappingDefinition mapping(FilePropertyValueExtractor filePropertyValueExtractor, (2)
AutoMappingStrategy autoMappingStrategy,
SimplePropertyValueExtractor simplePropertyValueExtractor) {
return MappingDefinition.builder()
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("*") (3)
.excludeProperties("hobby", "maritalStatus", "firstName", "lastName") (4)
.withFieldMappingStrategyClass(AutoMappingStrategy.class) (5)
.withPropertyValueExtractor(filePropertyValueExtractor) (6)
.build()
)
.addStaticAttributesGroup( (7)
StaticAttributesGroupConfiguration.builder()
.includeProperties("firstName")
.withFieldMappingStrategy(autoMappingStrategy) (8)
.withFieldConfiguration( (9)
"{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"standard\",\n" +
" \"boost\": 2\n" +
"}"
)
.build()
)
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("lastName")
.withFieldConfiguration( (10)
"{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"english\"\n" +
"}"
)
.withPropertyValueExtractor(simplePropertyValueExtractor) (11)
.withOrder(1) (12)
.build()
)
.build();
}
}
| 1 | Аннотация @ManualMappingDefinition отмечает методы с ручным созданием определения маппинга. |
| 2 | Вы можете передавать Spring-бины в качестве параметров для пользовательской конфигурации маппинга. |
| 3 | Список свойств, которые должны быть индексированы. Здесь используется подстановочный знак * для включения всех локальных свойств индексируемой сущности Customer. |
| 4 | Список свойств, которые не должны индексироваться. firstName и lastName исключены здесь, потому что они настроены отдельно в следующих группах для тонкой настройки. |
| 5 | Класс реализации FieldMappingStrategy, который должен использоваться для маппинга свойств. Стратегия маппинга также может быть определена как экземпляр с помощью метода withFieldMappingStrategy() с экземпляром стратегии в качестве параметра. |
| 6 | Явный экстрактор значений свойств. Например, экземпляр FilePropertyValueExtractor может использоваться для обработки атрибутов типа FileRef. |
| 7 | Вторая группа, которая настраивает одно поле с помощью конфигурации поля по умолчанию. |
| 8 | Сначала применяет стратегию автоматического маппинга для получения базового маппинга. |
| 9 | Определяет базовую конфигурацию поля в виде JSON-строки, которая переопределяет и расширяет настройки, сгенерированные стратегией:
|
| 10 | Определяет конфигурацию поля напрямую, минуя любую стратегию. Поле lastName индексируется как текст с анализатором "english". |
| 11 | Обязательно, когда указана только базовая конфигурация (без стратегии): экстрактор, который читает значение свойства для индексации. |
| 12 | Устанавливает порядок этой группы. Это важно, когда группы иначе конфликтуют — группа с более высоким порядком побеждает. |
Пример выше определяет несколько групп статических атрибутов, включая базовые конфигурации полей для firstName и lastName. Чтобы индексировать динамические атрибуты программно, добавьте группу динамических атрибутов, используя MappingDefinition.builder().addDynamicAttributesGroup() с DynamicAttributesGroupConfiguration. Подробнее см. Программное определение индекса.
| В интерфейсе определения индекса может быть только один метод с программным маппингом. Если такой метод существует, все аннотации маппинга полей в интерфейсе игнорируются. |
FieldMappingStrategy
io.jmix.search.index.mapping.strategy.FieldMappingStrategy - базовый интерфейс для стратегии маппинга.
Определяет, как свойства сущности преобразуются в поля поискового индекса. Каждая стратегия определяет:
-
Какие типы свойств она поддерживает (метод
isSupported()). -
Как генерируется конфигурация поля для поискового движка.
-
Какой метод извлечения значений использовать.
-
Порядок выполнения, когда применяются несколько стратегий.
PropertyValueExtractor
io.jmix.search.index.mapping.propertyvalue.PropertyValueExtractor - функциональный интерфейс, отвечающий за извлечение значений свойств из экземпляров сущностей во время индексации. Он преобразует данные свойств сущности в формат JSON, который может быть проиндексирован поисковым движком. Используйте пользовательские реализации, когда вам нужна специализированная логика извлечения значений, выходящая за рамки стандартного маппинга свойств.
Расширенные возможности поиска
Аннотация @ExtendedSearch
Необязательная аннотация @ExtendedSearch может быть применена к интерфейсу определения индекса для включения функциональности префиксного поиска.
По умолчанию запросы Starts with ("начинается с") в поисковых системах могут быть ресурсоемкими при использовании шаблонов (wildcards) или регулярных выражений. Аннотация @ExtendedSearch предоставляет высокопроизводительную альтернативу за счет предварительной индексации префиксов слов.
Когда эта аннотация присутствует, система создает дополнительные "виртуальные" подполя для каждого "реального" поля, определенного в индексе. Эти подполя хранят подготовленные термины-префиксы, сгенерированные специализированным анализатором.
Как это работает:
Основной механизм включает в себя цепочку анализа (фильтры и анализаторы), применяемую к виртуальным подполям:
-
Генерация префиксов: Для каждого слова в индексированном тексте анализатор генерирует набор префиксов, начиная с минимальной длины и до максимальной.
-
Хранение: Эти префиксы хранятся в подполях.
-
Поиск: Когда пользователь выполняет поиск, система сопоставляет запрос с этими предварительно проиндексированными префиксами вместо выполнения вычисления по шаблону в реальном времени.
Детализация генерации префиксов контролируется через свойства приложения jmix.search.min-prefix-length и jmix.search.max-prefix-length.
Атрибуты:
-
enabled- глобально включает/отключает функциональность расширенного поиска. По умолчанию установлено вtrue. -
tokenizer- токенизатор, используемый в префиксном анализаторе. Значение по умолчанию -whitespace. -
additionalFilters- дополнительные фильтры анализа помимо стандартныхlowercaseиedge N-gram. По умолчанию:none.
Пример:
@ExtendedSearch(tokenizer = "letter",
additionalFilters = {"asciifolding"})
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
}
Предикат индексируемости
Процесс индексации может иметь дополнительное условие на уровне экземпляра. Оно может быть добавлено путем настройки предиката индексируемости (Indexable Predicate). Этот предикат применяется к каждому экземпляру сущности во время индексации и определяет, должен ли он быть проиндексирован.
Он не применяется во время удаления.
Чтобы настроить предикат индексируемости, добавьте метод, удовлетворяющий следующим требованиям:
-
С модификатором
default. -
С любым именем.
-
С возвращаемым типом -
Predicate<TargetEntity>, гдеTargetEntity- это значение параметраentity()текущей аннотации. -
С Spring-бинами, требуемыми для логики предиката, в качестве параметров.
-
Аннотированный
@IndexablePredicate.
Фактический предикат должен быть создан в теле метода и возвращен как результат.
| Переданный в предикат экземпляр включает только объявленные индексируемые свойства, остальные не подгружены. Чтобы получить к ним доступ, вам нужно перезагрузить экземпляр с подходящим фетч-планом внутри предиката. |
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@AutoMappedField(
includeProperties = {"firstName", "lastName"},
analyzer = "german")
void customerMapping();
@IndexablePredicate
default Predicate<Customer> indexCustomerWithGoldStatusOnlyPredicate(DataManager dataManager) {
return (instance) -> {
Id<Customer> id = Id.of(instance);
Customer reloadedInstance = dataManager.load(id)
.fetchPlanProperties("status")
.one();
Status status = reloadedInstance.getStatus();
return Status.GOLD.equals(status);
};
}
}