Hibernate Search 中文操作指南

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

11.1. Basics

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

Projections allow retrieving data directly from matched documents as the result of a search query. As the structure of documents and projections becomes more complex, so do programmatic calls to the Projection DSL, which can lead to overwhelming projection definitions.

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

To address this, Hibernate Search offers the ability to define projections through the mapping of custom types (typically records), by applying the @ProjectionConstructor annotation to those types or their constructor. Executing such a projection then becomes as easy as referencing the custom type.

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

Such projections are composite, their inner projections (components) being inferred from the name and type of the projection constructors' parameters.

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

There are a few constraints to keep in mind when annotating a custom projection type:

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

The custom projection type must be in the same JAR as entity types, or Hibernate Search will require additional configuration.

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

When projecting on value fields or object fields, the path to the projected field is inferred from the constructor parameter name by default, but inference will fail if constructor parameter names are not included in the Java bytecode. Alternatively the path can be provided explicitly through @FieldProjection(path = …​)/@ObjectProjection(path = …​), in which case Hibernate Search won’t rely on constructor parameter names.

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

When projecting on value fields, the constraints of the field projection still apply. In particular, with the Lucene backend, value fields involved in the projection must be configured as projectable.

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

When projecting on object fields, the constraints of the object projection still apply. In particular, with the Lucene backend, multi-valued object fields involved in the projection must be configured as nested.

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

. Example 72. Using a custom record type to project data from the index

@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 或更低版本),这可能会很有用。

Custom, non-record classes can also be annotated with @ProjectionConstructor, which can be useful if you cannot use records for some reason (for example because you’re still using Java 13 or below).

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

The example above executes a projection equivalent to the following code:

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

. Example 73. Programmatic projection definition equivalent to the previous example

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 对其进行批注后,它就会这么做,因为会扫描类路径。

Hibernate Search must know of projection types on startup, which it generally does as soon as they are annotated with @ProjectionConstructor, thanks to classpath scanning.

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

For more information about classpath scanning and how to tune it (for example to scan dependencies instead of just the application JAR), see Classpath scanning.

11.3. Implicit inner projection inference

11.3.1. Basics

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

When constructor parameters are not annotated with explicit projection annotations, Hibernate Search applies some basic inference rules based on the name and type of those parameters in order to select (inner) projections.

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

The following sections explain how to define the name and type of constructor parameters to get the desired projection.

11.3.2. Inner projection and type

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

When a constructor parameter is not annotated with an explicit projection annotation, Hibernate Search infers the type of the inner projection from the type of the corresponding constructor parameter.

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

You should set the type of a constructor parameter according to the following rules:

  1. For a single-valued projection:

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

For a projection on a value field (generally mapped using @FullTextField/@GenericField/etc.), set the parameter type to the type of projected values for the target field, which in general is the type of the property annotated with @FullTextField/@GenericField/etc.

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

For a projection on an object field (generally mapped using @IndexedEmbedded), set the parameter type to another custom type annotated with @ProjectionConstructor, whose constructor will define which fields to extract from that object field.

  1. For a projection on a value field (generally mapped using @FullTextField/@GenericField/etc.), set the parameter type to the type of projected values for the target field, which in general is the type of the property annotated with @FullTextField/@GenericField/etc.

  2. For a projection on an object field (generally mapped using @IndexedEmbedded), set the parameter type to another custom type annotated with @ProjectionConstructor, whose constructor will define which fields to extract from that object field.

  3. For a multivalued projection, follow the rules above then wrap the type with Iterable, Collection or List, e.g. Iterable<SomeType>, Collection<SomeType> or List<SomeType>.[.iokays-translated-91ee4877ea1a6fc4d021421daa256224]

用以表示多值投影的构造函数参数只能具有 Iterable<…​>Collection<…​>List<…​> 类型。

Constructor parameters meant to represent a multivalued projection can only have the type Iterable<…​>, Collection<…​> or List<…​>.

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

Other container types such as Map or Optional are not supported at the moment.

11.3.3. Inner projection and field path

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

When a constructor parameter is not annotated with an explicit projection annotation or when it is but that annotation does not provide an explicit path, Hibernate Search infers the path of the field to project on from the name of the corresponding constructor parameter.

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

In that case, you should set the name of a constructor parameter (in the Java code) to the name of the field to project on.

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

Hibernate Search can only retrieve the name of the constructor parameter:

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

For the canonical constructor of record types, regardless of compiler flags.

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

For constructors of non-record types or non-canonical constructors of record types if and only if the type was compiled with the -parameters compiler flag.

11.4. Explicit inner projection

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

Constructor parameters can be annotated with explicit projection annotations such as @IdProjection or @FieldProjection.

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

For projections that would normally be inferred automatically, this allows further customization, for example in a field projection to set the target field path explicitly or to disable value conversion. Alternatively, in an object projection, this also allows breaking cycles of nested object projections.

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

For other projections such as identifier projection, this is actually the only way to use them in a projection constructor, because they would never be inferred automatically.

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

See the documentation of each projection for more information about the corresponding built-in annotation to be applied to projection constructor parameters.

11.5. Mapping types with multiple constructors

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

If the projection type (record or class) has multiple constructors, the @ProjectionConstructor annotation cannot be applied at the type level and must be applied to the constructor you wish to use for projections.

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

. Example 74. Annotating a specific constructor with @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 ...

}

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

In the case of records, the (implicit) canonical constructor can also be annotated, but it requires representing that constructor in the code with a specific syntax:

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

. Example 75. Annotating the canonical constructor with @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 映射投影构造函数。行为和选项与基于注释的映射相同。

You can map projection constructors through the programmatic mapping too. Behavior and options are identical to annotation-based mapping.

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

. Example 76. Mapping the main projection constructor with .projectionConstructor() and .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(),将构造函数参数的(原始)类型作为参数传递。

If the projection type (record or class) has multiple constructors, you will need to use .constructor(…​) instead of .mainConstructor(), passing the (raw) type of the constructor parameters as arguments.

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

. Example 77. Mapping a specific projection constructor with .projectionConstructor()

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