【问题标题】:How to count the number of rows of a JPA 2 CriteriaQuery in a generic JPA DAO?如何计算通用 JPA DAO 中 JPA 2 CriteriaQuery 的行数?
【发布时间】:2012-02-21 12:44:11
【问题描述】:

我是 JPA 的新手,想实现一个通用的 JPA DAO,需要找到查询结果集的行数来实现分页。在网上搜索后,我找不到一种实用的方法来做到这一点。这是很多文章中建议的代码:

public <T> Long findCountByCriteria(CriteriaQuery<?> criteria) {
    CriteriaBuilder builder = em.getCriteriaBuilder();

    CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class);
    Root<?> entityRoot = countCriteria.from(criteria.getResultType());
    countCriteria.select(builder.count(entityRoot));
    countCriteria.where(criteria.getRestriction());

    return em.createQuery(countCriteria).getSingleResult();
}

但是,当使用join 时,该代码不起作用。有没有办法使用 JPA Criteria API 计算查询结果集的行数?

更新: 这是创建 CriteriaQuery 的代码:

    CriteriaQuery<T> queryDefinition = criteriaBuilder.createQuery(this.entityClass);
    Root<T> root = queryDefinition.from(this.entityClass);

在执行查询之前,可能会向根添加一些连接:

public Predicate addPredicate(Root<T> root) {
                Predicate predicate = getEntityManager().getCriteriaBuilder().ge(root.join(Entity_.someList).get("id"), 13);
                return predicate;
}

生成的异常是这样的:

org.hibernate.hql.ast.QuerySyntaxException:无效路径: 'generatedAlias1.id' [从 entity.Entity 中选择计数(生成的Alias0) 作为 generateAlias0 其中 ( generatedAlias0.id>=13L ) 和 ( (生成的Alias1.id

哪个 generateAlias1 应该在 Entity 上,而 generatedAlias0 应该在我加入的关联上。 请注意,我正确地实现了 Join,因为当我执行没有计数查询的查询时,它执行时没有错误,并且 Join 工作正常,但是当我尝试执行计数查询时它会引发异常。

【问题讨论】:

  • 您能告诉我们您是如何定义 CriteriaQuery> 标准
  • @perissf tnx 4 请注意。我检查了那些,但没有帮助我。在我的 JPA 条件查询中使用联接时,我不知道如何获取行数。
  • 您仍然没有显示所有代码。您如何为 WHERE 语句生成谓词?连接应该为 WHERE 生成谓词。你遇到了什么错误?
  • @perissf 我在主帖中添加了更多详细信息。

标签: java jpa orm count pagination


【解决方案1】:

我已经这样做了:

public Long getRowCount(CriteriaQuery criteriaQuery,CriteriaBuilder criteriaBuilder,Root<?> root){
    CriteriaQuery<Long> countCriteria = criteriaBuilder.createQuery(Long.class);
    Root<?> entityRoot = countCriteria.from(root.getJavaType());
    entityRoot.alias(root.getAlias());
    doJoins(root.getJoins(),entityRoot);
    countCriteria.select(criteriaBuilder.count(entityRoot));
    countCriteria.where(criteriaQuery.getRestriction());
    return this.entityManager.createQuery(countCriteria).getSingleResult();
}

private void doJoins(Set<? extends Join<?, ?>> joins,Root<?> root_){
    for(Join<?,?> join: joins){
        Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType());
        doJoins(join.getJoins(), joined);
    }
}

private void doJoins(Set<? extends Join<?, ?>> joins,Join<?,?> root_){
    for(Join<?,?> join: joins){
        Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType());
        doJoins(join.getJoins(),joined);
    }
}

当然你不需要 Root 作为输入参数,你可以从条件查询中得到它,

【讨论】:

    【解决方案2】:

    @lubo08 给出了正确答案 - 为他点赞。 但是对于两个极端情况,他/她的代码将不起作用:

    • 当条件查询的限制使用别名进行连接时,COUNT 也需要设置这些别名。
    • 当条件查询使用 fetch join [root.fetch(..) 而不是 root.join(..)]

    所以为了完整起见,我敢于改进他/她的解决方案并在下面介绍:

    public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> criteria,
            Root<T> root) {
        CriteriaQuery<Long> query = createCountQuery(cb, criteria, root);
        return this.entityManager.createQuery(query).getSingleResult();
    }
    
    private <T> CriteriaQuery<Long> createCountQuery(final CriteriaBuilder cb,
            final CriteriaQuery<T> criteria, final Root<T> root) {
    
        final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
        final Root<T> countRoot = countQuery.from(criteria.getResultType());
    
        doJoins(root.getJoins(), countRoot);
        doJoinsOnFetches(root.getFetches(), countRoot);
    
        countQuery.select(cb.count(countRoot));
        countQuery.where(criteria.getRestriction());
    
        countRoot.alias(root.getAlias());
    
        return countQuery.distinct(criteria.isDistinct());
    }
    
    @SuppressWarnings("unchecked")
    private void doJoinsOnFetches(Set<? extends Fetch<?, ?>> joins, Root<?> root) {
        doJoins((Set<? extends Join<?, ?>>) joins, root);
    }
    
    private void doJoins(Set<? extends Join<?, ?>> joins, Root<?> root) {
        for (Join<?, ?> join : joins) {
            Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
            joined.alias(join.getAlias());
            doJoins(join.getJoins(), joined);
        }
    }
    
    private void doJoins(Set<? extends Join<?, ?>> joins, Join<?, ?> root) {
        for (Join<?, ?> join : joins) {
            Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
            joined.alias(join.getAlias());
            doJoins(join.getJoins(), joined);
        }
    }
    

    虽然它仍然不完美,因为只有一个根受到尊重。
    但我希望它可以帮助某人。

    【讨论】:

    • 我必须添加一个谓词cb.equal(countRoot, root),否则会有一个“笛卡尔连接”
    【解决方案3】:

    我不能告诉你问题出在哪里,但我可以告诉你,使用连接的计数查询效果很好,至少在eclipselink jpa 中是这样。我的猜测是这是标准的东西,所以它也应该在休眠状态下工作。我将从简化您的代码开始,以找出问题所在。 我看到您已经从主查询中复制了一些 cont 查询。也许您可以尝试稍微改变一下这种方法,仅出于调试目的。

    我通常做的是:

    CriteriaQuery cqCount = builder.createQuery();
    Root<T> root = cq.from(T.class);
    cqCount.select(builder.count(root)); 
    ListJoin<T, Entity> join = root.join(T_.someList);
    Predicate predicate = builder.ge(join.get(Entity_.id), "myId"); 
    cqCount.where(predicate);
    TypedQuery<Long> q = em.createQuery(cqCount);
    

    看你的伪代码,好像你在join方法中使用了错误的类:它必须是起始类,而不是目标类。

    【讨论】:

    • 在晚安后阅读您的简单答案让我摆脱了 10 个小时的连接查询和标准问题... :)
    猜你喜欢
    • 2011-02-22
    • 2011-04-29
    • 1970-01-01
    • 2011-06-12
    • 2011-04-12
    • 2012-07-24
    • 2015-08-08
    • 2016-01-01
    • 2019-08-07
    相关资源
    最近更新 更多