我们很高兴地宣布发布 Hibernate Search 6.2.0.Final 版本。
本版本特别引入了新的独立 POJO 映射器,使用 @ProjectionConstructor
将类/记录映射到投影,高亮显示,Elasticsearch 架构导出,以及更多错误修复和改进。
6.2.0.Final 还包括与 Elasticsearch 8.8 和 OpenSearch 2.8 的兼容性,以及将 -orm6
艺术品升级到 Hibernate ORM 6.2。
与 Hibernate Search 6.1 相比的新功能
依赖项升级
- Hibernate ORM
-
Hibernate Search 默认仍然依赖于 Hibernate ORM 5.6。
-orm6
艺术品依赖于 Hibernate ORM 6.2。
- Lucene
-
Lucene 后端仍然使用 Lucene 8.11。
- Elasticsearch
-
Elasticsearch 后端现在可以与 Elasticsearch 8.8 和 7.17 以及其他已经兼容的版本一起工作。
- OpenSearch
-
Elasticsearch 后端现在可以与 OpenSearch 1.3 和 2.8 以及其他已经兼容的版本一起工作。
独立 POJO 映射器
独立 POJO 映射器允许将任意 POJO 映射到索引。
与 Hibernate ORM 集成相比,其关键特性是能够在没有 Hibernate ORM 或关系型数据库的情况下运行。它可以用于索引来自任意数据存储的实体,甚至(尽管通常不推荐)可以将 Lucene 或 Elasticsearch 作为主数据存储使用。
有关独立 POJO 映射器的更多信息,请参阅 参考文档的此部分。
要开始使用独立POJO映射器,请参阅此入门指南。
将索引内容映射到自定义类型(投影构造函数)
Hibernate Search 现在可以通过将 @ProjectionConstructor
注解应用到自定义类型(通常是记录)或它们的构造函数,通过映射自定义类型(通常为记录)来定义投影的能力。
@ProjectionConstructor
public record MyBookProjection(String title, List<Author> authors) {
@ProjectionConstructor
public record Author(String firstName, String lastName) {
}
}
执行此类投影与引用自定义类型一样简单。
List<MyBookProjection> hits = searchSession.search( Book.class )
.select( MyBookProjection.class )
.where( f -> f.matchAll() )
.fetchHits( 20 );
可以通过显式注解配置更高级的投影,例如,将实体标识符投影出来。
@ProjectionConstructor
public record MyBookIdAndTitleProjection(
@IdProjection (1)
Integer id,
String title (2)
) {
}
1 | 将实体标识符投影出来(通过显式注解进行显式投影) |
2 | 将字段 title 投影出来(隐式投影) |
显式注解还可以更精确地配置字段投影,例如,设置字段投影的路径,而不是仅限于构造函数参数的名称。
@ProjectionConstructor
public record MyBookTitleAndAuthorNamesProjection(
@FieldProjection (1)
String title,
@FieldProjection(path = "authors.lastName") (2)
List<String> authorLastNames
) {
}
1 | 将字段 title 投影出来(路径来自构造函数参数的名称) |
2 | 将字段 authors.lastName 投影出来(显式路径) |
有关更多信息,请参阅此部分参考文档。
高亮显示
Hibernate Search 现在在搜索 DSL 中提供了一项新功能:高亮显示。
高亮显示是一种投影,它返回匹配文档全文字段中的片段,这些片段导致了查询匹配。导致匹配的特定术语通过一对开闭标签“高亮”显示。它可以帮助用户快速识别他们在结果页面上搜索的信息。
例如,可以这样启用全文字段上的高亮显示
@Entity
@Indexed
public class Book {
@Id
private Integer id;
@FullTextField(analyzer = "english")
private String title;
@FullTextField(analyzer = "english", projectable = Projectable.YES)
private String description;
}
然后这样检索高亮显示
SearchSession searchSession = /* ... */
List<List<String>> result = searchSession.search( Book.class )
.select( f -> f.highlight( "description" ) )
.where( f -> f.match().field( "description" ).matching( "robot" ) )
.fetchHits( 20 );
这可能返回以下高亮显示
# Hit #0
["A <em>robot</em> becomes self-aware."]
# Hit #1
["A <em>robot</em> helps investigate a murder on an extrasolar colony.",
"On this planet, <em>robots</em> are used extensively."]
# etc.
这是一个简单的示例,但高亮显示可以广泛配置。有关更多信息,请参阅此部分参考文档。
索引计划过滤器
Hibernate Search 现在允许在索引计划中启用/禁用索引(特别是监听器触发的索引),包括全部(对所有类型)或按类型启用/禁用。
例如,下面的代码将禁用类 Company
的所有实例的自动索引,但不会禁用其子类 Customer
的实例。
SearchMapping searchMapping = /* ... */
searchMapping.indexingPlanFilter(
ctx -> ctx.exclude( Company.class )
.include( Customer.class )
);
索引计划过滤器也可以按会话配置(尽管在使用outbox-polling
协调策略时存在限制)。
SearchSession searchSession = /* ... */
searchSession.indexingPlanFilter(
ctx -> ctx.exclude( Company.class )
.include( Customer.class )
);
有关更多信息,请参阅此部分参考文档。
映射改进
@IndexedEmbedded(excludePaths = …)
-
@IndexedEmbedded
注解现在公开了excludePaths
属性,允许包含所有路径,但仅选择性排除少数路径,与之前存在的仅选择性包含路径的includePaths
方法相反。有关更多信息,请参阅此部分参考文档。
- 可投影字段
-
现在,默认情况下,所有字段都可以通过 Elasticsearch 后端进行投影。
做出此更改是因为将字段设置为可投影不会对 Elasticsearch 后端产生任何性能影响。
由于将字段设置为可投影会对 Lucene 后端产生性能影响,因此 Lucene 后端的默认设置没有改变:Lucene 字段仍然需要显式设置为可投影。
搜索 DSL 改进
- 使用
and
/or
/not
前缀的简化布尔运算符 -
对于更简单的用例,现在可以避免使用相对复杂的
bool
前缀,转而使用新的and
/or
/not
前缀List<Book> hits = searchSession.search( Book.class ) .where( f -> f.and( f.match().field( "title" ) .matching( "robot" ), f.match().field( "description" ) .matching( "crime" ) ) ) .fetchHits( 20 );
List<Book> hits = searchSession.search( Book.class ) .where( f -> f.or( f.match().field( "title" ) .matching( "robot" ), f.match().field( "description" ) .matching( "investigation" ) ) ) .fetchHits( 20 );
List<Book> hits = searchSession.search( Book.class ) .where( f -> f.not( f.match() .field( "genre" ) .matching( Genre.SCIENCE_FICTION ) ) ) .fetchHits( 20 );
- 为复杂、根布尔前缀提供更短的语法
-
现在可以使用
.where( (f, b) → … )
代替.where( f → f.bool( b → … ) )
MySearchParameters searchParameters = getSearchParameters(); List<Book> hits = searchSession.search( Book.class ) .where( (f, root) -> { root.add( f.matchAll() ); if ( searchParameters.getGenreFilter() != null ) { root.add( f.match().field( "genre" ) .matching( searchParameters.getGenreFilter() ) ); } if ( searchParameters.getFullTextFilter() != null ) { root.add( f.match().fields( "title", "description" ) .matching( searchParameters.getFullTextFilter() ) ); } if ( searchParameters.getPageCountMaxFilter() != null ) { root.add( f.range().field( "pageCount" ) .atMost( searchParameters.getPageCountMaxFilter() ) ); } } ) .fetchHits( 20 );
旧语法已被弃用,以支持新语法。
- 为复杂、非根布尔前缀提供更清晰的语法
-
现在可以使用
f.bool().with( b → … )
代替f.bool( b → … )
MySearchParameters searchParameters = getSearchParameters(); List<Book> hits = searchSession.search( Book.class ) .where( (f, b) -> { b.must( f.matchAll() ); if ( searchParameters.getGenreFilter() != null ) { b.must( f.match().field( "genre" ) .matching( searchParameters.getGenreFilter() ) ); } if ( !searchParameters.getAuthorFilters().isEmpty() ) { b.must( f.bool().with( b2 -> { for ( String authorFilter : searchParameters.getAuthorFilters() ) { b2.should( f.match().fields( "authors.firstName", "authors.lastName" ) .matching( authorFilter ) ); } } ) ); } } ) .fetchHits( 20 );
旧语法已被弃用,以支持新语法。
- 为
nested
前缀 提供更清晰的语法 -
现在可以使用
f.nested( … ).add( … )
代替f.nested().objectField( … ).nest( f.bool().must( … ) )
List<Book> hits = searchSession.search( Book.class ) .where( f -> f.nested( "authors" ) .add( f.match().field( "authors.firstName" ) .matching( "isaac" ) ) .add( f.match().field( "authors.lastName" ) .matching( "asimov" ) ) ) .fetchHits( 20 );
旧语法已被弃用,以支持新语法。
- 新增
matchNone
前缀 -
matchNone
前缀不匹配任何文档。List<Book> hits = searchSession.search( Book.class ) .where( f -> f.matchNone() ) .fetchHits( 20 );
- 为 复合投影 提供新的语法
-
现在可以使用流畅的语法定义复合投影
List<MyPair<String, Genre>> hits = searchSession.search( Book.class ) .select( f -> f.composite() .from( f.field( "title", String.class ), f.field( "genre", Genre.class ) ) .as( MyPair::new ) ) .where( f -> f.matchAll() ) .fetchHits( 20 );
大多数旧语法已被弃用,以支持新语法。
- 新增
object
投影 -
object
投影为给定对象字段中的每个对象提供一个投影值。List<List<MyAuthorName>> hits = searchSession.search( Book.class ) .select( f -> f.object( "authors" ) .from( f.field( "authors.firstName", String.class ), f.field( "authors.lastName", String.class ) ) .as( MyAuthorName::new ) .multi() ) .where( f -> f.matchAll() ) .fetchHits( 20 );
- 新增
constant
投影 -
constant
投影为每个文档返回相同的值,该值在定义投影时提供。Instant searchRequestTimestamp = Instant.now(); List<MyPair<Integer, Instant>> hits = searchSession.search( Book.class ) .select( f -> f.composite() .from( f.id( Integer.class ), f.constant( searchRequestTimestamp ) ) .as( MyPair::new ) ) .where( f -> f.matchAll() ) .fetchHits( 20 );
- 排序中的
.missing().lowest()
/.missing().highest()
选项 -
在排序字段可能没有一些文档的值时,已经可以使用
.missing().first()
/.missing().last()
来告诉 Hibernate Search 将此类文档放在第一/最后位置(分别),而不考虑排序顺序(升序/降序)。现在可以使用
.missing().lowest()
/.missing().highest()
作为替代,告诉 Hibernate Search 将此类文档视为具有最低/最高值(分别),考虑排序顺序(升序/降序)。-
.missing().lowest()
在使用升序排序时将无值的文档放在第一位,在降序排序时放在最后一位。 -
.missing().highest()
在使用升序排序时将无值的文档放在最后一位,在降序排序时放在第一位。
这主要用于当缺失值的定位是硬编码的,但排序顺序由用户指定时。
SortOrder orderFromUser = /* ... */; List<Book> hits = searchSession.search( Book.class ) .where( f -> f.matchAll() ) .sort( f -> f.field( "pageCount" ).missing().lowest().order( orderFromuser ) ) .fetchHits( 20 );
-
批量索引改进
- 批量索引多个租户
-
在多租户应用程序中,批量索引现在可以同时处理多个租户,前提是在创建批量索引器时未传递任何租户标识符,并且在 Hibernate Search 配置中提供了租户列表。有关更多信息,请参阅 参考文档中的此部分。
- 在批量索引期间设置线程局部变量
-
质量索引器现在有了“质量索引环境”的概念,例如可以在质量索引线程中设置自定义线程局部变量。有关更多信息,请参阅参考文档本节。
- 更好的异常处理
-
在质量索引过程中,Hibernate ORM抛出的异常现在会像其他异常一样传递给失败处理器,而不是中断整个质量索引。
- 参数的智能默认值
-
当启用
dropAndCreateSchemaOnStart
时,默认情况下,质量索引器禁用了purgeAllOnStart
。
outbox-polling
协调改进
- 出站事件和代理现在使用 UUID 作为其标识符
-
相关表的主键现在使用 UUID 而不是长整型,以避免依赖于一些数据库上降低事件处理的序列。迁移指南包括必要的数据库模式更改的迁移脚本。
- 可定制的数据库模式
-
简单的配置属性现在允许定制 Hibernate Search 的
outbox-polling
协调策略所涉及的数据库模式:表名、模式和数据库名,以及 UUID 列的类型以及 UUID 生成策略(随机与时间)。有关更多信息,请参阅参考文档本节。
Elasticsearch 模式导出
现在可以将 Hibernate Search 预期使用的 Elasticsearch 模式导出到文件系统上的 JSON 文件
SearchSchemaManager schemaManager = searchSession.schemaManager();
schemaManager.exportExpectedSchema( Path.of( "mydirectory" ) );
上面的代码将产生类似以下目录树的结构
# For the default backend: backend/indexes/<index-name>/<file>
mydirectory/backend/indexes/customer/create-index.json
mydirectory/backend/indexes/customer/create-index-query-params.json
mydirectory/backend/indexes/order/create-index.json
mydirectory/backend/indexes/order/create-index-query-params.json
# For additional named backends: backend/<backend-name>/indexes/<index-name>/<file>
mydirectory/backends/auth/indexes/user/create-index.json
mydirectory/backends/auth/indexes/user/create-index-query-params.json
mydirectory/backends/auth/indexes/usergroup/create-index.json
mydirectory/backends/auth/indexes/usergroup/create-index-query-params.json
有关更多信息,请参阅参考文档本节。
与 Hibernate Search 6.2.0.CR1 相比的新功能
有关 6.2.0.CR1 以来更改的列表,请参阅发行说明。
如何获取此版本
所有详细信息都可以在hibernate.org 上的专用页面上找到并保持最新。