使用Spring JPA规范按子查询排序

时间:2018-02-12 08:31:35

标签: java spring jpa specifications

我有一个排序问题,原本SQL和JPQL很容易解决,但不是Spring JPA规范。

以下是修剪过的域模型:

@Entity
class DesignElement {
   List<Change> changes;
}
@Entity
class Change {
   List<DesignElement> designElements;
}
@Entity
class Vote {
   Change change;
   VoteType type;
}

用户在多个元素上发布他们的Change提案,其他用户使用VoteType GREATGOODNEUTRAL或{{1}对这些更改进行投票}}

我尝试完成的是按BADEntity投票的顺序订购Change s(而非GOOD s。)

使用SQL或JPQL可能更容易,但我们尝试使用GREAT API,因为我们需要其他几个谓词和命令。

经过两天的痛苦之后,我想出了一些关于我不能用Specification api做什么的假设。其中一些可能是错的,如果是这样的话,我会用答案编辑问题。

  • 如果我们要合并多个Specification,我们就不能在根查询中添加count(Vote_.id)
  • 无法通过JPA中的子查询进行排序:https://hibernate.atlassian.net/browse/HHH-256
  • 无法在子查询中执行Specification<DesignElement>

以下是其他multiselect s中定义的其他一些工作Order

Specification<DesignElement>

允许这样的用法:

private static void appendOrder(CriteriaQuery<?> query, Order order) {
    List<Order> orders = new ArrayList<>(query.getOrderList());
    orders.add(0, order);
    query.orderBy(orders);
}

public static Specification<DesignElement> orderByOpen() {
    return new Specification<DesignElement>() {
        @Override
        public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            appendOrder(query, cb.desc(root.get("status").get("open")));
            return null;
        }
    };
}

public static Specification<DesignElement> orderByReverseSeverityRank() {
    return new Specification<DesignElement>() {
        @Override
        public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            appendOrder(query, cb.desc(root.get("severity").get("rank")));
            return null;
        }
    };
}

public static Specification<DesignElement> orderByCreationTime() {
    return new Specification<DesignElement>() {
        @Override
        public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            appendOrder(query, cb.asc(root.get("creationTime"));
            return null;
        }
    };
}

2 个答案:

答案 0 :(得分:1)

我遇到了同样的问题。为了按计数(儿童收集)排序,我找到了一个解决方法 - 它不理想(污染实体对象,而不是动态等),但在某些情况下,它可能就足够了。

  1. 定义只读字段&#39; upvotesCount&#39;持有要用于排序的信息

    @Entity
    class Change {
    
        List<DesignElement> designElements;
    
        // let's define read-only field, mapped to db view 
        @Column(table = "change_votes_view", name = "upvotes_count", updatable = false, insertable = false)
        Integer upvotesCount;
    
    }
    
  2. 实现db视图,其中字段映射到

    CREATE VIEW change_votes_view AS SELECT c.id, count(v.*) as upvotes_count
    FROM change c
    LEFT JOIN votes v 
      ON <ids-condition> AND <votes-are-up=condition>
    
  3. 另一种方法是使用DTO对象 - 您构建符合您需求的查询,您也可以动态地执行此操作。

答案 1 :(得分:0)

我提出了一些有点愚蠢的解决方案。

还有一个问题在H2单元测试数据库中并不明显,但在实际的Postgres安装中显示:如果直接在Order by子句中使用,则必须按列分组:{{ 3}}

解决方案涉及两种不同的方法:

  • 构建联接,以便不会有重复的行在这些单行上执行身份聚合操作(如sum

  • 使用感兴趣的重复行构建联接 count子ID的数量。 (不要忘记group by root id)。

计算子ID的数量是解决此问题的方法。但即便如此(在感兴趣的行上创建一个先决条件)也很难(我很确定这是可能的)所以我采取了一种有点hacky的方法:交叉加入所有孩子,并在{{1}上做sum }:

case

我概括了这种方法并修复了以前的规范:

public static Specification<DesignElement> orderByGoodVotes() {
    return new Specification<DesignElement>() {
        @Override
        public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

            ListAttribute<? super DesignElement, Alert> designElementChanges = root.getModel().getList("changes", Change.class);
            ListJoin<Change, Vote> changeVotes = root.join(designElementChanges).joinList("votes", JoinType.LEFT);
            Path<VoteType> voteType = changeVotes.get("type");

            // This could have been avoided with a precondition on Change -> Vote join
            Expression<Integer> goodVotes1_others0 = cb.<Integer>selectCase().when(cb.in(voteType).value(GOOD).value(GREAT, 1).otherwise(0);

            Order order = cb.desc(cb.sum(goodVotes1_others0));
            appendOrder(query, order);

            // This is required
            query.groupBy(root.get("id"));
            return null;
        }
    };
}

由于还有大约15个这样的public static Specification<DesignElement> orderByOpen() { return new Specification<DesignElement>() { @Override public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Expression<Integer> opens1_others0 = cb.<Integer>selectCase().when(cb.equal(root.get("status").get("open"), Boolean.TRUE), 1).otherwise(0); appendOrder(query, cb.desc(cb.sum(opens1_others0))); query.groupBy(root.get("id")); return null; } }; } public static Specification<DesignElement> orderByReverseSeverityRank() { return new Specification<DesignElement>() { @Override public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) { appendOrder(query, cb.asc(cb.sum(root.get("severity").get("rank")))); query.groupBy(root.get("id")); return null; } }; } public static Specification<DesignElement> orderByCreationTime() { return new Specification<DesignElement>() { @Override public Predicate toPredicate(Root<DesignElement> root, CriteriaQuery<?> query, CriteriaBuilder cb) { // This isn't changed, we are already selecting all the root attributes appendOrder(query, cb.desc(root.get("creationTime"))); return null; } }; } 个,所以我觉得懒得会有性能损失,但我没有分析它。