Hibernate Search 是一个库,它通过自动索引实体,将 Hibernate ORM 与 Apache Lucene 或 Elasticsearch 集成,从而实现高级搜索功能:全文搜索、地理空间搜索、聚合等。更多详情请参阅 hibernate.org 上的 Hibernate Search。

我们很高兴地宣布发布 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 上的专用页面上找到并保持最新。

入门,迁移

对于新应用程序,请参阅以下指南以了解 Hibernate ORM 集成

对于现有应用程序,假设您也升级了依赖项,Hibernate Search 6.2 是 6.1 的直接替代品。有关已弃用配置和 API 的信息,请参阅迁移指南

反馈、问题、想法?

要联系,请使用以下渠道


返回顶部