Hibernate Search 中文操作指南

11. Mapping index content to custom types (projection constructors)

11.1. Basics

Projections 允许将数据直接从匹配的文档中检索为搜索查询的结果。随着文档和投影结构变得越来越复杂, programmatic calls to the Projection DSL 也变得越来越复杂,这可能导致投影定义难以理解。

为了解决这个问题,Hibernate Search 提供了通过映射自定义类型(通常是记录)来定义投影的功能,方法是将 @ProjectionConstructor 注释应用于这些类型或它们的构造函数。然后执行这样的投影变得像 referencing the custom type 一样简单。

这样的投影是 composite,它们的内部投影(组件)是 inferred from the name and type of the projection constructors' parameters

在注释自定义投影类型时,需要注意一些约束:

如果自定义投影类型不在与实体类型相同的 JAR 中,则 Hibernate Search 将 require additional configuration

在对值字段或对象字段进行投影时,默认情况下,投影字段的路径从构造函数参数名称中推断,但 inference will fail if constructor parameter names are not included in the Java bytecode 。 或者,可以通过 @FieldProjection(path = …​) / @ObjectProjection(path = …​) 显式提供路径,在这种情况下,Hibernate Search 不会依赖于构造函数参数名称。

在对值字段进行投影时, field 投影的约束仍然适用。 特别是,对于 Lucene backend ,必须将涉及投影的值字段配置为 projectable

在对对象字段进行投影时, object 投影的约束仍然适用。 特别是,对于 Lucene backend ,必须将涉及投影的多值对象字段配置为 nested

示例 72. 使用自定义记录类型从索引中投影数据

@ProjectionConstructor (1)
public record MyBookProjection(
        @IdProjection Integer id, (2)
        String title, (3)
        List<Author> authors) { (4)
    @ProjectionConstructor (5)
    public record Author(String firstName, String lastName) {
    }
}
List<MyBookProjection> hits = searchSession.search( Book.class )
        .select( MyBookProjection.class ) (1)
        .where( f -> f.matchAll() )
        .fetchHits( 20 ); (2)

自定义的非记录类也可以用 @ProjectionConstructor 添加注解,如果您由于某些原因无法使用记录(例如您仍在使用 Java 13 或更低版本),这可能会很有用。

上面的示例执行等效于以下代码的投影:

示例 73. 与前一个示例等价的编程投影定义

