用于减少 HibernateSearch SearchPredicate 流的累加器函数

Accumulator function to reduce stream of HibernateSearch SearchPredicates

以下摘自 https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#query-predicate 的示例(示例 183)解释了如何将多个过滤器应用于 HibernateSearch 查询:

List<Book> hits = searchSession.search( Book.class )
        .where( f -> f.bool() 
                .should( f.bool() 
                        .filter( f.match().field( "genre" )
                                .matching( Genre.SCIENCE_FICTION ) ) 
                        .must( f.match().fields( "description" )
                                .matching( "crime" ) ) 
                )
                .should( f.bool() 
                        .filter( f.match().field( "genre" )
                                .matching( Genre.CRIME_FICTION ) ) 
                        .must( f.match().fields( "description" )
                                .matching( "robot" ) ) 
                )
        )
        .fetchHits( 20 ); 

但是,如果我对文档的理解正确,我总是必须单独应用过滤器(此处:类型 =“科幻小说”或类型 = 犯罪小说)。

相反,我需要将任意过滤器列表应用于 HibernateSearch 搜索查询,例如作为 Map<String, String> 提供,其中每个条目键代表一个过滤器字段名称,条目值是过滤器字段值。

我尝试首先通过以下方式创建搜索谓词列表:

List<SearchPredicate> searchPredicates = new ArrayList<>();
for (Map.Entry<String, String> filterEntry : filterMap.entrySet()) {
    searchPredicates.add(scope.predicate().match().field(filterEntry.getKey())
            .matching(filterEntry.getValue()).toPredicate());
}

然后通过创建流在查询的 filter 子句中应用此谓词列表,然后按照 https://www.baeldung.com/java-predicate-chain.

中的说明减少流

但是我无法为流的“reduce”函数识别合适的 'identity' SearchPredicate 和 'accumulator' 函数参数(我使用 must() 而不是 should()因为我希望 所有 我的过滤器应用而不是只应用一个,所以逻辑合取而不是上面示例中的析取):

List<MyClass> hits = searchSession.search(MyClass.class)
  .where( f -> f.bool()
    .must( f.bool()
      .filter(searchPredicates.stream()
        .reduce(SearchPredicateFactory::matchAll, (partialResult, nextFilter) -> f.bool().must(nextFilter))))
        .fetchHits(10));

不过,“reduce”函数的这两个参数都不好:

是否有适用于 HibernateSearch SearchPredicates 的累加器函数(如果有:对应的 'identity' 是什么?)还是我需要自己写一个?

(抱歉,如果这是一个天真的问题,我阅读了 HibernateSearch 和 Java 谓词的文档,但我想我仍然是这些主题的相对初学者,并且找不到与我相同的问题试图解决 - 将搜索谓词列表应用于 HibernateSearch 查询 - 在其他地方介绍)

编辑:我在阅读 https://www.baeldung.com/java-stream-reduce 后更新了我的尝试,这至少让我对“reduce”及其参数应该如何工作有了更多的了解!

我会说 reduce 不是这项工作的正确工具。

Hibernate Search DSL 提供了 builder-like 个可以聚合多个谓词的实例(如下面的 b):只需使用它们即可。你甚至不需要这里的流。

使用 f.bool(b -> { ... }) 还不够吗,其中 b 就像一个累加器,像这样?

Map<String, String> searchParameters = getSearchParameters(); 
List<MyClass> hits = searchSession.search( MyClass.class )
        .where( f -> f.bool( b -> { 
            b.must( f.matchAll() ); // By default, return all documents
            // ... unless field/value pairs are provided,
            // in which case return documents that match every field/value pair.
            for ( Map.Entry<String, String> filterEntry : filterMap.entrySet() ) {
                b.must( f.match().field( filterEntry.getKey() )
                        .matching( filterEntry.getValue() ) );
            }
        } ) )
        .fetchHits( 10 ); 

这与 this section of the documentation 中的建议差不多。

如果你真的出于某种原因需要使用流,我想这样的事情可能会起作用:

List<SearchPredicate> searchPredicates = getSearchPredicates();
List<MyClass> hits = searchSession.search( MyClass.class )
        .where( f -> f.bool( b -> { 
            b.must( f.matchAll() ); // By default, return all documents
            // ... unless predicates are provided,
            // in which case return documents that match every predicate.
            searchPredicates.stream().forEach( b::must );
        } ) )
        .fetchHits( 10 );

如果你是一个函数式编程的极端主义者,使用 collect 而不是 reduce 可以工作,但坦率地说,这是非常丑陋的代码,比上面的两个建议更糟糕:

List<SearchPredicate> searchPredicates = getSearchPredicates();
List<MyClass> hits = searchSession.search( MyClass.class )
        .where( f -> f.bool()
                .must( f.matchAll() ) // By default, return all documents
                // PLEASE DO YOUR COWORKERS A FAVOR: DON'T DO THIS
                .must(searchPredicates.stream().collect(
                        () -> f.bool(),
                        BooleanPredicateClausesStep::must,
                        (left, right) -> f.bool().must( left ).must( right ) ) )
        )
        .fetchHits( 10 );