Lucene 和 Criteria Api 加入 2 个不同的对象

Lucene and Criteria Api Join on 2 different objects

我在 2 个对象之间存在一对一关系:广告(包含 address_id 作为 FK)和地址。

地址我正在使用 fullTextSearch 查询 (lucene) 和添加条件查询。

我想在地址的某些字段上搜索关键字,从 lucene 搜索中获取链接到结果记录(地址)的广告,然后使用条件查询中的过滤器来减少结果的数量。

Lucene:

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);

    QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Address.class).get();

    Query luceneQuery = queryBuilder
            .keyword()
            .wildcard()
            .onFields(Address_.__REGION, Address_.__TOWN, Address_.__STREET)
            .matching(input.toLowerCase() + "*")
            .createQuery();

    return fullTextEntityManager.createFullTextQuery(luceneQuery, Address.class);

条件API:

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Ad> criteria = criteriaBuilder.createQuery(Ad.class);

    Root<Ad> AdRoot = criteria.from(Ad.class);

    criteria.select(AdRoot);

    if (AddressID != null) {
        whereConditions = (whereConditions == null) ? criteriaBuilder.equal(AdRoot.get(Ad_.address), AddressID)
                : criteriaBuilder.and(whereConditions, criteriaBuilder.equal(AdRoot.get(Ad_.address), AddressID));
    }

    if (PriceFrom != null && PriceTo != null)
        whereConditions = (whereConditions == null) ? criteriaBuilder.between(AdRoot.get(Ad_._Price), PriceFrom, PriceTo)
                : criteriaBuilder.and(whereConditions, criteriaBuilder.between(AdRoot.get(Ad_._Price), PriceFrom, PriceTo));

    if (SizeFrom != null && SizeTo != null)
        whereConditions = (whereConditions == null) ? criteriaBuilder.between(AdRoot.get(Ad_._Size), SizeFrom, SizeTo)
                : criteriaBuilder.and(whereConditions, criteriaBuilder.between(AdRoot.get(Ad_._Size), SizeFrom, SizeTo));

    if (whereConditions==null){
        return null;
    }

    criteria.where(whereConditions);

    return entityManager.createQuery(criteria).setMaxResults(MAX_NR_SESULTS_PER_PAGE).getResultList();

从技术上讲,你并没有真正提出问题,所以我要给你一些建议。

要知道当你想使用分页时,混合条件查询和全文查询真的很难做到,当你有大量结果时你通常会这样做。

实际上,如果您必须将谓词应用于两个不同的实体,到目前为止最简单的方法是使用 Hibernate Search 的 @IndexedEmbedded 注释:

  • @Indexed 添加到您的 Ad 实体,并将其从您的 Address 实体中删除(尽管 @Field 保留在您的 Address 实体中)
  • @IndexedEmbedded 添加到 Ad 实体的 address 字段
    • 如果您的 Address 实体可能会随时间发生变化,您还应该在关联中添加反面(Address 实体中的 Ad ad 字段)并用 @ContainedIn 以便 Ad 个实体的索引在地址更新时得到更新。
  • Ad 实体的 pricesize 字段添加 @Field 注释
  • 完全使用 Hibernate Search 构建您的查询,针对 Ad 实体。
    • 复制现有全文查询中的代码,但您将为 Ad 实体而不是 Address 实体检索查询生成器,并且您将针对 address.regionaddress.townaddress.street 个字段。
    • 使用 range queries
    • pricesize 字段创建额外的 lucene 查询
    • 使用 boolean junction
    • 将所有 lucene 查询合并为一个

您不会拥有 SQL WHERE 子句和连接的全部功能,但在很多情况下就足够了,在您的情况下似乎也足够了。

为什么在进行分页时很难合并来自不同查询的结果

如果您想详细了解为什么这很难...警告:它有点复杂,所以我不确定我能否解释清楚。

分页通常由查询引擎执行,在您的应用程序之外,甚至在 Hibernate 之外。但是在您的应用程序中发生这些查询后,您将合并两个查询的结果,因此您在分页后应用了另一级别的过滤。这意味着查询引擎跳过的第一个结果可能根本就不是匹配结果。

因此,例如,如果您跳过了 100 个结果,那么这些跳过的结果中可能只有 50 个是您查询中的实际结果。所以你实际上检索的结果是从第 51 个结果开始的,而你查询的结果是从第 101 个开始的...

有很多方法可以解决这个问题,一种可能是从 "paging by index" 策略切换到 "paging by range on a unique sorting key" 策略。

本质上不是要求 "first 10 results",然后是 "results 11 to 20",等等,例如,您将按创建日期排序,要求 "first 10 results",然后是“10创建日期早于 foo 的第一个结果”(foo 是您在上一页中收到的第 10 个结果的创建日期)。

此策略有一些缺点,最显着的是:

  • 您的用户将无法跳转到任意页面(第一个页面除外),只能转到下一页,或者需要更多工作的上一页。
  • 您必须根据唯一键进行排序。如果你没有这样的key,或者你想按全文查询分值排序,那就不行了。

但是,这种策略很有效,而且我知道这是唯一可以产生正确结果的策略。