Определения индексов
Поисковые индексы задаются через Java-интерфейсы, которые описывают, какие сущности и атрибуты должны индексироваться. Рекомендуется создавать эти интерфейсы для каждой сущности, требующей функциональности полнотекстового поиска.
Интерфейс определения индекса
Java-интерфейс, определяющий индекс, должен соответствовать следующим требованиям:
-
Может иметь произвольное имя.
-
Должен быть аннотирован аннотацией
@JmixEntitySearchIndex.
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
}
@JmixEntitySearchIndex
Атрибуты
-
entity- указывает класс сущности, связанный с этим определением индекса. Является обязательным. -
indexName- указывает полное имя индекса, которое будет использоваться для этой сущности. Является опциональным. Если не задано, имя индекса формируется на основе префикса и имени сущности.
Индексируемые свойства сущности задаются с помощью методов внутри интерфейса. Эти методы должны соответствовать следующим правилам:
-
Должны возвращать
void. -
Могут иметь произвольное имя.
-
Не должны принимать параметры.
-
Не должны содержать никакой логики реализации.
-
Каждый метод должен быть аннотирован аннотацией
@AutoMappedField.
@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:
@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 в корневой сущности.
Программный маппинг
Вместо использования аннотаций вы можете построить определение маппинга программно.
Для этого вам нужно определить метод в вашем интерфейсе определения индекса. Этот метод должен удовлетворять следующим условиям:
-
Должен быть методом по умолчанию (default).
-
Может иметь произвольное имя.
-
Может принимать Spring-бины в качестве параметров для пользовательской конфигурации.
-
Должен возвращать тип
MappingDefinition. -
Должен быть аннотирован
@ManualMappingDefinition.
В теле метода вы можете создать определение маппинга, используя MappingDefinition.builder().
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition (1)
default MappingDefinition mapping(FilePropertyValueExtractor filePropertyValueExtractor) { (2)
return MappingDefinition.builder()
.addElement(
MappingDefinitionElement.builder()
.includeProperties("*") (3)
.excludeProperties("hobby", "maritalStatus") (4)
.withFieldMappingStrategyClass(AutoMappingStrategy.class) (5)
.withPropertyValueExtractor(filePropertyValueExtractor) (6)
.build()
)
.build();
}
}
| 1 | Аннотация @ManualMappingDefinition отмечает методы с ручным созданием определения маппинга. |
| 2 | Вы можете передавать Spring-бины в качестве параметров для пользовательской конфигурации маппинга. |
| 3 | Список свойств, которые должны быть индексированы. Здесь используется подстановочный знак * для включения всех локальных свойств индексируемой сущности Customer. |
| 4 | Список свойств, которые не должны индексироваться. |
| 5 | Класс реализации FieldMappingStrategy, который должен использоваться для маппинга свойств. Стратегия маппинга также может быть определена как экземпляр с помощью метода withFieldMappingStrategy() с экземпляром стратегии в качестве параметра. |
| 6 | Явный экстрактор значений свойств. Например, экземпляр FilePropertyValueExtractor может использоваться для обработки атрибутов типа FileRef. |
| Может быть только один метод с программным маппингом. Если метод с программным маппингом существует, все аннотации маппинга игнорируются. |
Предикат индексируемости
Процесс индексации может иметь дополнительное условие на уровне экземпляра. Оно может быть добавлено путем настройки предиката индексируемости (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);
};
}
}