对来自Lucene索引的结果进行分类

时间:2017-10-02 07:19:46

标签: java hibernate lucene hibernate-search

我有一个Lucene索引,通过Hibernate在Hibernate Search注释的帮助下生成,有3个字段(只是为了简化一点)来描述一篇文章:

id, title, brand

内容示例:

id, title, brand 1, "Long skirt", "Sweet and Gabbana" 2, "Sweet neck vest", "Armani" 3, "Sweet feeling shirt", "Armani"

请注意“Sweet and Gabbana”,“Sweet neck vest”和“Sweet feeling shirt”分享“甜蜜”这个词。

我想制作一个Lucene查询,这样,如果我搜索关键字“sweet”,我会得到2个不同的类别,一个用于标题,另一个用于品牌。例如:

  • 标题 - > “甜美的背心”,“甜美的衬衫”
  • 品牌 - > “Sweet and Gabbana”

换句话说,我想向用户表明系统在这两个不同的类别中找到了结果。

当我运行查询(标题和品牌之间的一种OR)时,我得到所有三个条目(在Lucene中,包含ID为1,2和3的文档),它们只包含一个属性或另一个属性,但是我该如何对它们进行分类?

@PersistenceContext
private EntityManager em;

...

@Override
public List<ArticleByIndexModel> retrieveArticlesSearchQueryResult(final String searchString,
        final String languageIso639) {

    final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
    final org.apache.lucene.search.Query luceneQuery = buildUpArticlesSearchLuceneQuery(searchString,
            languageIso639, fullTextEntityManager);

    final String titleFieldName = ArticleTranslationFieldPrefixes.TITLE + languageIso639;
    final String brandNameFieldName = BrandTaxonomy.BrandTaxonomyNameFieldName.NAME;

    final FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery);
    fullTextQuery.setMaxResults(50);
    fullTextQuery.setProjection(Article_.articleID.getName(), titleFieldName, brandNameFieldName,
            Article_.brandSku.getName(), FullTextQuery.DOCUMENT_ID, FullTextQuery.EXPLANATION, FullTextQuery.THIS);

    @SuppressWarnings("unchecked")
    final List<Object[]> list = (List<Object[]>) fullTextQuery.getResultList();

    final List<ArticleByIndexModel> resultList = list.stream()
            .map(x -> new ArticleByIndexModel((Integer) x[0], (String) x[1])).collect(Collectors.toList());
    return resultList;
}

private org.apache.lucene.search.Query buildUpArticlesSearchLuceneQuery(final String searchString,
        final String languageIso639, final FullTextEntityManager fullTextEntityManager) {

    final String brandSkuName = Article_.brandSku.getName();

    final String analyzerPartName = ArticleTranslationDiscriminator.getAnalyzerPartNameByLanguage(languageIso639);
    final String titleFieldName = ArticleTranslationFieldPrefixes.TITLE + languageIso639;
    final String titleEdgeNGramFieldName = ArticleTranslationFieldPrefixes.TITLE_EDGE_N_GRAM + languageIso639;
    final String titleNGramFieldName = ArticleTranslationFieldPrefixes.TITLE_N_GRAM + languageIso639;

    final String brandNameEdgeNGramFieldName = BrandTaxonomy.BrandTaxonomyNameFieldName.NAME_EDGE_N_GRAM;
    final String brandNameNGramFieldName = BrandTaxonomy.BrandTaxonomyNameFieldName.NAME_N_GRAM;

    final SearchFactory searchFactory = fullTextEntityManager.getSearchFactory();
    final QueryBuilder qb = searchFactory.buildQueryBuilder().forEntity(Article.class)
            .overridesForField(titleFieldName, ArticleTranslationFieldPrefixes.TITLE + analyzerPartName)
            .overridesForField(titleEdgeNGramFieldName,
                    ArticleTranslationFieldPrefixes.TITLE_EDGE_N_GRAM + analyzerPartName)
            .overridesForField(titleNGramFieldName, ArticleTranslationFieldPrefixes.TITLE_N_GRAM + analyzerPartName)
            .get();

    final org.apache.lucene.search.Query luceneQuery =
            /**/
            qb.bool()
                    /**/
                    .should(qb.phrase().withSlop(2).onField(titleNGramFieldName).andField(titleEdgeNGramFieldName)
                            .boostedTo(5).sentence(searchString.toLowerCase()).createQuery())
                    /**/
                    .should(qb.phrase().withSlop(2).onField(brandNameNGramFieldName)
                            .andField(brandNameEdgeNGramFieldName).boostedTo(5).sentence(searchString.toLowerCase())
                            .createQuery())
                    /**/
                    .should(qb.keyword().onField(brandSkuName).matching(searchString.toLowerCase()).createQuery())
                    /**/
                    .createQuery();

    return luceneQuery;
}

我看不到制作2个不同查询然后合并结果的解决方案。

我读到了Facets,但我不认为它们在这种情况下是合适的。

你有什么想法吗?

谢谢!!!

1 个答案:

答案 0 :(得分:0)

我假设您需要将结果作为单个列表显示给用户,并为每个项目添加一些描述(由于标题/匹配因为品牌/两者而匹配)。

我认为没有一项功能可以让你在Hibernate Search中完全 。我想有一些方法可以使用低级Lucene API(收集器),但这会涉及一些黑魔法,我认为我们不能在Hibernate Search中插入它。

让我们走更容易的路:自己动手。

就个人而言,我只会运行多个查询:

  • 第一次和你的例子一样
  • 第二次投射ID(.setProjection(ProjectionContants.ID))并仅使用两个子句:一个强制匹配具有与第一个查询的结果之一相同的ID(基本上must(should(id=<firstID>), should(id=<secondID>), ... ),以及一个强制搜索字符串匹配标题(基本上是must(title=<searchString>)
  • 第三次类似于第二次,但用品牌代替标题

然后我会使用第二个和第三个查询的结果来确定给定的结果是否因为标题或品牌而匹配。

当然,只有当搜索字符串仅与标题或品牌(或两者)完全匹配时才会起作用,而不是搜索字符串的某些部分与标题匹配,而其他部分与品牌匹配。但如果这就是你想要的,那你当前的查询总是错的......