java - 将单个 Join 实例传递给多个 Specification 实例?
问题描述
相关:提示 HINT_PASS_DISTINCT_THROUGH 将 PageRequest 的每页返回的实体数量减少到配置的页面大小以下(PostgreSQL)
我正在建立一个基于 JPA 规范的存储库实现,它利用 jpa 规范(基于 RSQL 过滤器字符串构建)来过滤结果,定义结果排序并通过“不同”删除任何重复项,否则这些重复项会因连接表而返回。JPA Specification builder 方法连接多个表并设置“distinct”标志:
public final class MySpec implements Specification<Tag>
{
@Override
public Predicate toPredicate(
final Root<Tag> root,
final CriteriaQuery<?> query,
final CriteriaBuilder builder)
{
final Join<Tag, Label> labelsJoin = root.join("labels", JoinType.INNER);
final Join<Label, LabelIdentity> labelIdentityJoin = labelsJoin.join("labelIdentity", JoinType.INNER);
final Predicate labelKeyPredicate = builder.equal(labelIdentityJoin.get("key"), property);
query.distinct(true);
return builder.and(
labelKeyPredicate,
builder.like(labelsJoin.get("value"), argument.replace('*', '%')));
}
}
为了允许按连接表列排序,我已将“HINT_PASS_DISTINCT_THROUGH”提示应用于相关存储库方法(否则,按连接表列排序会返回错误,类似于“排序列必须包含在 SELECT DISTINCT 查询中”) .
在这些更改之后,过滤和排序似乎可以按要求工作。但是,提示似乎会导致在已经构建结果页面之后应用“不同”过滤,从而将页面中返回的实体数量从配置的“大小”PageRequest 参数减少到过滤重复项后剩下的任何内容出去。
我的问题是:
Join
是否可以通过以某种方式重用不同实例中的实例来消除使用 distinct(从而解决分页问题)的需要Specification
?例如构造Join
实例,并将相同的Join
实例传递给每个新的规范实例(例如通过构造函数)?
例如,我尝试创建类似以下内容,然后将此JoinCache
实例传递给每个Specification
实例,但是,我收到有关不正确别名的错误,因此不确定是否支持这样的内容?
public class JoinCache
{
private final CriteriaBuilder criteriaBuilder;
private final CriteriaQuery<Tag> criteriaQuery;
private final Root<Tag> tagRoot;
private final Join<Tag, Label> labelJoin;
private final Join<Label, LabelIdentity> labelIdentityJoin;
public JoinCache(final CriteriaBuilder criteriaBuilder)
{
this.criteriaBuilder = criteriaBuilder;
this.criteriaQuery = this.criteriaBuilder.createQuery(Tag.class);
this.tagRoot = criteriaQuery.from(Tag.class);
this.labelJoin = tagRoot.join("labels", JoinType.INNER);
this.labelIdentityJoin = labelJoin.join("labelIdentity", JoinType.INNER);
}
public Join<Tag, Label> getLabelJoin()
{
return labelJoin;
}
public Join<Label, LabelIdentity> getLabelIdentityJoin()
{
return labelIdentityJoin;
}
public CriteriaBuilder getCriteriaBuilder()
{
return criteriaBuilder;
}
public CriteriaQuery<Tag> getCriteriaQuery()
{
return criteriaQuery;
}
public Root<Tag> getTagRoot()
{
return tagRoot;
}
}
更新
使用子查询而不是连接的替代方法(因此根本不需要使用 distinct),但是,我相信 JPA 规范不支持子查询中的 order by/sorting:
https://hibernate.atlassian.net/browse/HHH-256
public class MySpec implements Specification<Tag>
{
@Override
public Predicate toPredicate(
final Root<Tag> root,
final CriteriaQuery<?> query,
final CriteriaBuilder builder)
{
final String argument = arguments.get(0);
final Subquery<Label> subQuery = query.subquery(Label.class);
final Root<Label> subRoot = subQuery.from(Label.class);
final Predicate tagPredicate = builder.equal(subRoot.get("tag"), root);
final Predicate labelKeyPredicate = builder.equal(subRoot.get("labelIdentity").get("key"), "owner");
subQuery.select(subRoot).where(tagPredicate, labelKeyPredicate, builder.like(subRoot.get("value"), argument.replace('*', '%'));
return builder.exists(subQuery);
}
}
解决方案
创建对外部查询有副作用的可重用谓词是一种不好的做法(我的意思是query.distinct(true)
)。subquery
您可以使用和exists
谓词获得相同的结果。
假设Tag
实体有@Id Long id
字段
public final class MySpec implements Specification<Tag> {
@Override
public Predicate toPredicate(
final Root<Tag> root,
final CriteriaQuery<?> query,
final CriteriaBuilder builder) {
Subquery<Long> subquery = query.subquery(Long.class); // if entity id has Long type
Root<Tag> subRoot = subquery.from(Tag.class);
final Join<Tag, Label> label = subRoot.join("labels", JoinType.INNER);
final Join<Label, LabelIdentity> labelIdentity = label.join("labelIdentity", JoinType.INNER);
final Predicate externalQueryJoinPredicate =
builder.equal(subRoot.get("id"), root.get("id"));
final Predicate labelKeyPredicate =
builder.equal(labelIdentity.get("key"), property);
final Predicate labelValuePredicate =
builder.like(label.get("value"), argument.replace('*', '%'));
subquery.select(subRoot.get("id")).where(
externalQueryJoinPredicate, labelKeyPredicate, labelValuePredicate);
return builder.exists(subquery);
}
}
推荐阅读
- jquery - Angular 8 中的每周日历导航
- html - 将 HTML 内容插入 Vue 工具提示
- javascript - 通过状态对象映射并测试没有多个返回值的多个嵌套数组?
- postgresql - 代码返回不明确错误需要帮助
- kotlin - 在 Kotlin 的一行中使用(自定义)项目初始化新的 arraylist
- python - 我可以使用任何堆栈驱动程序监控 api 或 gcloud 命令获得 gcp 计算建议以调整实例大小吗
- python-3.x - 无法在 Raspberry Pi 上安装 snips-nlu:找不到 Rust 编译器
- c# - c#中的a is string和a !is string有什么区别
- android - 将 android studio 活动保存在单独的文件中
- mysql - PHP mySQL 查询,加入另一个表,但需要获取所有行