List<MyBookProjection> hits = searchSession.search( Book.class )
        .select( f -> f.composite()
                .from(
                        f.id( Integer.class ),
                        f.field( "title", String.class ),
                        f.object( "authors" )
                                .from(
                                        f.field( "authors.firstName", String.class ),
                                        f.field( "authors.lastName", String.class )
                                )
                                .as( MyBookProjection.Author::new )
                                .multi()
                )
                .as( MyBookProjection::new ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

11.2. Detection of mapped projection types

Hibernate Search 必须在启动时了解投影类型,通常在使用 @ProjectionConstructor 对其进行批注后,它就会这么做,因为会扫描类路径。

有关类路径扫描及其(例如,扫描依赖项而不是仅扫描应用程序 JAR)的更多信息,请参见 Classpath scanning

11.3. Implicit inner projection inference

11.3.1. Basics

当构造函数参数未用 explicit projection annotations 注释时,Hibernate Search 会基于这些参数的名称和类型应用一些基本推论规则,以便选择(内部)投影。

以下部分说明如何定义构造函数参数的名称和类型以获得所需的投影。

11.3.2. Inner projection and type

当构造函数参数未用 explicit projection annotation 注释时,Hibernate Search 会根据相应构造函数参数的类型推论内部投影的类型。

你应该根据以下规则设置构造函数参数的类型:

  1. For a single-valued projection:

对于 projection on a value field (通常使用 @FullTextField / @GenericField /等映射),将参数类型设置为目标字段的 type of projected values ,该类型通常是注解了 @FullTextField / @GenericField /等的属性的类型。

对于 projection on an object field (通常使用 @IndexedEmbedded 映射),将参数类型设置为另一个通过 @ProjectionConstructor 注解的自定义类型,其构造函数将定义从该对象字段提取哪些字段。

  1. 对于 projection on a value field (通常使用 @FullTextField / @GenericField /等映射),将参数类型设置为目标字段的 type of projected values ,该类型通常是注解了 @FullTextField / @GenericField /等的属性的类型。

  2. 对于 projection on an object field (通常使用 @IndexedEmbedded 映射),将参数类型设置为另一个通过 @ProjectionConstructor 注解的自定义类型,其构造函数将定义从该对象字段提取哪些字段。

  3. 对于多值投影,遵循上述规则,然后使用 Iterable, CollectionList_包装类型,例如 _Iterable&lt;SomeType&gt;, Collection&lt;SomeType&gt;List&lt;SomeType&gt;。====== 用以表示多值投影的构造函数参数只能具有 Iterable<…​>Collection<…​>List<…​> 类型。

不支持 MapOptional 等其他容器类型 at the moment

==== 11.3.3. Inner projection and field path

当构造函数参数未用 explicit projection annotation 注释,或者用此注释但未提供明确路径时,Hibernate Search 会根据相应构造函数参数的名称推论要投影到的字段的路径。

在这种情况下,你应该将构造函数参数的名称(在 Java 代码中)设置为要投影到的字段的名称。

Hibernate Search 只能检索构造函数参数的名称:

对于记录类型的规范构造方法,无论编译标志如何。

对于非记录类型或仅当类型使用 -parameters 编译标志编译时,记录类型的非规范构造方法。

=== 11.4. Explicit inner projection

建構函數參數可以標註為明確投影標註,例如 @IdProjection@FieldProjection

对于通常为 inferred automatically 的投影,这允许进行进一步的自定义,例如,在 field projection 中明确设置目标字段路径或禁用值转换。或者,在 object projection 中,这也允许 breaking cycles of nested object projections

对于 identifier projection 等其他投影,这实际上是唯一在投影构造函数中使用它们的方法,因为它们永远不会自动推断。

有关要应用于投影构造函数参数的相应内置注释的更多信息,请参见 documentation of each projection

=== 11.5. Mapping types with multiple constructors

如果投影类型(记录或类)有多个构造函数,则不能在类型级别应用 @ProjectionConstructor 注解,而必须应用到希望用于投影的构造函数上。

示例 74. 使用 @ProjectionConstructor 注解特定构造函数

public class MyAuthorProjectionClassMultiConstructor {
    public final String firstName;
    public final String lastName;

    @ProjectionConstructor (1)
    public MyAuthorProjectionClassMultiConstructor(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public MyAuthorProjectionClassMultiConstructor(String fullName) { (2)
        this( fullName.split( " " )[0], fullName.split( " " )[1] );
    }

    // ... Equals and hashcode ...

}

在记录的情况下,也可以注解(隐式)规范构造函数,但它要求在代码中使用特定语法表示该构造函数:

示例 75. 使用 @ProjectionConstructor 注解规范构造函数

public record MyAuthorProjectionRecordMultiConstructor(String firstName, String lastName) {
    @ProjectionConstructor (1)
    public MyAuthorProjectionRecordMultiConstructor { (2)
    }

    public MyAuthorProjectionRecordMultiConstructor(String fullName) { (3)
        this( fullName.split( " " )[0], fullName.split( " " )[1] );
    }
}

=== 11.6. Programmatic mapping

您还可以在通过 programmatic mapping 映射投影构造函数。行为和选项与基于注释的映射相同。

示例 76. 使用 .projectionConstructor().projection(<binder>) 映射主投影构造函数

TypeMappingStep myBookProjectionMapping = mapping.type( MyBookProjection.class );
myBookProjectionMapping.mainConstructor()
        .projectionConstructor(); (1)
myBookProjectionMapping.mainConstructor().parameter( 0 )
        .projection( IdProjectionBinder.create() ); (2)
TypeMappingStep myAuthorProjectionMapping = mapping.type( MyBookProjection.Author.class );
myAuthorProjectionMapping.mainConstructor()
        .projectionConstructor();

如果投影类型(记录或类)有多个构造函数,则需要使用 .constructor(…​) 来替换 .mainConstructor(),将构造函数参数的(原始)类型作为参数传递。

示例 77. 使用 .projectionConstructor() 映射特定投影构造函数

mapping.type( MyAuthorProjectionClassMultiConstructor.class )
        .constructor( String.class, String.class )
        .projectionConstructor();