简体   繁体   English

如何计算通用JPA DAO中JPA 2 CriteriaQuery的行数?

[英]How to count the number of rows of a JPA 2 CriteriaQuery in a generic JPA DAO?

I'm new in JPA and want to implement a generic JPA DAO and need to find the number of rows of a query result set to implement pagination. 我是JPA的新手,想要实现一个通用的JPA DAO,需要找到查询结果集的行数来实现分页。 After searching the web, I can't find a practical way to do that. 搜索网络后,我找不到切实可行的方法。 Here is the code suggested in many articles: 以下是许多文章中建议的代码:

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

However, that code doesn't work when using join . 但是,使用join时该代码不起作用。 Is there any way to count the rows of a query result set using the JPA Criteria API? 有没有办法使用JPA Criteria API计算查询结果集的行数?

UPDATE : here is the code that create CriteriaQuery : 更新:这是创建CriteriaQuery的代码:

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

and some joins may be added to the root until the query have been executed: 并且可以将一些联接添加到根,直到执行查询为止:

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

and the generated exception is like : 并且生成的异常如下:

org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.id' [select count(generatedAlias0) from entity.Entity as generatedAlias0 where ( generatedAlias0.id>=13L ) and ( (generatedAlias1.id<=34L ) )] org.hibernate.hql.ast.QuerySyntaxException:无效路径:'generatedAlias1.id'[从entity.Entity中选择count(generatedAlias0)为generatedAlias0,其中(generatedAlias0.id> = 13L)和((generatedAlias1.id <= 34L)) ]

which generatedAlias1 should be on Entity and generatedAlias0 should be on the association that I joined on that. 其中generatedAlias1应该在Entity上,generatedAlias0应该在我加入的关联上。 Note that I implement Join properly because when I execute query without count query it executes without error and the Join works properly but when I try to execute count query it throws exception. 请注意,我正确实现了Join,因为当我执行不带计数查询的查询时,它执行时没有错误,并且Join正常工作但是当我尝试执行count查询时它会抛出异常。

I've done this: 我这样做了:

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

of course you do not need Root as input parameter you could get it from criteria query, 当然,您不需要Root作为输入参数,您可以从条件查询中获取它,

@lubo08 gave correct answer - kudos for him . @ lubo08给出了正确的答案 - 对他不以为然。 But for two corner cases his/her code won't work: 但对于两个角落的情况,他/她的代码将不起作用:

  • When criteria query's restrictions use aliases for joins - then COUNT also require these aliases to be set. 当条件查询的限制使用别名进行连接时 - 那么COUNT也需要设置这些别名。
  • When criteria query use fetch join [ root.fetch(..) instead of root.join(..) ] 条件查询使用fetch join [ root.fetch(..)而不是root.join(..) ]

So for completeness I dared to improve his/her solution and present below: 因此,为了完整起见,我敢于改进他/她的解决方案,并在下面提出:

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

Although it is still not perfect , because only one root is honored. 虽然它仍然不完美 ,因为只有一个根被尊重。
But I hope it helps somebody. 但我希望它对某人有所帮助。

I can't tell you where your problem is, but I can tell you that count queries with joins work well, at least in eclipselink jpa. 我不能告诉你你的问题在哪里,但我可以告诉你,连接的计数查询工作得很好,至少在eclipselink jpa中。 My guess is that this is standard stuff, so it should work also in hibernate. 我的猜测是这是标准的东西,所以它也应该在hibernate中工作。 I would start by simplifying your code in order to catch where the problem is. 我将从简化代码开始,以便了解问题所在。 I see that you have copied some pieces of your cont query from your main query. 我看到你从主查询中复制了一些续查询。 Maybe you can try to change a little bit this approach, just for debugging purpose. 也许您可以尝试改变这种方法,仅用于调试目的。

What I do usually is: 我通常做的是:

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

Looking at your pseudo-code, it seems that you are using the wrong class in the join method: it must be the starting class, not the target class. 看看你的伪代码,似乎你在join方法中使用了错误的类:它必须是起始类,而不是目标类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM