【问题标题】:How to make a Hibernate SearchSession return results with unique attributes?如何使 Hibernate SearchSession 返回具有唯一属性的结果?
【发布时间】:2022-08-04 15:39:20
【问题描述】:

我正在使用 Java 中的 Hibernate SearchSession 类对数据库执行搜索,我目前必须搜索表的代码如下所示:

SearchSession searchSession = Search.session(entityManagerFactory.unwrap(SessionFactory.class).withOptions()
                .tenantIdentifier(\"locations\").openSession());

SearchResult<Location> result = searchSession.search(Location.class)
                .where(  f -> f.bool()
                        .must( f.match()
                                .field(\"locationName\")
                                .matching((phrase)).fuzzy())
                ).fetch(page * limit, limit);

此搜索有效并正确地从数据库返回结果,但 locationName 列没有唯一性约束,并且数据库在 locationName 中保存多个具有相同值的记录。结果,当我们尝试在应用程序的 UI 上显示它们时,看起来好像有重复的值,即使它们在数据库中是唯一的。

如果之前没有返回具有相同值的另一个结果(例如 locationName),有没有办法让 SearchSession 只返回一个结果?在这种情况下,对数据库表应用唯一性约束不是一种选择,我们希望有一种方法可以在会话中过滤掉重复值,而不是从搜索中获取结果并单独删除重复值。

    标签: java sql hibernate search hibernate-search


    【解决方案1】:

    如果之前没有返回具有相同值的另一个结果(例如 locationName),有没有办法让 SearchSession 只返回一个结果?

    不是真的,至少不是at the moment

    如果您使用的是 Elasticsearch 后端并且可以很好地使用本机,您可以insert native JSON into the Elasticsearch request,尤其是collapsing

    我认为这样的事情可能会奏效:

    SearchResult<Location> result = searchSession.search( Location.class )
            .extension( ElasticsearchExtension.get() ) 
            .where(  f -> f.bool()
                            .must( f.match()
                                    .field("locationName")
                                    .matching((phrase)).fuzzy())
                    )
            .requestTransformer( context -> { 
                JsonObject collapse = new JsonObject();
                collapse.addProperty("field", "locationName_keyword")
    
                JsonObject body = context.body(); 
                body.add( "collapse", collapse );
            } )
            // You probably need a sort, as well:
            .sort(f -> f.field("id"))
            .fetch( page * limit, limit ); 
    

    您需要将locationName_keyword 字段添加到您的Location 实体:

    @Indexed
    @Entity
    public class Location {
    
        // ...
    
        @Id
        @GenericField(sortable = Sortable.YES) // Add this
        private Long id;
    
        // ...
    
        @FullTextField
        @KeywordField(name = "locationName_keyword", sortable = Sortable.YES) // Add this
        private String locationName;
    
        // ...
    
    }
    

    (您可能还需要将 custom normalizer 分配给 locationName_keyword 字段,如果重复位置的 locationName 略有不同(不同的情况,...))

    但请注意,搜索结果中的“总命中数”将指示命中数崩溃。因此,如果只有一个匹配的 locationName,但有 5 个具有该名称的 Location 实例,则总命中数将为 5,但用户只会看到一个命中。他们肯定会感到困惑。


    话虽如此,可能值得再看看你的情况,以确定这里是否真的需要折叠:

    结果,当我们尝试在应用程序的 UI 上显示它们时,看起来好像有重复的值,即使它们在数据库中是唯一的。

    如果您有多个具有相同locationName 的文档,那么您在数据库中肯定有多个具有相同locationName 的行吗?索引时不会自发出现重复。

    我想说要做的第一件事是退后一步,考虑您是否真的要查询Location 实体,或者另一个相关实体是否更有意义。当两个位置具有相同的名称时,它们是否与另一个公共实体实例有关系(例如,Shop 类型,...)?

    => 如果是这样,您可能应该查询该实体类型(.search(Shop.class)),并利用@IndexedEmbedded 允许基于Location 属性的过滤(即添加@IndexedEmbedded 到@ 中的location 关联987654344@ 实体类型,然后在添加应与位置名称匹配的谓词时使用字段location.locationName)。

    如果没有这样相关的通用实体实例,那么我会尝试找出为什么位置会完全重复,更重要的是为什么重复在数据库中有意义,但对用户没有意义:

    • 用户是否不感兴趣全部地点?那么也许您应该向您的查询添加另一个过滤器(通过“类型”,...),这将有助于删除重复项。如有必要,您甚至可以运行多个搜索查询:第一个具有非常严格的过滤器,如果没有命中,则回退到另一个过滤器不太严格的搜索查询。
    • 您是否在使用某种版本控制或软删除?那么也许你应该避免索引软删除的实体或旧版本;您可以使用conditional indexing 来做到这一点,或者,如果这不起作用,请在搜索查询中使用过滤器。

    如果您的数据确实是重复的(遗留数据库,...),除了“只选择第一个”之外,没有任何方法可以选择重复,您可以考虑是否需要聚合而不是全面搜索。您只是在寻找最热门的地点名称,还是按名称查找地点数量?那么聚合是正确的工具。

    【讨论】:

      猜你喜欢
      • 2017-12-12
      • 2019-07-09
      • 1970-01-01
      • 1970-01-01
      • 2011-06-07
      • 2016-12-09
      • 2011-11-07
      • 2017-12-29
      相关资源
      最近更新 更多