【问题标题】:Order by subquery with Spring JPA Specification使用 Spring JPA 规范按子查询排序
【发布时间】:2018-07-22 08:56:41
【问题描述】:

我有一个排序问题,本机 SQL 和可能 JPQL 很容易解决这个问题,但 Spring JPA 规范却没有。

这是修剪后的域模型:

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

用户在几个元素上发布他们的Change 提案,其他用户使用VoteTypeGREATGOODNEUTRALBAD 对这些更改进行投票。

我试图完成的是通过GOODGREAT 的票数来订购Entitys(而不是Changes)。

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

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

  • 如果我们有多个 Specification&lt;DesignElement&gt;s 要组合,我们不能只在根查询中添加 count(Vote_.id)
  • 在 JPA 中无法通过子查询进行排序:https://hibernate.atlassian.net/browse/HHH-256
  • 不能在子查询中使用multiselect

这是在其他Specification&lt;DesignElement&gt;s 中定义的其他几个工作Orders:

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;
        }
    };
}

这将允许这样的用法:

List<DesignElement> elements = designElementDao.findAll(Specifications.where(orderByReverseSeverityRank()).and(orderByCreationTime());

【问题讨论】:

标签: java spring jpa specifications


【解决方案1】:

我遇到了同样的问题。对于按 count(child-collection) 排序,我找到了一种解决方法 - 它并不理想(污染实体对象,不是动态等),但在某些情况下,它可能就足够了。

  1. 定义只读字段“upvotesCount”,保存用于排序的信息

    @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. 实现字段映射到的数据库视图

    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>
    

另一种方法是使用 DTO 对象 - 您可以构建符合您需求的查询,并且您也可以动态地执行此操作。

【讨论】:

  • 是的,views 几乎总是适用于这样的情况,但我们正在努力避免它们。
  • 实体还需要添加@SecondaryTable(name = "change_votes_view")
【解决方案2】:

我想出了一个有点老套的解决方案。

还有一个问题,在 H2 单元测试数据库中并不明显,但在实际的 Postgres 安装中出现:如果直接在 Order by 子句中使用,则必须按列分组:must appear in the GROUP BY clause or be used in an aggregate function

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

  • 构造连接,以免出现重复行并且只需对这些单行执行标识聚合操作(如 sum

或者

  • 用感兴趣的重复行构造连接count子ID的数量。 (别忘了group by root id)。

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

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;
        }
    };
}

我概括了这种方法并修正了之前的规范:

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;
        }
    };
}

由于还有大约 15 个其他 Specifications 像这样,我想这么懒惰会导致性能损失,但我没有分析它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-02
    • 1970-01-01
    • 2017-08-19
    • 2017-03-14
    • 1970-01-01
    • 2019-04-29
    相关资源
    最近更新 更多