索引定义

搜索的索引是通过 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 注解支持将实体属性根据其类型映射为 ES 索引(参见 下文)。该注解有下列参数:

  • includeProperties - 应该建立索引的实体属性列表。支持使用点标记指定关联实体的属性。默认不包含任何属性,即不建立任何属性的索引。

  • excludeProperties - 不需要建立索引的实体属性列表。支持使用点标记指定关联实体的属性。默认不包含任何属性。

  • analyzer - ES/OpenSearch 中定义的分析器(Analyzer)名称,用在索引字段映射中。如果未指定,则使用 standard 分析器。

  • indexFileContent - 定义是否对文件属性的文件内容建立索引。默认 true

includePropertiesexcludeProperties 都支持 * 通配符。该通配符将转换为相应级别的本地属性:

  • * - 索引实体的本地属性。

  • refField.* - refField 属性指定的关联实体的本地属性。

通配符不覆盖反向引用属性和实体 特性 属性:versioncreatedBy 等。

仅当 includeProperties 包含通配符时,使用 excludeProperties 比较方便,用于限制属性范围。示例:

@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.nestedPropertyrefProperty.*

索引字段类似这样:

"refFieldName": {
  "_instance_name": "instanceNameValue"
}

如果有多个值:

"refFieldName": {
  "_instance_name": ["instanceNameValue1", "instanceNameValue2"]
}

文件属性

这是 FileRef 类型的属性,指向 文件存储 中的文件。文件名和文件内容默认都会用作索引值。如果仅需对文件名建立索引,需要设置 @AutoMappedFieldindexFileContent 参数为 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 实体(具有 firstNamelastName 属性)。如果在映射中包含了 customer 属性,则会自动包含 customer.firstNamecustomer.lastName 属性。

内部属性和集合

内部嵌套属性可用点标记指定:refProperty.nestedRefProperty.targetDataProperty

此外,系统也支持集合属性,可以包含不同级别的内部集合属性。此时索引会存储最低一级属性的全部值。例如,collectionOfReferences.nestedCollectionOfAnotherReferences.name 保存如下:

"collectionOfEntityA": {
	"nestedCollectionOfEntityB": {
		"name": ["value1", ..., "valueN"]
	}
}

数组包含根实体中全部 EntityA 实例的全部 EntityB 实例的 name 属性值。

编程式映射

除了使用注解之外,还可以通过编程的方式构建映射定义。

需要在索引定义接口中创建满足下列条件的方法:

  • 是 default 方法。

  • 命名无限制。

  • 为了自定义配置,可以使用 Spring bean 作为参数。

  • 必须返回 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 bean 作为参数。
3 需要建立索引的属性列表。这里使用 * 通配符包含 Customer 实体的所有本地属性。
4 不需要建立索引的属性列表。
5 需要用 FieldMappingStrategy 的实现类做属性映射。映射策略也可以定义为类的实例,需要用 withFieldMappingStrategy() 方法设置,参数为映射策略的实例。
6 显式指定属性值解析器。例如,FilePropertyValueExtractor 可用于处理 FileRef 类型的属性。
只能有一个编程式映射的方法。如果存在此方法,则会忽略所有的映射注解。

可索引谓词

建立索引的过程中,可以使用额外的实体级别的条件。 条件是通过配置可索引谓词(Indexable Predicate)进行添加。在建立索引的过程中,这个谓词会在每个实体实例上做检查,判断实例是否能加入索引。

谓词不会在删除时使用。

如需配置可索引谓词,请添加满足下列条件的方法:

  • 是 default 方法

  • 命名无限制。

  • 返回类型为 Predicate<TargetEntity>,这里 'TargetEntity' 是当前注解中 entity() 参数的值。

  • 谓词逻辑需要的 Spring bean 可以作为参数。

  • @IndexablePredicate 注解。

在方法体中创建实际的谓词并作为结果返回。

传递给谓词的实体实例仅包含声明用于索引的属性,其他属性均未定义。如需访问其他属性,需要在谓词中用合适的 fetch plan 重新加载。
@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);
        };
    }
}