Hibernate Search 中文操作指南
12. Binding and bridges
12.1. Basics
在 Hibernate Search 中, binding是将自定义组件分配给域模型的过程。
In Hibernate Search, binding is the process of assigning custom components to the domain model.
可以绑定的最直观的组件是桥接器,负责将实体模型中的数据块转换为文档模型。
The most intuitive components that can be bound are bridges, responsible for converting pieces of data from the entity model to the document model.
例如,当 @GenericField 应用到自定义枚举类型的属性时,在编制索引时,将使用内置桥接器将此枚举转换为字符串,并在投影时将该字符串转换回枚举。
For example, when @GenericField is applied to a property of a custom enum type, a built-in bridge will be used to convert this enum to a string when indexing, and to convert the string back to an enum when projecting.
类似地,当类型为 Long 的实体标识符映射到文档标识符时,在编制索引时,将使用内置桥接器将 Long 转换为 String(因为所有文档标识符都是字符串),并在从搜索结果加载时将 String 转换为 Long。
Similarly, when an entity identifier of type Long is mapped to a document identifier, a built-in bridge will be used to convert the Long to a String (since all document identifiers are strings) when indexing, and back from a String to a Long when loading search results.
桥接不限于一对一映射:例如, @GeoPointBinding 注解(将用 @Latitude 和 @Longitude 标注的两个属性映射到一个字段)由另一个内置桥接支持。
Bridges are not limited to one-to-one mapping: for example, the @GeoPointBinding annotation, which maps two properties annotated with @Latitude and @Longitude to a single field, is backed by another built-in bridge.
虽然为各种标准类型提供了内置桥接器,但它们可能不足以满足复杂模型的需求。这就是为什么桥接器非常有用的原因:可以实现自定义桥接器并将其引用到 Hibernate Search 映射中。使用自定义桥接器,可以映射自定义类型,甚至是需要在编制索引时执行用户代码的复杂类型。
While built-in bridges are provided for a wide range of standard types, they may not be enough for complex models. This is why bridges are really useful: it is possible to implement custom bridges and to refer to them in the Hibernate Search mapping. Using custom bridges, custom types can be mapped, even complex types that require user code to execute at indexing time.
有多种类型的桥接器,详情将在下一节中详细介绍。如果您需要实现自定义桥接器,但又不太清楚需要哪种类型的桥接器,下表可能会有所帮助:
There are multiple types of bridges, detailed in the next sections. If you need to implement a custom bridge, but don’t quite know which type of bridge you need, the following table may help:
表 5. 现有桥接器类型的比较
Table 5. Comparison of available bridge types
Bridge type |
ValueBridge |
PropertyBridge |
TypeBridge |
IdentifierBridge |
RoutingBridge |
Applied to… |
Class field or getter |
Class field or getter |
Class |
Class field or getter (usually entity ID) |
Class |
Maps to… |
One index field. Value field only: integer, text, geopoint, etc. No object field (composite). |
One index field or more. Value fields as well as object fields (composite). |
One index field or more. Value fields as well as object fields (composite). |
Document identifier |
Route (conditional indexing, routing key) |
Built-in annotation(s) |
@PropertyBinding |
@TypeBinding |
@DocumentId |
@Indexed( routingBinder = … ) |
|
Supports container extractors |
Yes |
No |
No |
No |
No |
Supports mutable types |
No |
Yes |
Yes |
No |
Yes |
但并非所有粘合剂都与索引相关。 projection constructors中涉及的构造函数参数也可以绑定;可以在 this section中的更多信息。
Not all binders are about indexing, however. The constructor parameters involved in projection constructors can be bound as well; you will find more information about that in this section.
12.2. Value bridge
12.2.1. Basics
值桥接是一个可插入组件,实现从属性到索引字段的映射。它应用于具有 @*Field annotation ( @GenericField 、 @FullTextField ,…)或具有 custom annotation 的属性。
A value bridge is a pluggable component that implements the mapping of a property to an index field. It is applied to a property with a @*Field annotation (@GenericField, @FullTextField, …) or with a custom annotation.
值桥的实现相对直接:在最简单的形式中,它可以归结为将值从属性类型转换为索引字段类型。由于集成到 @*Field 注释中,一些特性免费提供:
A value bridge is relatively straightforward to implement: in its simplest form, it boils down to converting a value from the property type to the index field type. Thanks to the integration to the @*Field annotations, several features come for free:
-
The type of the index field can be customized directly in the @*Field annotation: it can be defined as sortable, projectable, it can be assigned an analyzer, …
-
The bridge can be transparently applied to elements of a container. For example, you can implement a ValueBridge<ISBN, String> and transparently use it on a property of type List<ISBN>: the bridge will simply be applied once per list element and populate the index field with as many values.
然而,由于这些特性,对值桥施加了几个限制,例如 property bridge中不存在这些限制:
However, due to these features, several limitations are imposed on a value bridge which are not present in a property bridge for example:
-
A value bridge only allows one-to-one mapping: one property to one index field. A single value bridge cannot populate more than one index field.
-
A value bridge will not work correctly when applied to a mutable type. A value bridge is expected to be applied to "atomic" data, such as a LocalDate; if it is applied to an entity, for example, extracting data from its properties, Hibernate Search will not be aware of which properties are used and will not be able to detect that reindexing is required when these properties change.
下面是一个自定义值桥示例,它将自定义 ISBN 类型转换为其字符串表示以对其进行索引:
Below is an example of a custom value bridge that converts a custom ISBN type to its string representation to index it:
. Example 78. Implementing and using a ValueBridge
public class ISBNValueBridge implements ValueBridge<ISBN, String> { (1)
@Override
public String toIndexedValue(ISBN value, ValueBridgeToIndexedValueContext context) { (2)
return value == null ? null : value.getStringValue();
}
}
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
@Convert(converter = ISBNAttributeConverter.class) (1)
@KeywordField( (2)
valueBridge = @ValueBridgeRef(type = ISBNValueBridge.class), (3)
normalizer = "isbn" (4)
)
private ISBN isbn;
// Getters and setters
// ...
}
{
"isbn": "978-0-58-600835-5"
}
以上示例只是一个最小的实现。自定义值桥还能执行更多操作:
The example above is just a minimal implementations. A custom value bridge can do more:
有关详细信息,请参见下一部分。
See the next sections for more information.
12.2.2. Type resolution
默认情况下,值桥的属性类型和索引字段类型使用反射自动确定,以提取 ValueBridge 接口的泛型类型参数:第一个参数是属性类型,而第二个参数是索引字段类型。
By default, the value bridge’s property type and index field type are determined automatically, using reflection to extract the generic type arguments of the ValueBridge interface: the first argument is the property type while the second argument is the index field type.
例如,在 public class MyBridge implements ValueBridge<ISBN, String> 中,属性类型被解析为 ISBN,索引字段类型被解析为 String:该桥将应用于 ISBN 类型的属性,并填充 String 类型的索引字段。
For example, in public class MyBridge implements ValueBridge<ISBN, String>, the property type is resolved to ISBN and the index field type is resolved to String: the bridge will be applied to properties of type ISBN and will populate an index field of type String.
使用反射自动解析类型这一事实带来了一些限制。特别是,这意味着泛型类型参数不可能是任何类型;通常,您应该坚持使用明确类型 (MyBridge implements ValueBridge<ISBN, String>) 并避免使用泛型类型参数和通配符 (MyBridge<T> implements ValueBridge<List<T>, T>)。
The fact that types are resolved automatically using reflection brings a few limitations. In particular, it means the generic type arguments cannot be just anything; as a general rule, you should stick to literal types (MyBridge implements ValueBridge<ISBN, String>) and avoid generic type parameters and wildcards (MyBridge<T> implements ValueBridge<List<T>, T>).
如果你需要更复杂类型,你可以绕过自动解析并使用 ValueBinder 明确指定类型。
If you need more complex types, you can bypass the automatic resolution and specify types explicitly using a ValueBinder.
12.2.3. Using value bridges in other @*Field annotations
为了将自定义值桥与专业注释(例如 @FullTextField)一起使用,该桥必须声明一个兼容的索引字段类型。
In order to use a custom value bridge with specialized annotations such as @FullTextField, the bridge must declare a compatible index field type.
例如:
For example:
-
@FullTextField and @KeywordField require an index field type of type String (ValueBridge<Whatever, String>);
-
@ScaledNumberField requires an index field type of type BigDecimal (ValueBridge<Whatever, BigDecimal>) or BigInteger (ValueBridge<Whatever, BigInteger>).
请参阅 Available field annotations以了解每个注释的特定约束。
Refer to Available field annotations for the specific constraints of each annotation.
尝试使用声明不兼容类型的桥会在引导时引发异常。
Attempts to use a bridge that declares an incompatible type will trigger exceptions at bootstrap.
12.2.4. Supporting projections with fromIndexedValue()
默认情况下,尝试使用自定义桥在字段上投影将导致异常,因为 Hibernate Search 不知道如何将从索引获得的投影值转换回属性类型。
By default, any attempt to project on a field using a custom bridge will result in an exception, because Hibernate Search doesn’t know how to convert the projected values obtained from the index back to the property type.
可以 disable conversion explicitly以从索引中获取原始值,但解决问题的另一种方法是在自定义桥接器中简单地实现 fromIndexedValue。将在需要转换投影值时调用此方法。
It is possible to disable conversion explicitly to get the raw value from the index, but another way of solving the problem is to simply implement fromIndexedValue in the custom bridge. This method will be called whenever a projected value needs to be converted.
. Example 79. Implementing fromIndexedValue to convert projected values
public class ISBNValueBridge implements ValueBridge<ISBN, String> {
@Override
public String toIndexedValue(ISBN value, ValueBridgeToIndexedValueContext context) {
return value == null ? null : value.getStringValue();
}
@Override
public ISBN fromIndexedValue(String value, ValueBridgeFromIndexedValueContext context) {
return value == null ? null : ISBN.parse( value ); (1)
}
}
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
@Convert(converter = ISBNAttributeConverter.class) (1)
@KeywordField( (2)
valueBridge = @ValueBridgeRef(type = ISBNValueBridge.class), (3)
normalizer = "isbn",
projectable = Projectable.YES (4)
)
private ISBN isbn;
// Getters and setters
// ...
}
12.2.5. Parsing the string representation to an index field type with parse()
默认情况下,在使用自定义桥接时,某些 Hibernate Search 功能(如指定 @*Field 注解的 indexNullAs 属性,或在查询字符串谓词( simpleQueryString() / queryString() )中使用具有此类自定义桥接的字段)将无法使用本地后端(例如 Lucene)。
By default, when a custom bridge is used, some Hibernate Search features like specifying the indexNullAs attribute of @*Field annotations, or using a field with such a custom bridge in query string predicates (simpleQueryString()/queryString()) with local backends (e.g. Lucene), will not work out of the box.
为了使其正常工作,该桥需要实现 parse 方法,以便 Hibernate Search 可以将字符串表示转换为索引字段的正确类型的值。
In order to make it work, the bridge needs to implement the parse method so that Hibernate Search can convert the string representation to a value of the correct type for the index field.
. Example 80. Implementing parse
public class ISBNValueBridge implements ValueBridge<ISBN, String> {
@Override
public String toIndexedValue(ISBN value, ValueBridgeToIndexedValueContext context) {
return value == null ? null : value.getStringValue();
}
@Override
public String parse(String value) {
// Just check the string format and return the string
return ISBN.parse( value ).getStringValue(); (1)
}
}
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
@Convert(converter = ISBNAttributeConverter.class) (1)
@KeywordField( (2)
valueBridge = @ValueBridgeRef(type = ISBNValueBridge.class), (3)
normalizer = "isbn",
indexNullAs = "000-0-00-000000-0" (4)
)
private ISBN isbn;
// Getters and setters
// ...
}
List<Book> result = searchSession.search( Book.class )
.where( f -> f.queryString().field( "isbn" )
.matching( "978-0-13-468599-1" ) ) (1)
.fetchHits( 20 );
12.2.6. Compatibility across indexes with isCompatibleWith()
值桥参与索引以及各种搜索 DSL,以将传递给 DSL 的值转换为后端将理解的索引字段值。
A value bridges is involved in indexing, but also in the various search DSLs, to convert values passed to the DSL to an index field value that the backend will understand.
在创建跨多个索引定位单个字段的谓词时,Hibernate Search 将有多个桥可供选择:每个索引一个。由于只能创建具有单个值的单个谓词,因此 Hibernate Search 需要选择一个桥。默认情况下,当将自定义桥分配给字段时,Hibernate Search 将抛出异常,因为它无法决定选择哪个桥。
When creating a predicate targeting a single field across multiple indexes, Hibernate Search will have multiple bridges to choose from: one per index. Since only one predicate with a single value can be created, Hibernate Search needs to pick a single bridge. By default, when a custom bridge is assigned to the field, Hibernate Search will throw an exception because it cannot decide which bridge to pick.
如果分配给所有索引中字段的桥接器生成相同的结果,则可以通过实现 isCompatibleWith 向 Hibernate Search 指示任何桥接器都可以执行操作。
If the bridges assigned to the field in all indexes produce the same result, it is possible to indicate to Hibernate Search that any bridge will do by implementing isCompatibleWith.
此方法接受参数中的另一个桥接器,并且如果预期该桥接器始终与 this 的行为相同,则返回 true。
This method accepts another bridge in parameter, and returns true if that bridge can be expected to always behave the same as this.
. Example 81. Implementing isCompatibleWith to support multi-index search
public class ISBNValueBridge implements ValueBridge<ISBN, String> {
@Override
public String toIndexedValue(ISBN value, ValueBridgeToIndexedValueContext context) {
return value == null ? null : value.getStringValue();
}
@Override
public boolean isCompatibleWith(ValueBridge<?, ?> other) { (1)
return getClass().equals( other.getClass() );
}
}
12.2.7. Configuring the bridge more finely with ValueBinder
若要更精细地配置桥接器,可以实现将在引导时执行的值绑定器。此绑定器尤其能够定义自定义索引字段类型。
To configure a bridge more finely, it is possible to implement a value binder that will be executed at bootstrap. This binder will be able in particular to define a custom index field type.
. Example 82. Implementing a ValueBinder
public class ISBNValueBinder implements ValueBinder { (1)
@Override
public void bind(ValueBindingContext<?> context) { (2)
context.bridge( (3)
ISBN.class, (4)
new ISBNValueBridge(), (5)
context.typeFactory() (6)
.asString() (7)
.normalizer( "isbn" ) (8)
);
}
private static class ISBNValueBridge implements ValueBridge<ISBN, String> {
@Override
public String toIndexedValue(ISBN value, ValueBridgeToIndexedValueContext context) {
return value == null ? null : value.getStringValue(); (9)
}
}
}
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
@Convert(converter = ISBNAttributeConverter.class) (1)
@KeywordField( (2)
valueBinder = @ValueBinderRef(type = ISBNValueBinder.class), (3)
sortable = Sortable.YES (4)
)
private ISBN isbn;
// Getters and setters
// ...
}
使用带专用 @*Field 注解的值绑定器时,索引字段类型必须与注解兼容。 |
When using a value binder with a specialized @*Field annotation, the index field type must be compatible with the annotation. |
例如, @FullTextField 仅在使用 asString() 创建索引字段类型时才起作用。
For example, @FullTextField will only work if the index field type was created using asString().
这些限制与直接分配值桥的限制类似;请参阅 Using value bridges in other @*Field annotations 。
These restrictions are similar to those when assigning a value bridge directly; see Using value bridges in other @*Field annotations.
12.2.8. Passing parameters
值桥通常使用内置 @*Field annotation 应用,后者已接受参数以分别配置字段名、字段是否可排序等。
The value bridges are usually applied with built-in @*Field annotation, which already accept parameters to configure the field name, whether the field is sortable, etc.
但是,这些参数不会传递给值桥接器或值绑定器。有两种方法可以将参数传递给值桥接器:
However, these parameters are not passed to the value bridge or value binder. There are two ways to pass parameters to value bridges:
-
One is (mostly) limited to string parameters, but is trivial to implement.
-
The other can allow any type of parameters, but requires you to declare your own annotations.
Simple, string parameters
可以为 @ValueBinderRef 注释定义字符串参数,然后在绑定器中使用这些参数:
You can define string parameters to the @ValueBinderRef annotation and then use them later in the binder:
. Example 83. Passing parameters to a ValueBridge using the @ValueBinderRef annotation
public class BooleanAsStringBridge implements ValueBridge<Boolean, String> { (1)
private final String trueAsString;
private final String falseAsString;
public BooleanAsStringBridge(String trueAsString, String falseAsString) { (2)
this.trueAsString = trueAsString;
this.falseAsString = falseAsString;
}
@Override
public String toIndexedValue(Boolean value, ValueBridgeToIndexedValueContext context) {
if ( value == null ) {
return null;
}
return value ? trueAsString : falseAsString;
}
}
public class BooleanAsStringBinder implements ValueBinder {
@Override
public void bind(ValueBindingContext<?> context) {
String trueAsString = context.params().get( "trueAsString", String.class ); (1)
String falseAsString = context.params().get( "falseAsString", String.class );
context.bridge( Boolean.class, (2)
new BooleanAsStringBridge( trueAsString, falseAsString ) );
}
}
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
private String title;
@GenericField(valueBinder = @ValueBinderRef(type = BooleanAsStringBinder.class, (1)
params = {
@Param(name = "trueAsString", value = "yes"),
@Param(name = "falseAsString", value = "no")
}))
private boolean published;
@ElementCollection
@GenericField(valueBinder = @ValueBinderRef(type = BooleanAsStringBinder.class, (2)
params = {
@Param(name = "trueAsString", value = "passed"),
@Param(name = "falseAsString", value = "failed")
}), name = "censorshipAssessments_allYears")
private Map<Year, Boolean> censorshipAssessments = new HashMap<>();
// Getters and setters
// ...
}
Parameters with custom annotations
您可以通过定义具有属性的 custom annotation将任何类型的参数传递给桥接器:
You can pass parameters of any type to the bridge by defining a custom annotation with attributes:
. Example 84. Passing parameters to a ValueBridge using a custom annotation
public class BooleanAsStringBridge implements ValueBridge<Boolean, String> { (1)
private final String trueAsString;
private final String falseAsString;
public BooleanAsStringBridge(String trueAsString, String falseAsString) { (2)
this.trueAsString = trueAsString;
this.falseAsString = falseAsString;
}
@Override
public String toIndexedValue(Boolean value, ValueBridgeToIndexedValueContext context) {
if ( value == null ) {
return null;
}
return value ? trueAsString : falseAsString;
}
}
@Retention(RetentionPolicy.RUNTIME) (1)
@Target({ ElementType.METHOD, ElementType.FIELD }) (2)
@PropertyMapping(processor = @PropertyMappingAnnotationProcessorRef( (3)
type = BooleanAsStringField.Processor.class
))
@Documented (4)
@Repeatable(BooleanAsStringField.List.class) (5)
public @interface BooleanAsStringField {
String trueAsString() default "true"; (6)
String falseAsString() default "false";
String name() default ""; (7)
ContainerExtraction extraction() default @ContainerExtraction(); (7)
@Documented
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@interface List {
BooleanAsStringField[] value();
}
class Processor (8)
implements PropertyMappingAnnotationProcessor<BooleanAsStringField> { (9)
@Override
public void process(PropertyMappingStep mapping, BooleanAsStringField annotation,
PropertyMappingAnnotationProcessorContext context) {
BooleanAsStringBridge bridge = new BooleanAsStringBridge( (10)
annotation.trueAsString(), annotation.falseAsString()
);
mapping.genericField( (11)
annotation.name().isEmpty() ? null : annotation.name()
)
.valueBridge( bridge ) (12)
.extractors( (13)
context.toContainerExtractorPath( annotation.extraction() )
);
}
}
}
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
private String title;
@BooleanAsStringField(trueAsString = "yes", falseAsString = "no") (1)
private boolean published;
@ElementCollection
@BooleanAsStringField( (2)
name = "censorshipAssessments_allYears",
trueAsString = "passed", falseAsString = "failed"
)
private Map<Year, Boolean> censorshipAssessments = new HashMap<>();
// Getters and setters
// ...
}
12.2.9. Accessing the ORM session or session factory from the bridge
此功能仅可通过 Hibernate ORM integration 使用。 |
This feature is only available with the Hibernate ORM integration. |
尤其不能与 Standalone POJO Mapper 一起使用。
It cannot be used with the Standalone POJO Mapper in particular.
传递到桥接方法的上下文可用于检索 Hibernate ORM 会话或会话工厂。
Contexts passed to the bridge methods can be used to retrieve the Hibernate ORM session or session factory.
. Example 85. Retrieving the ORM session or session factory from a ValueBridge
public class MyDataValueBridge implements ValueBridge<MyData, String> {
@Override
public String toIndexedValue(MyData value, ValueBridgeToIndexedValueContext context) {
SessionFactory sessionFactory = context.extension( HibernateOrmExtension.get() ) (1)
.sessionFactory(); (2)
// ... do something with the factory ...
}
@Override
public MyData fromIndexedValue(String value, ValueBridgeFromIndexedValueContext context) {
Session session = context.extension( HibernateOrmExtension.get() ) (3)
.session(); (4)
// ... do something with the session ...
}
}
12.2.10. Injecting beans into the value bridge or value binder
使用 compatible frameworks,Hibernate Search 支持将 bean 注入到 _ValueBridge_和 _ValueBinder_中。
With compatible frameworks, Hibernate Search supports injecting beans into both the ValueBridge and the ValueBinder.
传递到值粘合器的 bind 方法的上下文还公开一个 beanResolver() 方法来访问 bean 解析器并显式实例化 bean。 |
The context passed to the value binder’s bind method also exposes a beanResolver() method to access the bean resolver and instantiate beans explicitly. |
有关更多详细信息,请参见 Bean injection。
See Bean injection for more details.
12.2.11. Programmatic mapping
您也可以通过 programmatic mapping应用值桥。只需传递桥接器的实例。
You can apply a value bridge through the programmatic mapping too. Just pass an instance of the bridge.
. Example 86. Applying a ValueBridge with .valueBridge(…)
TypeMappingStep bookMapping = mapping.type( Book.class );
bookMapping.indexed();
bookMapping.property( "isbn" )
.keywordField().valueBridge( new ISBNValueBridge() );
同样,您可以传递绑定器实例。可以通过该绑定器的构造函数或通过设置器传递参数。
Similarly, you can pass a binder instance. You can pass arguments either through the binder’s constructor or through setters.
. Example 87. Applying a ValueBinder with .valueBinder(…)
TypeMappingStep bookMapping = mapping.type( Book.class );
bookMapping.indexed();
bookMapping.property( "isbn" )
.genericField()
.valueBinder( new ISBNValueBinder() )
.sortable( Sortable.YES );
12.2.12. Incubating features
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
传递给值绑定器 bind 方法的上下文公开了一个 bridgedElement() 方法,该方法提供对正在绑定的值(特别是其类型)的元数据访问。
The context passed to the value binder’s bind method exposes a bridgedElement() method that gives access to metadata about the value being bound, in particular its type.
有关更多信息,请参见 javadoc。
See the javadoc for more information.
12.3. Property bridge
12.3.1. Basics
属性桥类似于 value bridge,它是一个可插入组件,用于实现将属性映射到一个或多个索引字段。它应用于具有 _@PropertyBinding_注释或 custom annotation的属性。
A property bridge, like a value bridge, is a pluggable component that implements the mapping of a property to one or more index fields. It is applied to a property with the @PropertyBinding annotation or with a custom annotation.
与值桥接器相比,属性桥接器的实现更复杂,但涵盖更广泛的用例:
Compared to the value bridge, the property bridge is more complex to implement, but covers a broader range of use cases:
-
A property bridge can map a single property to more than one index field.
-
A property bridge can work correctly when applied to a mutable type, provided it is implemented correctly.
然而,由于其相当灵活的性质,属性桥接器并不能透明地提供值桥接器免费提供的全部功能。它们可以得到支持,但必须手动实现。这尤其包括容器提取器,其不能与属性桥接器结合:属性桥接器必须显式地提取容器值。
However, due to its rather flexible nature, the property bridge does not transparently provide all the features that come for free with a value bridge. They can be supported, but have to be implemented manually. This includes in particular container extractors, which cannot be combined with a property bridge: the property bridge must extract container values explicitly.
实现属性桥接器需要两个组件:
Implementing a property bridge requires two components:
-
A custom implementation of PropertyBinder, to bind the bridge to a property at bootstrap. This involves declaring the parts of the property that will be used, declaring the index fields that will be populated along with their type, and instantiating the property bridge.
-
A custom implementation of PropertyBridge, to perform the conversion at runtime. This involves extracting data from the property, transforming it if necessary, and pushing it to index fields.
以下是一个自定义属性桥接器的示例,该桥接器将发票行项目列表映射到多个字段,这些字段汇总了该发票。
Below is an example of a custom property bridge that maps a list of invoice line items to several fields summarizing the invoice.
. Example 88. Implementing and using a PropertyBridge
public class InvoiceLineItemsSummaryBinder implements PropertyBinder { (1)
@Override
public void bind(PropertyBindingContext context) { (2)
context.dependencies() (3)
.use( "category" )
.use( "amount" );
IndexSchemaObjectField summaryField = context.indexSchemaElement() (4)
.objectField( "summary" );
IndexFieldType<BigDecimal> amountFieldType = context.typeFactory() (5)
.asBigDecimal().decimalScale( 2 ).toIndexFieldType();
context.bridge( (6)
List.class, (7)
new Bridge( (8)
summaryField.toReference(), (9)
summaryField.field( "total", amountFieldType ).toReference(), (10)
summaryField.field( "books", amountFieldType ).toReference(), (10)
summaryField.field( "shipping", amountFieldType ).toReference() (10)
)
);
}
// ... class continues below
// ... class InvoiceLineItemsSummaryBinder (continued)
private static class Bridge (1)
implements PropertyBridge<List> { (2)
private final IndexObjectFieldReference summaryField;
private final IndexFieldReference<BigDecimal> totalField;
private final IndexFieldReference<BigDecimal> booksField;
private final IndexFieldReference<BigDecimal> shippingField;
private Bridge(IndexObjectFieldReference summaryField, (3)
IndexFieldReference<BigDecimal> totalField,
IndexFieldReference<BigDecimal> booksField,
IndexFieldReference<BigDecimal> shippingField) {
this.summaryField = summaryField;
this.totalField = totalField;
this.booksField = booksField;
this.shippingField = shippingField;
}
@Override
public void write(DocumentElement target, List bridgedElement, PropertyBridgeWriteContext context) { (4)
List<InvoiceLineItem> lineItems = (List<InvoiceLineItem>) bridgedElement;
BigDecimal total = BigDecimal.ZERO;
BigDecimal books = BigDecimal.ZERO;
BigDecimal shipping = BigDecimal.ZERO;
for ( InvoiceLineItem lineItem : lineItems ) { (5)
BigDecimal amount = lineItem.getAmount();
total = total.add( amount );
switch ( lineItem.getCategory() ) {
case BOOK:
books = books.add( amount );
break;
case SHIPPING:
shipping = shipping.add( amount );
break;
}
}
DocumentElement summary = target.addObject( this.summaryField ); (6)
summary.addValue( this.totalField, total ); (7)
summary.addValue( this.booksField, books ); (7)
summary.addValue( this.shippingField, shipping ); (7)
}
}
}
@Entity
@Indexed
public class Invoice {
@Id
@GeneratedValue
private Integer id;
@ElementCollection
@OrderColumn
@PropertyBinding(binder = @PropertyBinderRef(type = InvoiceLineItemsSummaryBinder.class)) (1)
private List<InvoiceLineItem> lineItems = new ArrayList<>();
// Getters and setters
// ...
}
{
"summary": {
"total": 38.96,
"books": 30.97,
"shipping": 7.99
}
}
12.3.2. Passing parameters
可以通过两种方式将参数传递至属性桥:
There are two ways to pass parameters to property bridges:
-
One is (mostly) limited to string parameters, but is trivial to implement.
-
The other can allow any type of parameters, but requires you to declare your own annotations.
Simple, string parameters
可以将字符串参数传递至 @PropertyBinderRef
注解,然后稍后在粘合器中使用它们:
You can pass string parameters to the @PropertyBinderRef annotation and then use them later in the binder:
. Example 89. Passing parameters to a PropertyBinder using the @PropertyBinderRef annotation
public class InvoiceLineItemsSummaryBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
.use( "category" )
.use( "amount" );
String fieldName = context.params().get( "fieldName", String.class ); (1)
IndexSchemaObjectField summaryField = context.indexSchemaElement()
.objectField( fieldName ); (2)
IndexFieldType<BigDecimal> amountFieldType = context.typeFactory()
.asBigDecimal().decimalScale( 2 ).toIndexFieldType();
context.bridge( List.class, new Bridge(
summaryField.toReference(),
summaryField.field( "total", amountFieldType ).toReference(),
summaryField.field( "books", amountFieldType ).toReference(),
summaryField.field( "shipping", amountFieldType ).toReference()
) );
}
private static class Bridge implements PropertyBridge<List> {
/* ... same implementation as before ... */
}
}
@Entity
@Indexed
public class Invoice {
@Id
@GeneratedValue
private Integer id;
@ElementCollection
@OrderColumn
@PropertyBinding(binder = @PropertyBinderRef( (1)
type = InvoiceLineItemsSummaryBinder.class,
params = @Param(name = "fieldName", value = "itemSummary")))
private List<InvoiceLineItem> lineItems = new ArrayList<>();
// Getters and setters
// ...
}
Parameters with custom annotations
您可以通过定义具有属性的 custom annotation将任何类型的参数传递给桥接器:
You can pass parameters of any type to the bridge by defining a custom annotation with attributes:
. Example 90. Passing parameters to a PropertyBinder using a custom annotation
@Retention(RetentionPolicy.RUNTIME) (1)
@Target({ ElementType.METHOD, ElementType.FIELD }) (2)
@PropertyMapping(processor = @PropertyMappingAnnotationProcessorRef( (3)
type = InvoiceLineItemsSummaryBinding.Processor.class
))
@Documented (4)
public @interface InvoiceLineItemsSummaryBinding {
String fieldName() default ""; (5)
class Processor (6)
implements PropertyMappingAnnotationProcessor<InvoiceLineItemsSummaryBinding> { (7)
@Override
public void process(PropertyMappingStep mapping,
InvoiceLineItemsSummaryBinding annotation,
PropertyMappingAnnotationProcessorContext context) {
InvoiceLineItemsSummaryBinder binder = new InvoiceLineItemsSummaryBinder(); (8)
if ( !annotation.fieldName().isEmpty() ) { (9)
binder.fieldName( annotation.fieldName() );
}
mapping.binder( binder ); (10)
}
}
}
public class InvoiceLineItemsSummaryBinder implements PropertyBinder {
private String fieldName = "summary";
public InvoiceLineItemsSummaryBinder fieldName(String fieldName) { (1)
this.fieldName = fieldName;
return this;
}
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
.use( "category" )
.use( "amount" );
IndexSchemaObjectField summaryField = context.indexSchemaElement()
.objectField( this.fieldName ); (2)
IndexFieldType<BigDecimal> amountFieldType = context.typeFactory()
.asBigDecimal().decimalScale( 2 ).toIndexFieldType();
context.bridge( List.class, new Bridge(
summaryField.toReference(),
summaryField.field( "total", amountFieldType ).toReference(),
summaryField.field( "books", amountFieldType ).toReference(),
summaryField.field( "shipping", amountFieldType ).toReference()
) );
}
private static class Bridge implements PropertyBridge<List> {
/* ... same implementation as before ... */
}
}
@Entity
@Indexed
public class Invoice {
@Id
@GeneratedValue
private Integer id;
@ElementCollection
@OrderColumn
@InvoiceLineItemsSummaryBinding( (1)
fieldName = "itemSummary"
)
private List<InvoiceLineItem> lineItems = new ArrayList<>();
// Getters and setters
// ...
}
12.3.3. Accessing the ORM session from the bridge
此功能仅可通过 Hibernate ORM integration 使用。 |
This feature is only available with the Hibernate ORM integration. |
尤其不能与 Standalone POJO Mapper 一起使用。
It cannot be used with the Standalone POJO Mapper in particular.
传递给桥接方法的上下文可用于检索 Hibernate ORM 会话。
Contexts passed to the bridge methods can be used to retrieve the Hibernate ORM session.
. Example 91. Retrieving the ORM session from a PropertyBridge
private static class Bridge implements PropertyBridge<Object> {
private final IndexFieldReference<String> field;
private Bridge(IndexFieldReference<String> field) {
this.field = field;
}
@Override
public void write(DocumentElement target, Object bridgedElement, PropertyBridgeWriteContext context) {
Session session = context.extension( HibernateOrmExtension.get() ) (1)
.session(); (2)
// ... do something with the session ...
}
}
12.3.4. Injecting beans into the binder
使用 compatible frameworks,Hibernate Search 支持将 bean 注入到:
With compatible frameworks, Hibernate Search supports injecting beans into:
-
the PropertyMappingAnnotationProcessor if you use custom annotations.
-
the PropertyBinder if you use the @PropertyBinding annotation.
传递至属性粘合器 bind
方法的上下文还公开了一个 beanResolver()
方法,用于访问 Bean 解析器并明确实例化 Bean。
The context passed to the property binder’s bind method also exposes a beanResolver() method to access the bean resolver and instantiate beans explicitly.
有关更多详细信息,请参见 Bean injection。
See Bean injection for more details.
12.3.5. Programmatic mapping
您也可以通过 programmatic mapping应用属性桥。只需传递粘合剂实例。您可以通过粘合剂的构造函数或通过 setter 传递参数。
You can apply a property bridge through the programmatic mapping too. Just pass an instance of the binder. You can pass arguments either through the binder’s constructor, or through setters.
. Example 92. Applying an PropertyBinder with .binder(…)
TypeMappingStep invoiceMapping = mapping.type( Invoice.class );
invoiceMapping.indexed();
invoiceMapping.property( "lineItems" )
.binder( new InvoiceLineItemsSummaryBinder().fieldName( "itemSummary" ) );
12.3.6. Incubating features
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
传递给属性绑定程序的 bind 方法的上下文公开了一个 bridgedElement() 方法,该方法可访问正在绑定的属性的元数据。
The context passed to the property binder’s bind method exposes a bridgedElement() method that gives access to metadata about the property being bound.
元数据可用于详细检查属性:
The metadata can be used to inspect the property in details:
-
Getting the name of the property.
-
Checking the type of the property.
-
Getting accessors to properties.
-
Detecting properties with markers. Markers are applied by specific annotations carrying a @MarkerBinding meta-annotation.
有关更多信息,请参见 javadoc。
See the javadoc for more information.
下面是一个最简单的使用此元数据示例:获取属性名称并将其用作字段名称。
Below is an example of the simplest use of this metadata, getting the property name and using it as a field name.
. Example 93. Naming a field after the property being bound in a PropertyBinder
public class InvoiceLineItemsSummaryBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
.use( "category" )
.use( "amount" );
PojoModelProperty bridgedElement = context.bridgedElement(); (1)
IndexSchemaObjectField summaryField = context.indexSchemaElement()
.objectField( bridgedElement.name() ); (2)
IndexFieldType<BigDecimal> amountFieldType = context.typeFactory()
.asBigDecimal().decimalScale( 2 ).toIndexFieldType();
context.bridge( List.class, new Bridge(
summaryField.toReference(),
summaryField.field( "total", amountFieldType ).toReference(),
summaryField.field( "books", amountFieldType ).toReference(),
summaryField.field( "shipping", amountFieldType ).toReference()
) );
}
private static class Bridge implements PropertyBridge<List> {
/* ... same implementation as before ... */
}
}
@Entity
@Indexed
public class Invoice {
@Id
@GeneratedValue
private Integer id;
@ElementCollection
@OrderColumn
@PropertyBinding(binder = @PropertyBinderRef( (1)
type = InvoiceLineItemsSummaryBinder.class
))
private List<InvoiceLineItem> lineItems = new ArrayList<>();
// Getters and setters
// ...
}
{
"lineItems": {
"total": 38.96,
"books": 30.97,
"shipping": 7.99
}
}
12.4. Type bridge
12.4.1. Basics
类型桥是一个可插入组件,用于实现将整个类型映射到一个或多个索引字段。它应用于具有 _@TypeBinding_注释或 custom annotation的类型。
A type bridge is a pluggable component that implements the mapping of a whole type to one or more index fields. It is applied to a type with the @TypeBinding annotation or with a custom annotation.
类型桥在其核心原则和实现方式上与属性桥非常相似。唯一(显而易见)的区别在于属性桥应用于属性(字段或 getter),而类型桥应用于类型(类或接口)。这就带来了一些类型桥公开的 API 的细微差异。
The type bridge is very similar to the property bridge in its core principles and in how it is implemented. The only (obvious) difference is that the property bridge is applied to properties (fields or getters), while the type bridge is applied to the type (class or interface). This entails some slight differences in the APIs exposed to the type bridge.
实现类型桥需要两个组件:
Implementing a type bridge requires two components:
-
A custom implementation of TypeBinder, to bind the bridge to a type at bootstrap. This involves declaring the properties of the type that will be used, declaring the index fields that will be populated along with their type, and instantiating the type bridge.
-
A custom implementation of TypeBridge, to perform the conversion at runtime. This involves extracting data from an instance of the type, transforming the data if necessary, and pushing it to index fields.
下面是一个自定义类型桥的示例,它将 Author
类的两个属性 firstName
和 lastName
映射到单个 fullName
字段。
Below is an example of a custom type bridge that maps two properties of the Author class, the firstName and lastName, to a single fullName field.
. Example 94. Implementing and using a TypeBridge
public class FullNameBinder implements TypeBinder { (1)
@Override
public void bind(TypeBindingContext context) { (2)
context.dependencies() (3)
.use( "firstName" )
.use( "lastName" );
IndexFieldReference<String> fullNameField = context.indexSchemaElement() (4)
.field( "fullName", f -> f.asString().analyzer( "name" ) ) (5)
.toReference();
context.bridge( (6)
Author.class, (7)
new Bridge( (8)
fullNameField (9)
)
);
}
// ... class continues below
// ... class FullNameBinder (continued)
private static class Bridge (1)
implements TypeBridge<Author> { (2)
private final IndexFieldReference<String> fullNameField;
private Bridge(IndexFieldReference<String> fullNameField) { (3)
this.fullNameField = fullNameField;
}
@Override
public void write(
DocumentElement target,
Author author,
TypeBridgeWriteContext context) { (4)
String fullName = author.getLastName() + " " + author.getFirstName(); (5)
target.addValue( this.fullNameField, fullName ); (6)
}
}
}
@Entity
@Indexed
@TypeBinding(binder = @TypeBinderRef(type = FullNameBinder.class)) (1)
public class Author {
@Id
@GeneratedValue
private Integer id;
private String firstName;
private String lastName;
@GenericField (2)
private LocalDate birthDate;
// Getters and setters
// ...
}
{
"fullName": "Asimov Isaac"
}
12.4.2. Passing parameters
可以通过两种方式将参数传递至类型桥:
There are two ways to pass parameters to type bridges:
-
One is (mostly) limited to string parameters, but is trivial to implement.
-
The other can allow any type of parameters, but requires you to declare your own annotations.
Simple, string parameters
可以将字符串参数传递至 @TypeBinderRef
注解,然后稍后在粘合器中使用它们:
You can pass string parameters to the @TypeBinderRef annotation and then use them later in the binder:
. Example 95. Passing parameters to a TypeBinder using the @TypeBinderRef annotation
public class FullNameBinder implements TypeBinder {
@Override
public void bind(TypeBindingContext context) {
context.dependencies()
.use( "firstName" )
.use( "lastName" );
IndexFieldReference<String> fullNameField = context.indexSchemaElement()
.field( "fullName", f -> f.asString().analyzer( "name" ) )
.toReference();
IndexFieldReference<String> fullNameSortField = null;
String sortField = context.params().get( "sortField", String.class ); (1)
if ( "true".equalsIgnoreCase( sortField ) ) { (2)
fullNameSortField = context.indexSchemaElement()
.field(
"fullName_sort",
f -> f.asString().normalizer( "name" ).sortable( Sortable.YES )
)
.toReference();
}
context.bridge( Author.class, new Bridge(
fullNameField,
fullNameSortField
) );
}
private static class Bridge implements TypeBridge<Author> {
private final IndexFieldReference<String> fullNameField;
private final IndexFieldReference<String> fullNameSortField;
private Bridge(IndexFieldReference<String> fullNameField,
IndexFieldReference<String> fullNameSortField) { (2)
this.fullNameField = fullNameField;
this.fullNameSortField = fullNameSortField;
}
@Override
public void write(
DocumentElement target,
Author author,
TypeBridgeWriteContext context) {
String fullName = author.getLastName() + " " + author.getFirstName();
target.addValue( this.fullNameField, fullName );
if ( this.fullNameSortField != null ) {
target.addValue( this.fullNameSortField, fullName );
}
}
}
}
@Entity
@Indexed
@TypeBinding(binder = @TypeBinderRef(type = FullNameBinder.class, (1)
params = @Param(name = "sortField", value = "true")))
public class Author {
@Id
@GeneratedValue
private Integer id;
private String firstName;
private String lastName;
// Getters and setters
// ...
}
Parameters with custom annotations
您可以通过定义具有属性的 custom annotation将任何类型的参数传递给桥接器:
You can pass parameters of any type to the bridge by defining a custom annotation with attributes:
. Example 96. Passing parameters to a TypeBinder using a custom annotation
@Retention(RetentionPolicy.RUNTIME) (1)
@Target({ ElementType.TYPE }) (2)
@TypeMapping(processor = @TypeMappingAnnotationProcessorRef(type = FullNameBinding.Processor.class)) (3)
@Documented (4)
public @interface FullNameBinding {
boolean sortField() default false; (5)
class Processor (6)
implements TypeMappingAnnotationProcessor<FullNameBinding> { (7)
@Override
public void process(TypeMappingStep mapping, FullNameBinding annotation,
TypeMappingAnnotationProcessorContext context) {
FullNameBinder binder = new FullNameBinder() (8)
.sortField( annotation.sortField() ); (9)
mapping.binder( binder ); (10)
}
}
}
public class FullNameBinder implements TypeBinder {
private boolean sortField;
public FullNameBinder sortField(boolean sortField) { (1)
this.sortField = sortField;
return this;
}
@Override
public void bind(TypeBindingContext context) {
context.dependencies()
.use( "firstName" )
.use( "lastName" );
IndexFieldReference<String> fullNameField = context.indexSchemaElement()
.field( "fullName", f -> f.asString().analyzer( "name" ) )
.toReference();
IndexFieldReference<String> fullNameSortField = null;
if ( this.sortField ) { (2)
fullNameSortField = context.indexSchemaElement()
.field(
"fullName_sort",
f -> f.asString().normalizer( "name" ).sortable( Sortable.YES )
)
.toReference();
}
context.bridge( Author.class, new Bridge(
fullNameField,
fullNameSortField
) );
}
private static class Bridge implements TypeBridge<Author> {
private final IndexFieldReference<String> fullNameField;
private final IndexFieldReference<String> fullNameSortField;
private Bridge(IndexFieldReference<String> fullNameField,
IndexFieldReference<String> fullNameSortField) { (2)
this.fullNameField = fullNameField;
this.fullNameSortField = fullNameSortField;
}
@Override
public void write(
DocumentElement target,
Author author,
TypeBridgeWriteContext context) {
String fullName = author.getLastName() + " " + author.getFirstName();
target.addValue( this.fullNameField, fullName );
if ( this.fullNameSortField != null ) {
target.addValue( this.fullNameSortField, fullName );
}
}
}
}
@Entity
@Indexed
@FullNameBinding(sortField = true) (1)
public class Author {
@Id
@GeneratedValue
private Integer id;
private String firstName;
private String lastName;
// Getters and setters
// ...
}
12.4.3. Accessing the ORM session from the bridge
此功能仅可通过 Hibernate ORM integration 使用。 |
This feature is only available with the Hibernate ORM integration. |
尤其不能与 Standalone POJO Mapper 一起使用。
It cannot be used with the Standalone POJO Mapper in particular.
传递给桥接方法的上下文可用于检索 Hibernate ORM 会话。
Contexts passed to the bridge methods can be used to retrieve the Hibernate ORM session.
. Example 97. Retrieving the ORM session from a TypeBridge
private static class Bridge implements TypeBridge<Object> {
private final IndexFieldReference<String> field;
private Bridge(IndexFieldReference<String> field) {
this.field = field;
}
@Override
public void write(DocumentElement target, Object bridgedElement, TypeBridgeWriteContext context) {
Session session = context.extension( HibernateOrmExtension.get() ) (1)
.session(); (2)
// ... do something with the session ...
}
}
12.4.4. Injecting beans into the binder
使用 compatible frameworks,Hibernate Search 支持将 bean 注入到:
With compatible frameworks, Hibernate Search supports injecting beans into:
-
the TypeMappingAnnotationProcessor if you use custom annotations.
-
the TypeBinder if you use the @TypeBinding annotation.
传递至路由键粘合器 bind
方法的上下文还公开了一个 beanResolver()
方法,用于访问 Bean 解析器并明确实例化 Bean。
The context passed to the routing key binder’s bind method also exposes a beanResolver() method to access the bean resolver and instantiate beans explicitly.
有关更多详细信息,请参见 Bean injection。
See Bean injection for more details.
12.4.5. Programmatic mapping
您也可以通过 programmatic mapping 应用一个类型桥接。只需传递粘合剂的一个实例。您可以通过粘合剂的构造函数或通过设置器传递参数。
You can apply a type bridge through the programmatic mapping too. Just pass an instance of the binder. You can pass arguments either through the binder’s constructor, or through setters.
. Example 98. Applying a TypeBinder with .binder(…)
TypeMappingStep authorMapping = mapping.type( Author.class );
authorMapping.indexed();
authorMapping.binder( new FullNameBinder().sortField( true ) );
12.4.6. Incubating features
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
传递给类型绑定程序的 bind 方法的上下文公开了一个 bridgedElement() 方法,该方法可访问正在绑定的类型的元数据。
The context passed to the type binder’s bind method exposes a bridgedElement() method that gives access to metadata about the type being bound.
尤其是元数据可用于详细检查类型:
The metadata can in particular be used to inspect the type in details:
-
Getting accessors to properties.
-
Detecting properties with markers. Markers are applied by specific annotations carrying a @MarkerBinding meta-annotation.
有关更多信息,请参见 javadoc。
See the javadoc for more information.
12.5. Identifier bridge
12.5.1. Basics
标识符桥接是一个可插入组件,该组件实现实体属性到文档标识符的映射。它使用 @DocumentId 注释或使用 custom annotation 应用于属性。
An identifier bridge is a pluggable component that implements the mapping of an entity property to a document identifier. It is applied to a property with the @DocumentId annotation or with a custom annotation.
实现标识符桥归结为实现两个方法:
Implementing an identifier bridge boils down to implementing two methods:
-
one method to convert the property value (any type) to the document identifier (a string);
-
one method to convert the document identifier back to the original property value.
以下是自定义标识符桥接器的示例,它将自定义 BookId 类型转换为其字符串表示形式,并返回:
Below is an example of a custom identifier bridge that converts a custom BookId type to its string representation and back:
. Example 99. Implementing and using an IdentifierBridge
public class BookIdBridge implements IdentifierBridge<BookId> { (1)
@Override
public String toDocumentIdentifier(BookId value,
IdentifierBridgeToDocumentIdentifierContext context) { (2)
return value.getPublisherId() + "/" + value.getPublisherSpecificBookId();
}
@Override
public BookId fromDocumentIdentifier(String documentIdentifier,
IdentifierBridgeFromDocumentIdentifierContext context) { (3)
String[] split = documentIdentifier.split( "/" );
return new BookId( Long.parseLong( split[0] ), Long.parseLong( split[1] ) );
}
}
@Entity
@Indexed
public class Book {
@EmbeddedId
@DocumentId( (1)
identifierBridge = @IdentifierBridgeRef(type = BookIdBridge.class) (2)
)
private BookId id = new BookId();
private String title;
// Getters and setters
// ...
}
12.5.2. Type resolution
默认情况下,标识符桥接器的属性类型自动确定,使用反射提取 IdentifierBridge 接口的泛型类型参数。
By default, the identifier bridge’s property type is determined automatically, using reflection to extract the generic type argument of the IdentifierBridge interface.
例如,在 public class MyBridge implements IdentifierBridge<BookId> 中,属性类型解析为 BookId:桥接器将应用于类型为 BookId 的属性。
For example, in public class MyBridge implements IdentifierBridge<BookId>, the property type is resolved to BookId: the bridge will be applied to properties of type BookId.
使用反射自动解析类型的事实带来了一些限制。特别是,这意味着泛型类型参数不能仅用于任何内容;作为一般规则,您应坚持使用文本类型 (MyBridge implements IdentifierBridge<BookId>),避免使用泛型类型参数和通配符 (MyBridge<T extends Number> implements IdentifierBridge<T>,`MyBridge implements IdentifierBridge<List<? extends Number>>)。
The fact that the type is resolved automatically using reflection brings a few limitations. In particular, it means the generic type argument cannot be just anything; as a general rule, you should stick to literal types (MyBridge implements IdentifierBridge<BookId>) and avoid generic type parameters and wildcards (MyBridge<T extends Number> implements IdentifierBridge<T>, `MyBridge implements IdentifierBridge<List<? extends Number>>).
如果您需要更复杂的类型,您可以绕过自动解析并使用 IdentifierBinder 显式指定类型。
If you need more complex types, you can bypass the automatic resolution and specify types explicitly using an IdentifierBinder.
12.5.3. Compatibility across indexes with isCompatibleWith()
标识符网桥涉及索引,也涉及搜索 DSL,以将传递给 id predicate 的值转换为后端将了解的文档标识符。
An identifier bridge is involved in indexing, but also in the search DSLs, to convert values passed to the id predicate to a document identifier that the backend will understand.
创建针对多个实体类型(及其索引)的 id 谓词时,Hibernate Search 将有多个桥接器可供选择:每个实体类型一个。由于只能使用单个值创建一个谓词,因此 Hibernate Search 需要挑选一个桥接器。
When creating an id predicate targeting multiple entity types (and their indexes), Hibernate Search will have multiple bridges to choose from: one per entity type. Since only one predicate with a single value can be created, Hibernate Search needs to pick a single bridge.
默认情况下,当将自定义桥接器分配给字段时,由于无法决定挑选哪个桥接器,因此 Hibernate Search 将抛出异常。
By default, when a custom bridge is assigned to the field, Hibernate Search will throw an exception because it cannot decide which bridge to pick.
如果分配给所有索引中字段的桥接器生成相同的结果,则可以通过实现 isCompatibleWith 向 Hibernate Search 指示任何桥接器都可以执行操作。
If the bridges assigned to the field in all indexes produce the same result, it is possible to indicate to Hibernate Search that any bridge will do by implementing isCompatibleWith.
此方法接受参数中的另一个桥接器,并且如果预期该桥接器始终与 this 的行为相同,则返回 true。
This method accepts another bridge in parameter, and returns true if that bridge can be expected to always behave the same as this.
. Example 100. Implementing isCompatibleWith to support multi-index search
public class BookOrMagazineIdBridge implements IdentifierBridge<BookOrMagazineId> {
@Override
public String toDocumentIdentifier(BookOrMagazineId value,
IdentifierBridgeToDocumentIdentifierContext context) {
return value.getPublisherId() + "/" + value.getPublisherSpecificBookId();
}
@Override
public BookOrMagazineId fromDocumentIdentifier(String documentIdentifier,
IdentifierBridgeFromDocumentIdentifierContext context) {
String[] split = documentIdentifier.split( "/" );
return new BookOrMagazineId( Long.parseLong( split[0] ), Long.parseLong( split[1] ) );
}
@Override
public boolean isCompatibleWith(IdentifierBridge<?> other) {
return getClass().equals( other.getClass() ); (1)
}
}
12.5.4. Parsing identifier’s string representation with parseIdentifierLiteral(..)
在某些情况下,Hibernate 搜索可能需要分析标识符的字符串表示形式,例如在在 identifier match predicate 的匹配子句中使用了 ValueConvert.PARSE 时。
In some scenarios, Hibernate Search may need to parse a string representation of an identifier, e.g. when the ValueConvert.PARSE is used in the matching clause of an identifier match predicate.
通过自定义标识符桥接器,默认情况下,Hibernate Search 无法自动解析此类标识符文本。为了解决此问题,可以实现 parseIdentifierLiteral(..)。
With a custom identifier bridge, Hibernate Search cannot automatically parse such identifier literals by default. To address this, parseIdentifierLiteral(..) can be implemented.
. Example 101. Implementing parseIdentifierLiteral(..)
public class BookIdBridge implements IdentifierBridge<BookId> { (1)
// Implement mandatory toDocumentIdentifier/fromDocumentIdentifier ...
// ...
@Override
public BookId parseIdentifierLiteral(String value) { (2)
if ( value == null ) {
return null;
}
String[] parts = value.split( "/" );
if ( parts.length != 2 ) {
throw new IllegalArgumentException( "BookId string literal must be in a `pubId/bookId` format." );
}
return new BookId( Long.parseLong( parts[0] ), Long.parseLong( parts[1] ) );
}
}
List<Book> result = searchSession.search( Book.class )
.where( f -> f.id().matching( "1/42", ValueConvert.PARSE ) ) (1)
.fetchHits( 20 );
12.5.5. Configuring the bridge more finely with IdentifierBinder
要更精细地配置桥接器,可以实现将在启动时执行的值绑定器。此绑定器特别是能够检查属性的类型。
To configure a bridge more finely, it is possible to implement a value binder that will be executed at bootstrap. This binder will be able in particular to inspect the type of the property.
. Example 102. Implementing an IdentifierBinder
public class BookIdBinder implements IdentifierBinder { (1)
@Override
public void bind(IdentifierBindingContext<?> context) { (2)
context.bridge( (3)
BookId.class, (4)
new Bridge() (5)
);
}
private static class Bridge implements IdentifierBridge<BookId> { (6)
@Override
public String toDocumentIdentifier(BookId value,
IdentifierBridgeToDocumentIdentifierContext context) {
return value.getPublisherId() + "/" + value.getPublisherSpecificBookId();
}
@Override
public BookId fromDocumentIdentifier(String documentIdentifier,
IdentifierBridgeFromDocumentIdentifierContext context) {
String[] split = documentIdentifier.split( "/" );
return new BookId( Long.parseLong( split[0] ), Long.parseLong( split[1] ) );
}
}
}
@Entity
@Indexed
public class Book {
@EmbeddedId
@DocumentId( (1)
identifierBinder = @IdentifierBinderRef(type = BookIdBinder.class) (2)
)
private BookId id = new BookId();
@FullTextField(analyzer = "english")
private String title;
// Getters and setters
// ...
}
12.5.6. Passing parameters
有两种方法可以将参数传递到标识符桥接器:
There are two ways to pass parameters to identifier bridges:
-
One is (mostly) limited to string parameters, but is trivial to implement.
-
The other can allow any type of parameters, but requires you to declare your own annotations.
Simple, string parameters
您可以将字符串参数传递给 @IdentifierBinderRef 注释,然后在绑定器中使用它们:
You can pass string parameters to the @IdentifierBinderRef annotation and then use them later in the binder:
. Example 103. Passing parameters to an IdentifierBridge using the @IdentifierBinderRef annotation
public class OffsetIdentifierBridge implements IdentifierBridge<Integer> { (1)
private final int offset;
public OffsetIdentifierBridge(int offset) { (2)
this.offset = offset;
}
@Override
public String toDocumentIdentifier(Integer propertyValue, IdentifierBridgeToDocumentIdentifierContext context) {
return String.valueOf( propertyValue + offset );
}
@Override
public Integer fromDocumentIdentifier(String documentIdentifier,
IdentifierBridgeFromDocumentIdentifierContext context) {
return Integer.parseInt( documentIdentifier ) - offset;
}
}
public class OffsetIdentifierBinder implements IdentifierBinder {
@Override
public void bind(IdentifierBindingContext<?> context) {
String offset = context.params().get( "offset", String.class ); (1)
context.bridge(
Integer.class,
new OffsetIdentifierBridge( Integer.parseInt( offset ) ) (2)
);
}
}
@Entity
@Indexed
public class Book {
@Id
// DB identifiers start at 0, but index identifiers start at 1
@DocumentId(identifierBinder = @IdentifierBinderRef( (1)
type = OffsetIdentifierBinder.class,
params = @Param(name = "offset", value = "1")))
private Integer id;
private String title;
// Getters and setters
// ...
}
Parameters with custom annotations
您可以通过定义具有属性的 custom annotation将任何类型的参数传递给桥接器:
You can pass parameters of any type to the bridge by defining a custom annotation with attributes:
. Example 104. Passing parameters to an IdentifierBridge using a custom annotation
public class OffsetIdentifierBridge implements IdentifierBridge<Integer> { (1)
private final int offset;
public OffsetIdentifierBridge(int offset) { (2)
this.offset = offset;
}
@Override
public String toDocumentIdentifier(Integer propertyValue, IdentifierBridgeToDocumentIdentifierContext context) {
return String.valueOf( propertyValue + offset );
}
@Override
public Integer fromDocumentIdentifier(String documentIdentifier,
IdentifierBridgeFromDocumentIdentifierContext context) {
return Integer.parseInt( documentIdentifier ) - offset;
}
}
@Retention(RetentionPolicy.RUNTIME) (1)
@Target({ ElementType.METHOD, ElementType.FIELD }) (2)
@PropertyMapping(processor = @PropertyMappingAnnotationProcessorRef( (3)
type = OffsetDocumentId.Processor.class
))
@Documented (4)
public @interface OffsetDocumentId {
int offset(); (5)
class Processor (6)
implements PropertyMappingAnnotationProcessor<OffsetDocumentId> { (7)
@Override
public void process(PropertyMappingStep mapping, OffsetDocumentId annotation,
PropertyMappingAnnotationProcessorContext context) {
OffsetIdentifierBridge bridge = new OffsetIdentifierBridge( (8)
annotation.offset()
);
mapping.documentId() (9)
.identifierBridge( bridge ); (10)
}
}
}
@Entity
@Indexed
public class Book {
@Id
// DB identifiers start at 0, but index identifiers start at 1
@OffsetDocumentId(offset = 1) (1)
private Integer id;
private String title;
// Getters and setters
// ...
}
12.5.7. Accessing the ORM session or session factory from the bridge
此功能仅可通过 Hibernate ORM integration 使用。 |
This feature is only available with the Hibernate ORM integration. |
尤其不能与 Standalone POJO Mapper 一起使用。
It cannot be used with the Standalone POJO Mapper in particular.
传递到桥接方法的上下文可用于检索 Hibernate ORM 会话或会话工厂。
Contexts passed to the bridge methods can be used to retrieve the Hibernate ORM session or session factory.
. Example 105. Retrieving the ORM session or session factory from an IdentifierBridge
public class MyDataIdentifierBridge implements IdentifierBridge<MyData> {
@Override
public String toDocumentIdentifier(MyData propertyValue, IdentifierBridgeToDocumentIdentifierContext context) {
SessionFactory sessionFactory = context.extension( HibernateOrmExtension.get() ) (1)
.sessionFactory(); (2)
// ... do something with the factory ...
}
@Override
public MyData fromDocumentIdentifier(String documentIdentifier,
IdentifierBridgeFromDocumentIdentifierContext context) {
Session session = context.extension( HibernateOrmExtension.get() ) (3)
.session(); (4)
// ... do something with the session ...
}
}
12.5.8. Injecting beans into the bridge or binder
使用 compatible frameworks,Hibernate 搜索支持将 bean 注入到 IdentifierBridge 和 IdentifierBinder 中。
With compatible frameworks, Hibernate Search supports injection of beans into both the IdentifierBridge and the IdentifierBinder.
传递到标识符粘合器的 bind 方法的上下文还公开一个 beanResolver() 方法来访问 bean 解析器并显式实例化 bean。 |
The context passed to the identifier binder’s bind method also exposes a beanResolver() method to access the bean resolver and instantiate beans explicitly. |
有关更多详细信息,请参见 Bean injection。
See Bean injection for more details.
12.5.9. Programmatic mapping
您也可以通过 programmatic mapping 应用一个标识符桥接。只需传递桥接的一个实例。
You can apply an identifier bridge through the programmatic mapping too. Just pass an instance of the bridge.
. Example 106. Applying an IdentifierBridge with .identifierBridge(…)
TypeMappingStep bookMapping = mapping.type( Book.class );
bookMapping.indexed();
bookMapping.property( "id" )
.documentId().identifierBridge( new BookIdBridge() );
同样,您可以传递绑定器实例:
Similarly, you can pass a binder instance:
. Example 107. Applying an IdentifierBinder with .identifierBinder(…)
TypeMappingStep bookMapping = mapping.type( Book.class );
bookMapping.indexed();
bookMapping.property( "id" )
.documentId().identifierBinder( new BookIdBinder() );
12.5.10. Incubating features
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
传递给标识符绑定程序的 bind 方法的上下文公开了一个 bridgedElement() 方法,该方法可访问正在绑定的值的元数据,尤其是其类型。
The context passed to the identifier binder’s bind method exposes a bridgedElement() method that gives access to metadata about the value being bound, in particular its type.
有关更多信息,请参见 javadoc。
See the javadoc for more information.
12.6. Routing bridge
12.6.1. Basics
路由桥接是一个可插入组件,它在运行时定义实体是否应被索引和 to which shard the corresponding indexed document should be routed 。通过其 routingBinder 属性 (@Indexed(routingBinder = …)),使用 @Indexed 注释将其应用于已索引实体类型。
A routing bridge is a pluggable component that defines, at runtime, whether an entity should be indexed and to which shard the corresponding indexed document should be routed. It is applied to an indexed entity type with the @Indexed annotation, using its routingBinder attribute (@Indexed(routingBinder = …)).
实现路由桥接器需要两个组件:
Implementing a routing bridge requires two components:
-
A custom implementation of RoutingBinder, to bind the bridge to an indexed entity type at bootstrap. This involves declaring the properties of the indexed entity type that will be used by the routing bridge and instantiating the routing bridge.
-
A custom implementation of RoutingBridge, to route entities to the index at runtime. This involves extracting data from an instance of the type, transforming the data if necessary, and defining the current route (or marking the entity as "not indexed").If routing can change during the lifetime of an entity instance, you will also need to define the potential previous routes, so that Hibernate Search can find and delete previous documents indexed for this entity instance.
在下面的章节中,您将找到主要使用案例示例:
In the sections below, you will find examples for the main use cases:
12.6.2. Using a routing bridge for conditional indexing
下面是禁用 Book 类的实例的索引的自定义路由桥的第一个示例,如果其状态为 ARCHIVED。
Below is a first example of a custom routing bridge that disables indexing for instances of the Book class if their status is ARCHIVED.
. Example 108. Implementing and using a RoutingBridge for conditional indexing
public class BookStatusRoutingBinder implements RoutingBinder { (1)
@Override
public void bind(RoutingBindingContext context) { (2)
context.dependencies() (3)
.use( "status" );
context.bridge( (4)
Book.class, (5)
new Bridge() (6)
);
}
// ... class continues below
// ... class BookStatusRoutingBinder (continued)
public static class Bridge (1)
implements RoutingBridge<Book> { (2)
@Override
public void route(DocumentRoutes routes, Object entityIdentifier, (3)
Book indexedEntity, RoutingBridgeRouteContext context) {
switch ( indexedEntity.getStatus() ) { (4)
case PUBLISHED:
routes.addRoute(); (5)
break;
case ARCHIVED:
routes.notIndexed(); (6)
break;
}
}
@Override
public void previousRoutes(DocumentRoutes routes, Object entityIdentifier, (7)
Book indexedEntity, RoutingBridgeRouteContext context) {
routes.addRoute(); (8)
}
}
}
@Entity
@Indexed(routingBinder = @RoutingBinderRef(type = BookStatusRoutingBinder.class)) (1)
public class Book {
@Id
private Integer id;
private String title;
@Basic(optional = false)
@KeywordField (2)
private Status status;
// Getters and setters
// ...
}
12.6.3. Using a routing bridge to control routing to index shards
有关分片的初步介绍,包括它在 Hibernate Search 中的工作方式以及它的局限性是什么,请参阅 Sharding and routing。 |
For a preliminary introduction to sharding, including how it works in Hibernate Search and what its limitations are, see Sharding and routing. |
路由桥接还可以用于控制 routing to index shards 。
Routing bridges can also be used to control routing to index shards.
下面是一个自定义路由桥接的示例,它使用 Book 类的 genre 属性作为路由键。参阅 Routing,了解如何在搜索查询中使用路由的示例,其映射与以下示例相同。
Below is an example of a custom routing bridge that uses the genre property of the Book class as a routing key. See Routing for an example of how to use routing in search queries, with the same mapping as the example below.
. Example 109. Implementing and using a RoutingBridge to control routing to index shards
public class BookGenreRoutingBinder implements RoutingBinder { (1)
@Override
public void bind(RoutingBindingContext context) { (2)
context.dependencies() (3)
.use( "genre" );
context.bridge( (4)
Book.class, (5)
new Bridge() (6)
);
}
// ... class continues below
// ... class BookGenreRoutingBinder (continued)
public static class Bridge implements RoutingBridge<Book> { (1)
@Override
public void route(DocumentRoutes routes, Object entityIdentifier, (2)
Book indexedEntity, RoutingBridgeRouteContext context) {
String routingKey = indexedEntity.getGenre().name(); (3)
routes.addRoute().routingKey( routingKey ); (4)
}
@Override
public void previousRoutes(DocumentRoutes routes, Object entityIdentifier, (5)
Book indexedEntity, RoutingBridgeRouteContext context) {
for ( Genre possiblePreviousGenre : Genre.values() ) {
String routingKey = possiblePreviousGenre.name();
routes.addRoute().routingKey( routingKey ); (6)
}
}
}
}
@Entity
@Indexed(routingBinder = @RoutingBinderRef(type = BookGenreRoutingBinder.class)) (1)
public class Book {
@Id
private Integer id;
private String title;
@Basic(optional = false)
@KeywordField (2)
private Genre genre;
// Getters and setters
// ...
}
优化 previousRoutes(…) 在某些情况下,您可能比上面的示例中关于以前路由有更多信息,您可以利用该信息触发索引中更少的删除: |
Optimizing previousRoutes(…) In some cases you might have more information than in the example above about the previous routes, and you can take advantage of that information to trigger fewer deletions in the index: |
如果路由键来自不可变属性,那么你可以确保路由永远不会更改。在这种情况下,只需使用传递给 previousRoutes(…)_的参数调用 _route(…),告诉 Hibernate Search 先前路由与当前路由相同,那么 Hibernate Search 会跳过删除。
If the routing key is derived from an immutable property, then you can be sure the route never changes. In that case, just call route(…) with the arguments passed to previousRoutes(…) to tell Hibernate Search that the previous route is the same as the current route, and Hibernate Search will skip the deletion.
如果路由键是从一个以可预测的方式变化的属性派生的,例如一个总是从 DRAFT 到 PUBLISHED 再到 ARCHIVED 并且永远不会后退的状态,那么您可以确信以前的路由是那些对应于可能的先前值。在这种情况下,只需为每个可能的先前状态添加一条路由,例如,如果当前状态是 PUBLISHED ,则只需要为 DRAFT 和 PUBLISHED 添加一条路由,而不需要为 ARCHIVED 添加路由。
If the routing key is derived from a property that changes in a predictable way, e.g. a status that always goes from DRAFT to PUBLISHED to ARCHIVED and never goes back, then you can be sure the previous routes are those corresponding to the possible previous values. In that case, just add one route for each possible previous status, e.g. if the current status is PUBLISHED you only need to add a route for DRAFT and PUBLISHED, but not for ARCHIVED.
12.6.4. Passing parameters
有两种方法可以将参数传递给路由桥:
There are two ways to pass parameters to routing bridges:
-
One is (mostly) limited to string parameters, but is trivial to implement.
-
The other can allow any type of parameters, but requires you to declare your own annotations.
参考 this example for TypeBinder ,它与 RoutingBinder 中需要的相当类似。
Refer to this example for TypeBinder, which is fairly similar to what you’ll need for a RoutingBinder.
12.6.5. Accessing the ORM session from the bridge
此功能仅可通过 Hibernate ORM integration 使用。 |
This feature is only available with the Hibernate ORM integration. |
尤其不能与 Standalone POJO Mapper 一起使用。
It cannot be used with the Standalone POJO Mapper in particular.
传递给桥接方法的上下文可用于检索 Hibernate ORM 会话。
Contexts passed to the bridge methods can be used to retrieve the Hibernate ORM session.
. Example 110. Retrieving the ORM session from a RoutingBridge
private static class Bridge implements RoutingBridge<MyEntity> {
@Override
public void route(DocumentRoutes routes, Object entityIdentifier, MyEntity indexedEntity,
RoutingBridgeRouteContext context) {
Session session = context.extension( HibernateOrmExtension.get() ) (1)
.session(); (2)
// ... do something with the session ...
}
@Override
public void previousRoutes(DocumentRoutes routes, Object entityIdentifier, MyEntity indexedEntity,
RoutingBridgeRouteContext context) {
// ...
}
}
12.6.6. Injecting beans into the binder
使用 compatible frameworks,Hibernate Search 支持将 bean 注入到:
With compatible frameworks, Hibernate Search supports injecting beans into:
-
the TypeMappingAnnotationProcessor if you use custom annotations.
-
the RoutingBinder if you use @Indexed(routingBinder = …).
传递给路由粘合剂 bind 方法的上下文还公开了一个 beanResolver() 方法来访问 bean 解析器并显式实例化 bean。
The context passed to the routing binder’s bind method also exposes a beanResolver() method to access the bean resolver and instantiate beans explicitly.
有关更多详细信息,请参见 Bean injection。
See Bean injection for more details.
12.6.7. Programmatic mapping
您也可以通过 programmatic mapping 应用路由键桥接。只需传递粘合剂的一个实例。
You can apply a routing key bridge through the programmatic mapping too. Just pass an instance of the binder.
. Example 111. Applying an RoutingBinder with .binder(…)
TypeMappingStep bookMapping = mapping.type( Book.class );
bookMapping.indexed()
.routingBinder( new BookStatusRoutingBinder() );
bookMapping.property( "status" ).keywordField();
12.6.8. Incubating features
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
传递给路由绑定程序的 bind 方法的上下文公开了一个 bridgedElement() 方法,该方法可访问正在绑定的类型的元数据。
The context passed to the routing binder’s bind method exposes a bridgedElement() method that gives access to metadata about the type being bound.
尤其是元数据可用于详细检查类型:
The metadata can in particular be used to inspect the type in details:
-
Getting accessors to properties.
-
Detecting properties with markers. Markers are applied by specific annotations carrying a @MarkerBinding meta-annotation.
有关更多信息,请参见 javadoc。
See the javadoc for more information.
12.7. Declaring dependencies to bridged elements
12.7.1. Basics
为了使索引保持同步,Hibernate Search 需要了解用于生成已编入索引文档的所有实体属性,以便在它们更改时触发重新索引。
In order to keep the index synchronized, Hibernate Search needs to be aware of all the entity properties that are used to produce indexed documents, so that it can trigger reindexing when they change.
在使用 type bridge 或 property bridge 时,桥接本身决定在索引时访问哪些实体属性。因此,它需要让 Hibernate 搜索知道其“依赖关系”(它可能访问的实体属性)。
When using a type bridge or a property bridge, the bridge itself decides which entity properties to access during indexing. Thus, it needs to let Hibernate Search know of its "dependencies" (the entity properties it may access).
这是通过一个专用的 DSL 完成的,可以通过 TypeBinder 和 PropertyBinder 的 bind(…) 方法访问该 DSL。
This is done through a dedicated DSL, accessible from the bind(…) method of TypeBinder and PropertyBinder.
以下是类型粘合剂的一个示例,该粘合剂希望应用于 ScientificPaper 类型,并声明对论文作者的姓氏和名字的依赖性。
Below is an example of a type binder that expects to be applied to the ScientificPaper type, and declares a dependency to the paper author’s last name and first name.
. Example 112. Declaring dependencies in a bridge
public class AuthorFullNameBinder implements TypeBinder {
@Override
public void bind(TypeBindingContext context) {
context.dependencies() (1)
.use( "author.firstName" ) (2)
.use( "author.lastName" ); (3)
IndexFieldReference<String> authorFullNameField = context.indexSchemaElement()
.field( "authorFullName", f -> f.asString().analyzer( "name" ) )
.toReference();
context.bridge( Book.class, new Bridge( authorFullNameField ) );
}
private static class Bridge implements TypeBridge<Book> {
// ...
}
}
以上内容足以开始,但如果您想了解更多信息,这里有一些关于声明依赖项的事实。
The above should be enough to get started, but if you want to know more, here are a few facts about declaring dependencies.
Paths are relative to the bridged element
例如:
For example:
对于类型 _ScientificPaper_上的类型网桥,路径 _author_将引用 _ScientificPaper_实例上属性 _author_的值。
for a type bridge on type ScientificPaper, path author will refer to the value of property author on ScientificPaper instances.
对于 _ScientificPaper_的属性 _author_上的属性网桥,路径 _name_将引用 _Author_实例上属性 _name_的值。
for a property bridge on the property author of ScientificPaper, path name will refer to the value of property name on Author instances.
Every component of given paths will be considered as a dependency
您不需要声明任何父路径。
You do not need to declare any parent path.
例如,如果路径 myProperty.someOtherProperty 被声明为已使用,Hibernate Search 将自动假设 myProperty 也被使用。
For example, if the path myProperty.someOtherProperty is declared as used, Hibernate Search will automatically assume that myProperty is also used.
Only mutable properties need to be declared
如果一个属性在实体首次持久化后永远不会改变,那么它永远不会触发重新索引,并且 Hibernate Search 无需了解该依赖关系。
If a property never, ever changes after the entity is first persisted, then it will never trigger reindexing and Hibernate Search does not need to know about the dependency.
如果您的桥只依赖于不可变属性,请参见 useRootOnly(): declaring no dependency at all 。
If your bridge only relies on immutable properties, see useRootOnly(): declaring no dependency at all.
Associations included in dependency paths need to have an inverse side
如果您通过关联声明跨实体边界交叉存在的依赖关系,并且该关联在其他实体中没有反向侧,那么将会抛出一个异常。
If you declare a dependency that crosses entity boundaries through an association, and that association has no inverse side in the other entity, an exception will be thrown.
例如,当您声明对路径 author.lastName 的依赖项时,Hibernate Search 推断出每当作者的姓氏更改时,都需要重新对他的书进行索引。因此,当它检测到作者的姓氏更改时,Hibernate Search 需要检索这些书以对其重新编制索引。这就是为什么实体 ScientificPaper 中的 author 关联在实体 Author 中需要有一个反向端,例如 books 关联。
For example, when you declare a dependency to path author.lastName, Hibernate Search infers that whenever the last name of an author changes, its books need to be re-indexed. Thus, when it detects an author’s last name changed, Hibernate Search will need to retrieve the books to reindex them. That’s why the author association in entity ScientificPaper needs to have an inverse side in entity Author, e.g. a books association.
有关这些约束以及如何处理非平凡模型的详细信息,请参阅 Tuning when to trigger reindexing。
See Tuning when to trigger reindexing for more information about these constraints and how to address non-trivial models.
12.7.2. Traversing non-default containers (map keys, …)
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
当路径元素引用容器类型的属性(List、Map、Optional、…)时,路径将隐式解析为该容器的元素。例如,someMap.otherObject 将解析为 someMap 的 values 的 otherObject 属性(而非键)。
When a path element refers to a property of a container type (List, Map, Optional, …), the path will be implicitly resolved to elements of that container. For example someMap.otherObject will resolve to the otherObject property of the values (not the keys) of someMap.
如果默认解析不符合您的需要,您可以显式控制如何通过容器,只需传递 PojoModelPath 对象,而不是仅仅字符串:
If the default resolution is not what you need, you can explicitly control how to traverse containers by passing PojoModelPath objects instead of just strings:
. Example 113. Declaring dependencies in a bridge with explicit container extractors
@Entity
@Indexed
@TypeBinding(binder = @TypeBinderRef(type = BookEditionsForSaleTypeBinder.class)) (1)
public class Book {
@Id
@GeneratedValue
private Integer id;
@FullTextField(analyzer = "name")
private String title;
@ElementCollection
@JoinTable(
name = "book_editionbyprice",
joinColumns = @JoinColumn(name = "book_id")
)
@MapKeyJoinColumn(name = "edition_id")
@Column(name = "price")
@OrderBy("edition_id asc")
@AssociationInverseSide(
extraction = @ContainerExtraction(BuiltinContainerExtractors.MAP_KEY),
inversePath = @ObjectPath(@PropertyValue(propertyName = "book"))
)
private Map<BookEdition, BigDecimal> priceByEdition = new LinkedHashMap<>(); (2)
public Book() {
}
// Getters and setters
// ...
}
public class BookEditionsForSaleTypeBinder implements TypeBinder {
@Override
public void bind(TypeBindingContext context) {
context.dependencies()
.use( PojoModelPath.builder() (1)
.property( "priceByEdition" ) (2)
.value( BuiltinContainerExtractors.MAP_KEY ) (3)
.property( "label" ) (4)
.toValuePath() ); (5)
IndexFieldReference<String> editionsForSaleField = context.indexSchemaElement()
.field( "editionsForSale", f -> f.asString().analyzer( "english" ) )
.multiValued()
.toReference();
context.bridge( Book.class, new Bridge( editionsForSaleField ) );
}
private static class Bridge implements TypeBridge<Book> {
private final IndexFieldReference<String> editionsForSaleField;
private Bridge(IndexFieldReference<String> editionsForSaleField) {
this.editionsForSaleField = editionsForSaleField;
}
@Override
public void write(DocumentElement target, Book book, TypeBridgeWriteContext context) {
for ( BookEdition edition : book.getPriceByEdition().keySet() ) { (6)
target.addValue( editionsForSaleField, edition.getLabel() );
}
}
}
}
对于应用到容器属性的属性粘合剂,您可以通过将容器提取器路径作为第一个参数传递给 use(…) 来控制如何遍历该属性本身:
For property binders applied to a container property, you can control how to traverse the property itself by passing a container extractor path as the first argument to use(…):
. Example 114. Declaring dependencies in a bridge with explicit container extractors for the bridged property
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
@FullTextField(analyzer = "name")
private String title;
@ElementCollection
@JoinTable(
name = "book_editionbyprice",
joinColumns = @JoinColumn(name = "book_id")
)
@MapKeyJoinColumn(name = "edition_id")
@Column(name = "price")
@OrderBy("edition_id asc")
@AssociationInverseSide(
extraction = @ContainerExtraction(BuiltinContainerExtractors.MAP_KEY),
inversePath = @ObjectPath(@PropertyValue(propertyName = "book"))
)
@PropertyBinding(binder = @PropertyBinderRef(type = BookEditionsForSalePropertyBinder.class)) (1)
private Map<BookEdition, BigDecimal> priceByEdition = new LinkedHashMap<>();
public Book() {
}
// Getters and setters
// ...
}
public class BookEditionsForSalePropertyBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
.use( ContainerExtractorPath.explicitExtractor( BuiltinContainerExtractors.MAP_KEY ), (1)
"label" ); (2)
IndexFieldReference<String> editionsForSaleField = context.indexSchemaElement()
.field( "editionsForSale", f -> f.asString().analyzer( "english" ) )
.multiValued()
.toReference();
context.bridge( Map.class, new Bridge( editionsForSaleField ) );
}
private static class Bridge implements PropertyBridge<Map> {
private final IndexFieldReference<String> editionsForSaleField;
private Bridge(IndexFieldReference<String> editionsForSaleField) {
this.editionsForSaleField = editionsForSaleField;
}
@Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
Map<BookEdition, ?> priceByEdition = (Map<BookEdition, ?>) bridgedElement;
for ( BookEdition edition : priceByEdition.keySet() ) { (3)
target.addValue( editionsForSaleField, edition.getLabel() );
}
}
}
}
12.7.3. useRootOnly(): declaring no dependency at all
如果您的桥只访问不可变属性,那么可以安全地声明其唯一的依赖项是对根对象。
If your bridge only accesses immutable properties, then it’s safe to declare that its only dependency is to the root object.
要做到这一点,请调用 .dependencies().useRootOnly()。
To do so, call .dependencies().useRootOnly().
如果没有此调用,Hibernate Search 会怀疑存在缺陷,并在启动时抛出一个异常。 |
Without this call, Hibernate Search will suspect an oversight and will throw an exception on startup. |
12.7.4. fromOtherEntity(…): declaring dependencies using the inverse path
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
并不总是可以将依赖关系表示为从桥接元素到桥接访问的值的路径。
It is not always possible to represent the dependency as a path from the bridged element to the values accessed by the bridge.
尤其当桥梁依赖其他组件(查询、服务)来检索另一实体时,可能甚至不存在从桥梁元素到该实体的路径。在这种情况下,如果存在从其他实体到桥接元素的 inverse 路径,并且桥接元素是一个实体,则您可以简单地声明依赖关系,如下所示。
In particular, when the bridge relies on other components (queries, services) to retrieve another entity, there may not even be a path from the bridge element to that entity. In this case, if there is an inverse path from the other entity to the bridged element, and the bridged element is an entity, you can simply declare the dependency from the other entity, as shown below.
. Example 115. Declaring dependencies in a bridge using the inverse path
@Entity
@Indexed
@TypeBinding(binder = @TypeBinderRef(type = ScientificPapersReferencedByBinder.class)) (1)
public class ScientificPaper {
@Id
private Integer id;
private String title;
@ManyToMany
private List<ScientificPaper> references = new ArrayList<>();
public ScientificPaper() {
}
// Getters and setters
// ...
}
public class ScientificPapersReferencedByBinder implements TypeBinder {
@Override
public void bind(TypeBindingContext context) {
context.dependencies()
.fromOtherEntity( ScientificPaper.class, "references" ) (1)
.use( "title" ); (2)
IndexFieldReference<String> papersReferencingThisOneField = context.indexSchemaElement()
.field( "referencedBy", f -> f.asString().analyzer( "english" ) )
.multiValued()
.toReference();
context.bridge( ScientificPaper.class, new Bridge( papersReferencingThisOneField ) );
}
private static class Bridge implements TypeBridge<ScientificPaper> {
private final IndexFieldReference<String> referencedByField;
private Bridge(IndexFieldReference<String> referencedByField) { (2)
this.referencedByField = referencedByField;
}
@Override
public void write(DocumentElement target, ScientificPaper paper, TypeBridgeWriteContext context) {
for ( String referencingPaperTitle : findReferencingPaperTitles( context, paper ) ) { (3)
target.addValue( referencedByField, referencingPaperTitle );
}
}
private List<String> findReferencingPaperTitles(TypeBridgeWriteContext context, ScientificPaper paper) {
Session session = context.extension( HibernateOrmExtension.get() ).session();
Query<String> query = session.createQuery(
"select p.title from ScientificPaper p where :this member of p.references",
String.class );
query.setParameter( "this", paper );
return query.list();
}
}
}
当前,以这种方式声明的依赖关系将在“其他实体”被删除时被忽略。
Currently, dependencies declared this way will be ignored when the "other entity" gets deleted.
请参见 HSEARCH-3567 以跟踪解决这个问题的进度。
See HSEARCH-3567 to track progress on solving this problem.
12.8. Declaring and writing to index fields
12.8.1. Basics
实现 PropertyBinder 或 TypeBinder 时,必须声明桥将编写的索引字段。此声明使用专门的 DSL 执行。
When implementing a PropertyBinder or TypeBinder, it is necessary to declare the index fields that the bridge will contribute to. This declaration is performed using a dedicated DSL.
此 DSL 的入口点是 IndexNode,它表示装订器将数据推送到其中的文档结构部分。从 IndexNode 开始,可以声明字段。
The entry point to this DSL is the IndexNode, which represents the part of the document structure that the binder will push data to. From the IndexNode, it is possible to declare fields.
每个字段的声明都会产生一个字段 reference。此引用应存储在桥梁中,桥梁将在运行时使用它来设置给定文档(由 DocumentElement 表示)中此字段的值。
The declaration of each field yields a field reference. This reference is to be stored in the bridge, which will use it at runtime to set the value of this field in a given document, represented by a DocumentElement.
下面是一个简单的示例,通过使用 DSL 在属性装订器中声明单个字段,然后将此字段写入属性桥梁中。
Below is a simple example using the DSL to declare a single field in a property binder and then write to that field in a property bridge.
. Example 116. Declaring a simple index field and writing to that field
public class ISBNBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement(); (1)
IndexFieldReference<String> field =
schemaElement.field( (2)
"isbn", (3)
f -> f.asString() (4)
.normalizer( "isbn" )
)
.toReference(); (5)
context.bridge( (6)
ISBN.class, (7)
new ISBNBridge( field ) (8)
);
}
}
private static class ISBNBridge implements PropertyBridge<ISBN> {
private final IndexFieldReference<String> fieldReference;
private ISBNBridge(IndexFieldReference<String> fieldReference) {
this.fieldReference = fieldReference;
}
@Override
public void write(DocumentElement target, ISBN bridgedElement, PropertyBridgeWriteContext context) {
String indexedValue = /* ... (extraction of data, not relevant) ... */
target.addValue( this.fieldReference, indexedValue ); (1)
}
}
12.8.2. Type objects
声明每个字段类型的 lambda 语法很方便,但有时会产生一些问题,尤其是在必须使用完全相同的类型声明多个字段时。
The lambda syntax to declare the type of each field is convenient, but sometimes gets in the way, in particular when multiple fields must be declared with the exact same type.
因此,传递给装订器的上下文对象会公开 typeFactory() 方法。使用此工厂,可以构建可以在多个字段声明中重复使用的 IndexFieldType 对象。
For that reason, the context object passed to binders exposes a typeFactory() method. Using this factory, it is possible to build IndexFieldType objects that can be re-used in multiple field declarations.
. Example 117. Re-using an index field type in multiple field declarations
@Override
public void bind(TypeBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement();
IndexFieldType<String> nameType = context.typeFactory() (1)
.asString() (2)
.analyzer( "name" )
.toIndexFieldType(); (3)
context.bridge( Author.class, new Bridge(
schemaElement.field( "firstName", nameType ) (4)
.toReference(),
schemaElement.field( "lastName", nameType ) (4)
.toReference(),
schemaElement.field( "fullName", nameType ) (4)
.toReference()
) );
}
12.8.3. Multivalued fields
默认情况下,字段被视为单值的:如果尝试在索引编制期间向单值字段添加多个值,则会引发异常。
Fields are considered single-valued by default: if you attempt to add multiple values to a single-valued field during indexing, an exception will be thrown.
若要向字段添加多个值,则必须在字段声明期间将其标记为多值的:
In order to add multiple values to a field, this field must be marked as multivalued during its declaration:
. Example 118. Declaring a field as multivalued
@Override
public void bind(TypeBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement();
context.bridge( Author.class, new Bridge(
schemaElement.field( "names", f -> f.asString().analyzer( "name" ) )
.multiValued() (1)
.toReference()
) );
}
12.8.4. Object fields
前几部分只介绍了带有值字段的扁平模式,但索引模式实际上可以组织成树结构,其中有两个类别的索引字段:
The previous sections only presented flat schemas with value fields, but the index schema can actually be organized in a tree structure, with two categories of index fields:
-
Value fields, often simply called "fields", which hold an atomic value of a specific type: string, integer, date, …
-
Object fields, which hold a composite value.
对象字段的声明方式类似于值字段,但需要一个附加步骤来声明每个子字段,如下所示。
Object fields are declared similarly to value fields, with an additional step to declare each subfield, as shown below.
. Example 119. Declaring an object field
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement();
IndexSchemaObjectField summaryField =
schemaElement.objectField( "summary" ); (1)
IndexFieldType<BigDecimal> amountFieldType = context.typeFactory()
.asBigDecimal().decimalScale( 2 )
.toIndexFieldType();
context.bridge( List.class, new Bridge(
summaryField.toReference(), (2)
summaryField.field( "total", amountFieldType ) (3)
.toReference(),
summaryField.field( "books", amountFieldType ) (3)
.toReference(),
summaryField.field( "shipping", amountFieldType ) (3)
.toReference()
) );
}
对象字段的子字段可以包括对象字段。 |
The subfields of an object field can include object fields. |
如同值字段一样,对象字段默认情况下是单值的。如果您希望使其成为多值的,请务必在对象字段定义期间调用 .multiValued()。 |
Just as value fields, object fields are single-valued by default. Be sure to call .multiValued() during the object field definition if you want to make it multivalued. |
对象字段及其子字段各分配一个引用,该引用将供桥接用于写入文档,如下例所示。
Object fields as well as their subfields are each assigned a reference, which will be used by the bridge to write to documents, as shown in the example below.
. Example 120. Writing to an object field
@Override
public void write(DocumentElement target, List bridgedElement, PropertyBridgeWriteContext context) {
List<InvoiceLineItem> lineItems = (List<InvoiceLineItem>) bridgedElement;
BigDecimal total = BigDecimal.ZERO;
BigDecimal books = BigDecimal.ZERO;
BigDecimal shipping = BigDecimal.ZERO;
/* ... (computation of amounts, not relevant) ... */
DocumentElement summary = target.addObject( this.summaryField ); (1)
summary.addValue( this.totalField, total ); (2)
summary.addValue( this.booksField, books ); (2)
summary.addValue( this.shippingField, shipping ); (2)
}
12.8.5. Object structure
默认情况下,对象字段将变平,这意味着不会保留树结构。有关详细信息,请参阅 DEFAULT or FLATTENED structure 。
By default, object fields are flattened, meaning that the tree structure is not preserved. See DEFAULT or FLATTENED structure for more information.
可以通过向 objectField 方法传递参数来切换到 a nested structure,如下所示。然后,对象字段的每个值都会作为一个单独的嵌套文档进行透明索引,而无需对桥接的 write 方法进行任何更改。
It is possible to switch to a nested structure by passing an argument to the objectField method, as shown below. Each value of the object field will then transparently be indexed as a separate nested document, without any change to the write method of the bridge.
. Example 121. Declaring an object field as nested
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement();
IndexSchemaObjectField lineItemsField =
schemaElement.objectField( (1)
"lineItems", (2)
ObjectStructure.NESTED (3)
)
.multiValued(); (4)
context.bridge( List.class, new Bridge(
lineItemsField.toReference(), (5)
lineItemsField.field( "category", f -> f.asString() ) (6)
.toReference(),
lineItemsField.field( "amount", f -> f.asBigDecimal().decimalScale( 2 ) ) (7)
.toReference()
) );
}
12.8.6. Dynamic fields with field templates
在上述几部分中声明的字段都是 static:它们的路径和类型在引导时是已知的。
Field declared in the sections above are all static: their path and type are known on bootstrap.
在一些非常特殊的情况下,字段的路径在您实际索引它之前是未知的。例如,您可能希望通过使用映射键作为字段名称来索引 Map<String, Integer>,或者索引事先不知道其模式的 JSON 对象的属性。那么,字段将被视为 dynamic。
In some very specific cases, the path of a field is not known until you actually index it; for example, you may want to index a Map<String, Integer> by using the map keys as field names, or index the properties of a JSON object whose schema is not known in advance. The fields, then, are considered dynamic.
动态字段不会在引导时声明,但需要匹配在引导时声明的字段 template。模板包括字段类型和结构信息(多值或不是,…),但省略字段名称。
Dynamic fields are not declared on bootstrap, but need to match a field template that is declared on bootstrap. The template includes the field types and structural information (multivalued or not, …), but omits the field names.
字段模板始终在粘合剂中声明:在 type binder 或 property binder 中。对于静态字段,声明模板的入口点是传递给粘合剂的 bind(…) 方法的 IndexNode。对模式元素执行 fieldTemplate 方法的调用将声明一个字段模板。
A field template is always declared in a binder: either in a type binder or in a property binder. As for static fields, the entry point to declaring a template is the IndexNode passed to the binder’s bind(…) method. A call to the fieldTemplate method on the schema element will declare a field template.
假设在绑定期间声明了字段模板,那么桥梁可以通过调用 addValue 并传递字段名称(作为字符串)和字段值,在索引编制时将动态字段添加到 DocumentElement。
Assuming a field template was declared during binding, the bridge can then add dynamic fields to the DocumentElement when indexing, by calling addValue and passing the field name (as a string) and the field value.
以下是使用 DSL 声明属性绑定器中的字段模板,然后写入该字段中的一个简单示例。
Below is a simple example using the DSL to declare a field template in a property binder and then write to that field in a property bridge.
. Example 122. Declaring a field template and writing to a dynamic field
public class UserMetadataBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement();
IndexSchemaObjectField userMetadataField =
schemaElement.objectField( "userMetadata" ); (1)
userMetadataField.fieldTemplate( (2)
"userMetadataValueTemplate", (3)
f -> f.asString().analyzer( "english" ) (4)
); (5)
context.bridge( Map.class, new UserMetadataBridge(
userMetadataField.toReference() (6)
) );
}
}
private static class UserMetadataBridge implements PropertyBridge<Map> {
private final IndexObjectFieldReference userMetadataFieldReference;
private UserMetadataBridge(IndexObjectFieldReference userMetadataFieldReference) {
this.userMetadataFieldReference = userMetadataFieldReference;
}
@Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
Map<String, String> userMetadata = (Map<String, String>) bridgedElement;
DocumentElement indexedUserMetadata = target.addObject( userMetadataFieldReference ); (1)
for ( Map.Entry<String, String> entry : userMetadata.entrySet() ) {
String fieldName = entry.getKey();
String fieldValue = entry.getValue();
indexedUserMetadata.addValue( fieldName, fieldValue ); (2)
}
}
}
尽管很少需要,但您还可以使用 objectFieldTemplate 方法为对象字段声明模板。 |
Though rarely necessary, you can also declare templates for object fields using the objectFieldTemplate methods. |
还可以将具有不同类型的多个字段添加到同一对象。为此,请确保可以从字段名称推断出字段的格式。然后,您可以声明多个模板,并为每个模板分配一个路径模式,如下例所示。
It is also possible to add multiple fields with different types to the same object. To that end, make sure that the format of a field can be inferred from the field name. You can then declare multiple templates and assign a path pattern to each template, as shown below.
. Example 123. Declaring multiple field templates with different types
public class MultiTypeUserMetadataBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies()
/* ... (declaration of dependencies, not relevant) ... */
IndexSchemaElement schemaElement = context.indexSchemaElement();
IndexSchemaObjectField userMetadataField =
schemaElement.objectField( "multiTypeUserMetadata" ); (1)
userMetadataField.fieldTemplate( (2)
"userMetadataValueTemplate_int", (3)
f -> f.asInteger().sortable( Sortable.YES ) (4)
)
.matchingPathGlob( "*_int" ); (5)
userMetadataField.fieldTemplate( (6)
"userMetadataValueTemplate_default",
f -> f.asString().analyzer( "english" )
);
context.bridge( Map.class, new Bridge( userMetadataField.toReference() ) );
}
}
private static class Bridge implements PropertyBridge<Map> {
private final IndexObjectFieldReference userMetadataFieldReference;
private Bridge(IndexObjectFieldReference userMetadataFieldReference) {
this.userMetadataFieldReference = userMetadataFieldReference;
}
@Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
Map<String, Object> userMetadata = (Map<String, Object>) bridgedElement;
DocumentElement indexedUserMetadata = target.addObject( userMetadataFieldReference ); (1)
for ( Map.Entry<String, Object> entry : userMetadata.entrySet() ) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
indexedUserMetadata.addValue( fieldName, fieldValue ); (2)
}
}
}
字段模板优先级Hibernate Search 尝试按照声明模板的顺序匹配模板,因此你应始终先声明具有最具体路径模式的模板。 |
Precedence of field templates Hibernate Search tries to match templates in the order they are declared, so you should always declare the templates with the most specific path pattern first. |
在给定模式元素声明的模板可以在该元素的子元素中匹配。例如,如果在实体根处声明模板(通过 type bridge ),这些模板将在该实体的每个属性桥接中隐式可用。在这种情况下,在属性桥接声明的模板优先级高于在类型桥接声明的模板。
Templates declared on a given schema element can be matched in children of that element. For example, if you declare templates at the root of your entity (through a type bridge), these templates will be implicitly available in every single property bridge of that entity. In such cases, templates declared in property bridges will take precedence over those declared in the type bridge.
12.9. Defining index field types
12.9.1. Basics
基于 Lucene 的搜索引擎(包括 Elasticsearch)的一个特殊性是,字段类型比仅数据类型(“string”、“integer”等)复杂得多。
A specificity of Lucene-based search engines (including Elasticsearch) is that field types are much more complex than just a data type ("string", "integer", …).
声明字段时,必须不仅声明数据类型,还要声明各种特性,这些特性将定义数据如何确切存储:该字段是否可以排序,是否可以投影,是否进行分析,如果是,则使用哪个分析器等。
When declaring a field, you must not only declare the data type, but also various characteristics that will define how the data is stored exactly: is the field sortable, is it projectable, is it analyzed and if so with which analyzer, …
正因为有了这样的复杂性,所以当字段类型必须在各种绑定器中定义时(ValueBinder、PropertyBinder、TypeBinder),则使用专用 DSL 定义它们。
Because of this complexity, when field types must be defined in the various binders (ValueBinder, PropertyBinder, TypeBinder), they are defined using a dedicated DSL.
该 DSL 的入口点是 IndexFieldTypeFactory。该类型工厂通常可以通过传递给绑定器的上下文对象访问(context.typeFactory())。对于 PropertyBinder 和 TypeBinder,该类型工厂还可以传递给传递给 field 方法的 lambda 表达式,以在线定义字段类型。
The entry point to this DSL is the IndexFieldTypeFactory. The type factory is generally accessible though the context object passed to the binders (context.typeFactory()). In the case of PropertyBinder and TypeBinder, the type factory can also be passed to the lambda expression passed to the field method to define the field type inline.
该类型工厂公开了各种 as*() 方法,例如 asString 或 asLocalDate。这些是类型定义 DSL 的第一步,其中定义数据类型。它们返回其他步骤,从中可以设置选项,如分析器。有关示例,请参见以下内容。
The type factory exposes various as*() methods, for example asString or asLocalDate. These are the first steps of the type definition DSL, where the data type is defined. They return other steps, from which options can be set, such as the analyzer. See below for an example.
. Example 124. Defining a field type
IndexFieldType<String> type = context.typeFactory() (1)
.asString() (2)
.normalizer( "isbn" ) (3)
.sortable( Sortable.YES ) (3)
.toIndexFieldType(); (4)
在 ValueBinder 中,省略了对 toIndexFieldType() 的调用: context.bridge(…) 期待接收传入的最后一个 DSL 步骤,而不是一个完全构建的类型。 |
In ValueBinder, the call to toIndexFieldType() is omitted: context.bridge(…) expects to be passed the last DSL step, not a fully built type. |
在 field declaration DSL 的 field 方法传递的 lambda 表达式中也省略了 toIndexFieldType() 。
toIndexFieldType() is also omitted in the lambda expressions passed to the field method of the field declaration DSL.
12.9.2. Available data types
所有可用的数据类型在 IndexFieldTypeFactory 中都有一个专门的 as*() 方法。有关详细信息,请参阅 IndexFieldTypeFactory 的 javadoc 或特定于后端的文档:
All available data types have a dedicated as*() method in IndexFieldTypeFactory. For details, see the javadoc of IndexFieldTypeFactory, or the backend-specific documentation:
12.9.3. Available type options
索引字段类型 DSL 中的大多数可用选项与 @*Field 注释公开的选项相同。有关它们的详细信息,请参阅 Field annotation attributes。
Most of the options available in the index field type DSL are identical to the options exposed by @*Field annotations. See Field annotation attributes for details about them.
其他选项在以下部分进行了解释。
Other options are explained in the following sections.
12.9.4. DSL converter
本节与 ValueBinder 无关:Hibernate Search 自动为值桥设置 DSL 转换器,创建一个仅仅是委托给值桥的 DSL 转换器。 |
This section is not relevant for ValueBinder: Hibernate Search sets the DSL converter automatically for value bridges, creating a DSL converter that simply delegates to the value bridge. |
各种搜索 DSL 公开了某些预期字段值的方法:matching()、between()、atMost()、missingValue().use()、… 按默认,预期类型将与数据类型相同,即,如果您调用了 asString(),则为 String;如果您调用了 asLocalDate(),则为 LocalDate,等等。
The various search DSLs expose some methods that expect a field value: matching(), between(), atMost(), missingValue().use(), … By default, the expected type will be the same as the data type, i.e. String if you called asString(), LocalDate if you called asLocalDate(), etc.
当桥在索引时将值从不同类型转换时,这可能会很烦人。例如,如果桥在索引时将枚举转换为字符串,则您可能不想将字符串传递给搜索谓词,而是枚举。
This can be annoying when the bridge converts values from a different type when indexing. For example, if the bridge converts an enum to a string when indexing, you probably don’t want to pass a string to search predicates, but rather the enum.
通过在字段类型上设置 DSL 转换器,可以更改传递给各种 DSL 的值类型。有关示例,请参见以下内容。
By setting a DSL converter on a field type, it is possible to change the expected type of values passed to the various DSL, See below for an example.
. Example 125. Assigning a DSL converter to a field type
IndexFieldType<String> type = context.typeFactory()
.asString() (1)
.normalizer( "isbn" )
.sortable( Sortable.YES )
.dslConverter( (2)
ISBN.class, (3)
(value, convertContext) -> value.getStringValue() (4)
)
.toIndexFieldType();
ISBN expectedISBN = /* ... */
List<Book> result = searchSession.search( Book.class )
.where( f -> f.match().field( "isbn" )
.matching( expectedISBN ) ) (1)
.fetchHits( 20 );
12.9.5. Projection converter
本节与 ValueBinder 无关:Hibernate Search 自动为值桥设置投影转换器,创建一个仅仅是委托给值桥的投影转换器。 |
This section is not relevant for ValueBinder: Hibernate Search sets the projection converter automatically for value bridges, creating a projection converter that simply delegates to the value bridge. |
默认情况下,由 field projections 或 aggregations 返回的值的类型将与相应字段的数据类型相同,即如果您调用了 asString(),则为 String,如果您调用了 asLocalDate(),则为 LocalDate,等等。
By default, the type of values returned by field projections or aggregations will be the same as the data type of the corresponding field, i.e. String if you called asString(), LocalDate if you called asLocalDate(), etc.
当桥在索引时将值从不同类型转换时,这可能会很烦人。例如,如果桥在索引时将枚举转换为字符串,则您可能不想让投影返回字符串,而是枚举。
This can be annoying when the bridge converts values from a different type when indexing. For example, if the bridge converts an enum to a string when indexing, you probably don’t want projections to return a string, but rather the enum.
通过在字段类型上设置投影转换器,可以更改字段投影或聚合返回的值的类型。有关示例,请参见以下内容。
By setting a projection converter on a field type, it is possible to change the type of values returned by field projections or aggregations. See below for an example.
. Example 126. Assigning a projection converter to a field type
IndexFieldType<String> type = context.typeFactory()
.asString() (1)
.projectable( Projectable.YES )
.projectionConverter( (2)
ISBN.class, (3)
(value, convertContext) -> ISBN.parse( value ) (4)
)
.toIndexFieldType();
List<ISBN> result = searchSession.search( Book.class )
.select( f -> f.field( "isbn", ISBN.class ) ) (1)
.where( f -> f.matchAll() )
.fetchHits( 20 );
12.10. Defining named predicates
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
实现 PropertyBinder 或 TypeBinder 时,可以将“命名谓词”分配到索引模式元素(索引根或 object field )。
When implementing a PropertyBinder or TypeBinder, it is possible to assign "named predicates" to index schema elements (either the index root or an object field).
然后,可以使用这些命名谓词 through the Search DSL,按名称引用它们并可选择传递参数。主要区别在于实施对调用者是隐藏的:他们不需要了解如何索引数据才能使用命名谓词。
These named predicates will then be usable through the Search DSL, referencing them by name and optionally passing parameters. The main point is that the implementation is hidden from callers: they do not need to understand how data is indexed in order to use a named predicate.
以下是使用 DSL 声明对象字段并在属性绑定器中为该字段分配命名谓词的一个简单示例。
Below is a simple example using the DSL to declare an object field and assign a named predicate to that field, in a property binder.
. Example 127. Declaring a named predicate
/**
* A binder for Stock Keeping Unit (SKU) identifiers, i.e. Strings with a specific format.
*/
public class SkuIdentifierBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
IndexSchemaObjectField skuIdObjectField = context.indexSchemaElement()
.objectField( context.bridgedElement().name() );
IndexFieldType<String> skuIdPartType = context.typeFactory()
.asString().normalizer( "lowercase" ).toIndexFieldType();
context.bridge( String.class, new Bridge(
skuIdObjectField.toReference(),
skuIdObjectField.field( "departmentCode", skuIdPartType ).toReference(),
skuIdObjectField.field( "collectionCode", skuIdPartType ).toReference(),
skuIdObjectField.field( "itemCode", skuIdPartType ).toReference()
) );
skuIdObjectField.namedPredicate( (1)
"skuIdMatch", (2)
new SkuIdentifierMatchPredicateDefinition() (3)
);
}
// ... class continues below
// ... class SkuIdentifierBinder (continued)
private static class Bridge implements PropertyBridge<String> { (1)
private final IndexObjectFieldReference skuIdObjectField;
private final IndexFieldReference<String> departmentCodeField;
private final IndexFieldReference<String> collectionCodeField;
private final IndexFieldReference<String> itemCodeField;
private Bridge(IndexObjectFieldReference skuIdObjectField,
IndexFieldReference<String> departmentCodeField,
IndexFieldReference<String> collectionCodeField,
IndexFieldReference<String> itemCodeField) {
this.skuIdObjectField = skuIdObjectField;
this.departmentCodeField = departmentCodeField;
this.collectionCodeField = collectionCodeField;
this.itemCodeField = itemCodeField;
}
@Override
public void write(DocumentElement target, String skuId, PropertyBridgeWriteContext context) {
DocumentElement skuIdObject = target.addObject( this.skuIdObjectField );(2)
// An SKU identifier is formatted this way: "<department code>.<collection code>.<item code>".
String[] skuIdParts = skuId.split( "\\." );
skuIdObject.addValue( this.departmentCodeField, skuIdParts[0] ); (3)
skuIdObject.addValue( this.collectionCodeField, skuIdParts[1] ); (3)
skuIdObject.addValue( this.itemCodeField, skuIdParts[2] ); (3)
}
}
// ... class continues below
// ... class SkuIdentifierBinder (continued)
private static class SkuIdentifierMatchPredicateDefinition implements PredicateDefinition { (1)
@Override
public SearchPredicate create(PredicateDefinitionContext context) {
SearchPredicateFactory f = context.predicate(); (2)
String pattern = context.params().get( "pattern", String.class ); (3)
return f.and().with( and -> { (4)
// An SKU identifier pattern is formatted this way: "<department code>.<collection code>.<item code>".
// Each part supports * and ? wildcards.
String[] patternParts = pattern.split( "\\." );
if ( patternParts.length > 0 ) {
and.add( f.wildcard()
.field( "departmentCode" ) (5)
.matching( patternParts[0] ) );
}
if ( patternParts.length > 1 ) {
and.add( f.wildcard()
.field( "collectionCode" )
.matching( patternParts[1] ) );
}
if ( patternParts.length > 2 ) {
and.add( f.wildcard()
.field( "itemCode" )
.matching( patternParts[2] ) );
}
} ).toPredicate(); (6)
}
}
}
@Entity
@Indexed
public class ItemStock {
@Id
@PropertyBinding(binder = @PropertyBinderRef(type = SkuIdentifierBinder.class)) (1)
private String skuId;
private int amountInStock;
// Getters and setters
// ...
}
12.11. Assigning default bridges with the bridge resolver
12.11.1. Basics
默认情况下, @*Field annotations 和 @DocumentId annotation 均支持范围广泛的标准类型,无需告知 Hibernate Search 如何将值转换为可索引形式。
Both the @*Field annotations and the @DocumentId annotation support a broad range of standard types by default, without needing to tell Hibernate Search how to convert values to something that can be indexed.
在底层,对默认类型支持的处理由桥接解析器负责。例如,当某个属性与 @GenericField 映射,而 @GenericField.valueBridge 或 @GenericField.valueBinder 均未设置时,Hibernate Search 会解析此属性的类型,然后将其传递给桥接解析器,解析器将返回一个合适的桥接,如果没有任何桥接,则会失败。
Under the hood, the support for default types is handled by the bridge resolver. For example, when a property is mapped with @GenericField and neither @GenericField.valueBridge nor @GenericField.valueBinder is set, Hibernate Search will resolve the type of this property, then pass it to the bridge resolver, which will return an appropriate bridge, or fail if there isn’t any.
完全可以自定义桥接解析器,以覆盖现有的默认桥接(例如,对 java.util.Date 进行不同索引),也可以为其他类型(例如,外部库中的地理空间类型)定义默认桥接。
It is possible to customize the bridge resolver, to override existing default bridges (indexing java.util.Date differently, for example) or to define default bridges for additional types (a geospatial type from an external library, for example).
为此,如 Programmatic mapping 中所述定义一个映射配置器,然后按如下所示定义桥接:
To that end, define a mapping configurer as explained in Programmatic mapping, then define bridges as shown below:
示例 128. 使用映射配置程序定义默认桥接器
. Example 128. Defining default bridges with a mapping configurer
public class MyDefaultBridgesConfigurer implements HibernateOrmSearchMappingConfigurer {
@Override
public void configure(HibernateOrmMappingConfigurationContext context) {
context.bridges().exactType( MyCoordinates.class )
.valueBridge( new MyCoordinatesBridge() ); (1)
context.bridges().exactType( MyProductId.class )
.identifierBridge( new MyProductIdBridge() ); (2)
context.bridges().exactType( ISBN.class )
.valueBinder( new ValueBinder() { (3)
@Override
public void bind(ValueBindingContext<?> context) {
context.bridge( ISBN.class, new ISBNValueBridge(),
context.typeFactory().asString().normalizer( "isbn" ) );
}
} );
}
}
12.11.2. Assigning a single binder to multiple types
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
对于更高级别的使用情形,还可以将单个绑定程序分配给给定类型的子类型。这在应类似地索引多种类型时很有用。
For more advanced use cases, it is also possible to assign a single binder to subtypes of a given type. This is useful when many types should be indexed similarly.
下面是一个示例,其中枚举不是作为它们的 .name()(默认值)进行索引的,而是作为从外部服务中检索到的标签进行索引的。
Below is an example where enums are not indexed as their .name() (which is the default), but instead are indexed as their label retrieved from an external service.
示例 129. 使用映射配置程序将单个默认绑定器分配给多个类型
. Example 129. Assigning a single default binder to multiple types with a mapping configurer
context.bridges().subTypesOf( Enum.class ) (1)
.valueBinder( new ValueBinder() {
@Override
public void bind(ValueBindingContext<?> context) {
Class<?> enumType = context.bridgedElement().rawType(); (2)
doBind( context, enumType );
}
private <T> void doBind(ValueBindingContext<?> context, Class<T> enumType) {
BeanHolder<EnumLabelService> serviceHolder = context.beanResolver()
.resolve( EnumLabelService.class, BeanRetrieval.ANY ); (3)
context.bridge(
enumType,
new EnumLabelBridge<>( enumType, serviceHolder )
); (4)
}
} );
12.12. Projection binder
12.12.1. Basics
投影粘合器是一个可插入组件,它实现了将构造函数参数绑定到 projection 的操作。它应用于带 @ProjectionBinding 注释或带 custom annotation 的 projection constructor 的参数。 |
A projection binder is a pluggable component that implements the binding of a constructor parameter to a projection. It is applied to a parameter of a projection constructor with the @ProjectionBinding annotation or with a custom annotation. |
投影粘合剂可以检查构造函数参数,并且应将投影定义分配给该构造函数参数,以便每当调用 projection constructor 时,Hibernate 搜索将通过该构造函数参数传递该投影的结果。
The projection binder can inspect the constructor parameter, and is expected to assign a projection definition to that constructor parameter, so that whenever the projection constructor is invoked, Hibernate Search will pass the result of that projection through that constructor parameter.
实现投影粘合剂需要两个组件:
Implementing a projection binder requires two components:
-
A custom implementation of ProjectionBinder, to bind the projection definition to the parameter at bootstrap. This involves inspecting the constructor parameter if necessary, and instantiating the projection definition.
-
A custom implementation of ProjectionDefinition, to instantiate the projection at runtime. This involves using the projection DSL and returning the resulting SearchProjection.
下面是一个自定义投影粘合剂的示例,它将类型为 String 的参数绑定到索引中 title 字段的投影。
Below is an example of a custom projection binder that binds a parameter of type String to a projection to the title field in the index.
示例 130. 实现和使用 ProjectionBinder |
. Example 130. Implementing and using a ProjectionBinder |
public class MyFieldProjectionBinder implements ProjectionBinder { (1)
@Override
public void bind(ProjectionBindingContext context) { (2)
context.definition( (3)
String.class, (4)
new MyProjectionDefinition() (5)
);
}
// ... class continues below
// ... class MyFieldProjectionBinder (continued)
private static class MyProjectionDefinition (1)
implements ProjectionDefinition<String> { (2)
@Override
public SearchProjection<String> create(SearchProjectionFactory<?, ?> factory,
ProjectionDefinitionContext context) {
return factory.field( "title", String.class ) (3)
.toProjection(); (4)
}
}
}
@ProjectionConstructor
public record MyBookProjection(
@ProjectionBinding(binder = @ProjectionBinderRef( (1)
type = MyFieldProjectionBinder.class
))
String title) {
}
List<MyBookProjection> hits = searchSession.search( Book.class )
.select( MyBookProjection.class )
.where( f -> f.matchAll() )
.fetchHits( 20 );
12.12.2. Multi-valued projections
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
您可以在传递给投影粘合剂的上下文中调用 .multi() 以发现要绑定的构造函数参数是否是多值的(遵循与 implicit inner projection inference 相同的规则),并绑定多值投影。
You can call .multi() on the context passed to the projection binder in order to discover whether the constructor parameter being bound is multi-valued (according to the same rules as implicit inner projection inference), and to bind a multi-valued projection.
示例 131. 实现和使用支持多值投影的 ProjectionBinder
. Example 131. Implementing and using a ProjectionBinder supporting multi-valued projections
public class MyFieldProjectionBinder implements ProjectionBinder {
@Override
public void bind(ProjectionBindingContext context) {
Optional<? extends ProjectionBindingMultiContext> multi = context.multi(); (1)
if ( multi.isPresent() ) {
multi.get().definition( String.class, new MyProjectionDefinition() ); (2)
}
else {
throw new RuntimeException( "This binder only supports multi-valued constructor parameters" ); (3)
}
}
private static class MyProjectionDefinition
implements ProjectionDefinition<List<String>> { (4)
@Override
public SearchProjection<List<String>> create(SearchProjectionFactory<?, ?> factory,
ProjectionDefinitionContext context) {
return factory.field( "tags", String.class )
.multi() (4)
.toProjection();
}
}
}
@ProjectionConstructor
public record MyBookProjection(
@ProjectionBinding(binder = @ProjectionBinderRef( (1)
type = MyFieldProjectionBinder.class
))
List<String> tags) {
}
List<MyBookProjection> hits = searchSession.search( Book.class )
.select( MyBookProjection.class )
.where( f -> f.matchAll() )
.fetchHits( 20 );
12.12.3. Composing projection constructors
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
您可以根据_SomeType_中的 projection constructor mapping,在投影绑定器中传递的上下文中调用_.createObjectDefinition( "someFieldPath", SomeType.class )_以检索 object projection的定义。
You can call .createObjectDefinition( "someFieldPath", SomeType.class ) on the context passed to the projection binder in order to retrieve the definition of an object projection based on the projection constructor mapping of SomeType.
这样实际上允许在投影粘合剂内使用投影构造函数,只需将得到的定义传递给 .definition(…) 或在自定义投影定义中将其委派给它。
This effectively allows using projection constructors within projection binders, simply by passing the resulting definition to .definition(…) or by delegating to it in a custom projection definition.
绑定上下文中公开的其他方法工作方式类似:
Other methods exposed on the binding context work similarly:
-
.createObjectDefinitionMulti(…) returns a multi-valued object projection definition.
-
.createCompositeDefinition(…) returns a (single-valued) composite projection definition (which, on contrary to an object projection, is not bound to an object field in the index).
下面是一个使用 .createObjectDefinition(…) 委派到另一个投影构造函数的示例。
Below is an example using .createObjectDefinition(…) to delegate to another projection constructor.
示例 132. 实现和使用委托给投影构造函数的 ProjectionBinder |
. Example 132. Implementing and using a ProjectionBinder that delegates to a projection constructor |
public class MyObjectFieldProjectionBinder implements ProjectionBinder {
@Override
public void bind(ProjectionBindingContext context) {
var authorProjection = context.createObjectDefinition( (1)
"author", (2)
MyBookProjection.MyAuthorProjection.class, (3)
TreeFilterDefinition.includeAll() (4)
);
context.definition( (5)
MyBookProjection.MyAuthorProjection.class,
authorProjection
);
}
}
@ProjectionConstructor
public record MyBookProjection(
@ProjectionBinding(binder = @ProjectionBinderRef( (1)
type = MyObjectFieldProjectionBinder.class
))
MyAuthorProjection author) {
@ProjectionConstructor (2)
public record MyAuthorProjection(String name) {
}
}
List<MyBookProjection> hits = searchSession.search( Book.class )
.select( MyBookProjection.class )
.where( f -> f.matchAll() )
.fetchHits( 20 );
12.12.4. Passing parameters
可以通过两种方式将参数传递至属性桥:
There are two ways to pass parameters to property bridges:
-
One is (mostly) limited to string parameters, but is trivial to implement.
-
The other can allow any type of parameters, but requires you to declare your own annotations.
Simple, string parameters
您可以将字符串参数传递给 @ProjectionBinderRef 注释,然后在粘合剂中使用它们:
You can pass string parameters to the @ProjectionBinderRef annotation and then use them later in the binder:
示例 133. 使用 @ProjectionBinderRef 注释向 ProjectionBinder 传递参数
. Example 133. Passing parameters to a ProjectionBinder using the @ProjectionBinderRef annotation
public class MyFieldProjectionBinder implements ProjectionBinder {
@Override
public void bind(ProjectionBindingContext context) {
String fieldName = context.param( "fieldName", String.class ); (1)
context.definition(
String.class,
new MyProjectionDefinition( fieldName ) (2)
);
}
private static class MyProjectionDefinition
implements ProjectionDefinition<String> {
private final String fieldName;
public MyProjectionDefinition(String fieldName) { (2)
this.fieldName = fieldName;
}
@Override
public SearchProjection<String> create(SearchProjectionFactory<?, ?> factory,
ProjectionDefinitionContext context) {
return factory.field( fieldName, String.class ) (3)
.toProjection();
}
}
}
@ProjectionConstructor
public record MyBookProjection(
@ProjectionBinding(binder = @ProjectionBinderRef(
type = MyFieldProjectionBinder.class,
params = @Param( name = "fieldName", value = "title" )
)) String title) { (1)
}
Parameters with custom annotations
您可以通过定义具有属性的 custom annotation将任何类型的参数传递给桥接器:
You can pass parameters of any type to the bridge by defining a custom annotation with attributes:
示例 134. 使用自定义注释向 PropertyBinder 传递参数
. Example 134. Passing parameters to a PropertyBinder using a custom annotation
@Retention(RetentionPolicy.RUNTIME) (1)
@Target({ ElementType.PARAMETER }) (2)
@MethodParameterMapping(processor = @MethodParameterMappingAnnotationProcessorRef( (3)
type = MyFieldProjectionBinding.Processor.class
))
@Documented (4)
public @interface MyFieldProjectionBinding {
String fieldName() default ""; (5)
class Processor (6)
implements MethodParameterMappingAnnotationProcessor<MyFieldProjectionBinding> { (7)
@Override
public void process(MethodParameterMappingStep mapping, MyFieldProjectionBinding annotation,
MethodParameterMappingAnnotationProcessorContext context) {
MyFieldProjectionBinder binder = new MyFieldProjectionBinder(); (8)
if ( !annotation.fieldName().isEmpty() ) { (9)
binder.fieldName( annotation.fieldName() );
}
mapping.projection( binder ); (10)
}
}
}
public class MyFieldProjectionBinder implements ProjectionBinder {
private String fieldName = "name";
public MyFieldProjectionBinder fieldName(String fieldName) { (1)
this.fieldName = fieldName;
return this;
}
@Override
public void bind(ProjectionBindingContext context) {
context.definition(
String.class,
new MyProjectionDefinition( fieldName ) (2)
);
}
private static class MyProjectionDefinition
implements ProjectionDefinition<String> {
private final String fieldName;
public MyProjectionDefinition(String fieldName) { (2)
this.fieldName = fieldName;
}
@Override
public SearchProjection<String> create(SearchProjectionFactory<?, ?> factory,
ProjectionDefinitionContext context) {
return factory.field( fieldName, String.class ) (3)
.toProjection();
}
}
}
@ProjectionConstructor
public record MyBookProjection(
@MyFieldProjectionBinding(fieldName = "title") (1)
String title) {
}
12.12.5. Injecting beans into the binder
使用 compatible frameworks,Hibernate Search 支持将 bean 注入到:
With compatible frameworks, Hibernate Search supports injecting beans into:
-
the MethodParameterMappingAnnotationProcessor if you use custom annotations.
-
the ProjectionBinder if you use the @ProjectionBinding annotation.
传递至属性粘合器 bind
方法的上下文还公开了一个 beanResolver()
方法,用于访问 Bean 解析器并明确实例化 Bean。
The context passed to the property binder’s bind method also exposes a beanResolver() method to access the bean resolver and instantiate beans explicitly.
有关更多详细信息,请参见 Bean injection。
See Bean injection for more details.
12.12.6. Programmatic mapping
您也可以通过 programmatic mapping应用投影绑定器。只需将绑定器的实例传递给_.projection(…)_即可。您可以通过绑定器的构造函数或setter传递参数。
You can apply a projection binder through the programmatic mapping too. Just pass an instance of the binder to .projection(…). You can pass arguments either through the binder’s constructor, or through setters.
示例 135. 使用 .projection(…) 应用 ProjectionBinder
. Example 135. Applying a ProjectionBinder with .projection(…)
TypeMappingStep myBookProjectionMapping = mapping.type( MyBookProjection.class );
myBookProjectionMapping.mainConstructor().projectionConstructor();
myBookProjectionMapping.mainConstructor().parameter( 0 )
.projection( new MyFieldProjectionBinder().fieldName( "title" ) );
12.12.7. Other incubating features
以下列出的特性尚处于 incubating 阶段:它们仍在积极开发中。
Features detailed below are incubating: they are still under active development.
通常 compatibility policy 不适用:孵化元素(例如类型、方法、配置属性等)的契约在后续版本中可能会以向后不兼容的方式更改,甚至可能被移除。
The usual compatibility policy does not apply: the contract of incubating elements (e.g. types, methods, configuration properties, etc.) may be altered in a backward-incompatible way — or even removed — in subsequent releases.
我们建议您使用孵化特性,以便开发团队可以收集反馈并对其进行改进,但在需要时您应做好更新依赖于这些特性的代码的准备。
You are encouraged to use incubating features so the development team can get feedback and improve them, but you should be prepared to update code which relies on them as needed.
传递给投影绑定程序的 bind 方法的上下文公开了一个 constructorParameter() 方法,该方法可访问正在绑定的构造函数参数的元数据。
The context passed to the projection binder’s bind method exposes a constructorParameter() method that gives access to metadata about the constructor parameter being bound.
元数据可用于详细检查构造函数参数:
The metadata can be used to inspect the constructor parameter in details:
-
Getting the name of the constructor parameter.
-
Checking the type of the constructor parameter.
同样, context used for multi-valued projection binding也公开了一个_containerElement()_方法,该方法允许访问元素类型的类型(多值)构造函数参数类型。
Similarly, the context used for multi-valued projection binding exposes a containerElement() method that gives access to the type of elements of the (multi-valued) constructor parameter type.
有关更多信息,请参见 javadoc。
See the javadoc for more information.
构造函数参数的名称仅可用: |
The name of the constructor parameter is only available: |
对于记录类型的规范构造方法,无论编译标志如何。
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.
以下是此元数据最简单用法的一个示例,即获取构造函数参数名称并将其用作字段名称。
Below is an example of the simplest use of this metadata, getting the constructor parameter name and using it as a field name.
示例 136. 投影到名称与 ProjectionBinder 中的构造函数参数名称相同的字段上
. Example 136. Projecting on a field whose name is the same as the constructor parameter name in a ProjectionBinder
public class MyFieldProjectionBinder implements ProjectionBinder {
@Override
public void bind(ProjectionBindingContext context) {
var constructorParam = context.constructorParameter(); (1)
context.definition(
String.class,
new MyProjectionDefinition( constructorParam.name().orElseThrow() ) (2)
);
}
private static class MyProjectionDefinition
implements ProjectionDefinition<String> {
private final String fieldName;
private MyProjectionDefinition(String fieldName) {
this.fieldName = fieldName;
}
@Override
public SearchProjection<String> create(SearchProjectionFactory<?, ?> factory,
ProjectionDefinitionContext context) {
return factory.field( fieldName, String.class ) (3)
.toProjection();
}
}
}
@ProjectionConstructor
public record MyBookProjection(
@ProjectionBinding(binder = @ProjectionBinderRef( (1)
type = MyFieldProjectionBinder.class
))
String title) {
}