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
实体的 price
和 size
字段添加 @Field
注释
- 完全使用 Hibernate Search 构建您的查询,针对
Ad
实体。
- 复制现有全文查询中的代码,但您将为
Ad
实体而不是 Address
实体检索查询生成器,并且您将针对 address.region
、address.town
、address.street
个字段。
- 使用 range queries
为 price
和 size
字段创建额外的 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,或者你想按全文查询分值排序,那就不行了。
但是,这种策略很有效,而且我知道这是唯一可以产生正确结果的策略。
我在 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
实体的price
和size
字段添加@Field
注释 - 完全使用 Hibernate Search 构建您的查询,针对
Ad
实体。- 复制现有全文查询中的代码,但您将为
Ad
实体而不是Address
实体检索查询生成器,并且您将针对address.region
、address.town
、address.street
个字段。 - 使用 range queries 为
- 使用 boolean junction 将所有 lucene 查询合并为一个
price
和size
字段创建额外的 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,或者你想按全文查询分值排序,那就不行了。
但是,这种策略很有效,而且我知道这是唯一可以产生正确结果的策略。