首页 > 解决方案 > 将单个 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);
    }
}

标签: javaspringpostgresqlhibernatejpa

解决方案


创建对外部查询有副作用的可重用谓词是一种不好的做法(我的意思是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);
     }
}

推荐阅读