在JPA 2中,使用CriteriaQuery,如何计算结果


114

我对JPA 2相当陌生,它是CriteriaBuilder / CriteriaQuery API:

CriteriaQuery Java文档

CriteriaQuery 在Java EE 6教程中

我想计算一个CriteriaQuery的结果而不实际检索它们。那有可能吗,我没有找到任何这样的方法,唯一的办法就是这样做:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery<MyEntity> cq = cb
        .createQuery(MyEntityclass);

// initialize predicates here

return entityManager.createQuery(cq).getResultList().size();

那不可能是正确的方法...

有解决方案吗?


如果有人可以帮助或包括在下面的答案中,这将非常有用。如何使用JPA条件API实现后续计数查询?从my_table中选择count(distcolt col1,col2,col3);
巴菲什

寻找下面的答案,但而不是qb.count使用qb.distinctCount @Bhavesh
Tonino

Answers:


220

类型查询MyEntity将返回MyEntity。您想要查询Long

CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(MyEntity.class)));
cq.where(/*your stuff*/);
return entityManager.createQuery(cq).getSingleResult();

显然,您将希望使用示例中跳过的任何限制和分组等来构建表达式。


3
我就是这么想的,谢谢。但这意味着我不能使用相同的查询实例来查询结果的数量和我知道的类似于SQL的实际结果,但这会使该API更像OOP。好吧,我猜至少我可以重用某些谓词。
肖恩·帕特里克·弗洛伊德

6
@Barett如果数量很大,您可能不想将数百或数千个实体的列表加载到内存中,只是想找出有多少个实体!
2012年

@Barett经常在分页的情况下使用。因此,需要总数而不是实际行的子集。
gkephorus

2
提醒您,此操作qb.count是在Root<MyEntity>查询(Root<MyEntity>myEntity = cq.from(MyEntity.class))上完成的,通常这已经在常规选择代码中了,而当您忘记了自己与自己的联接时。
gkephorus

2
要重用相同的条件来检索对象和计数,您可能需要在根目录上使用别名,有关示例,请参阅forum.hibernate.org/viewtopic.php?p=2471522#p2471522
2014年

31

我已经使用cb.createQuery()进行了排序(没有结果类型参数):

public class Blah() {

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery query = criteriaBuilder.createQuery();
    Root<Entity> root;
    Predicate whereClause;
    EntityManager entityManager;
    Class<Entity> domainClass;

    ... Methods to create where clause ...

    public Blah(EntityManager entityManager, Class<Entity> domainClass) {
        this.entityManager = entityManager;
        this.domainClass = domainClass;
        criteriaBuilder = entityManager.getCriteriaBuilder();
        query = criteriaBuilder.createQuery();
        whereClause = criteriaBuilder.equal(criteriaBuilder.literal(1), 1);
        root = query.from(domainClass);
    }

    public CriteriaQuery<Entity> getQuery() {
        query.select(root);
        query.where(whereClause);
        return query;
    }

    public CriteriaQuery<Long> getQueryForCount() {
        query.select(criteriaBuilder.count(root));
        query.where(whereClause);
        return query;
    }

    public List<Entity> list() {
        TypedQuery<Entity> q = this.entityManager.createQuery(this.getQuery());
        return q.getResultList();
    }

    public Long count() {
        TypedQuery<Long> q = this.entityManager.createQuery(this.getQueryForCount());
        return q.getSingleResult();
    }
}

希望能帮助到你 :)


23
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
cq.select(cb.count(cq.from(MyEntity.class)));

return em.createQuery(cq).getSingleResult();

12

由于其他答案是正确的,但又太简单了,因此为了完整起见,我在下面提供了要SELECT COUNT复杂的 JPA Criteria查询(具有多个联接,提取,条件)上执行的代码段。

此答案略有修改。

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> selectQuery,
        Root<T> root) {
    CriteriaQuery<Long> query = createCountQuery(cb, selectQuery, 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);
    }
}

希望它可以节省别人的时间。

因为IMHO JPA Criteria API不直观也不可读。


2
@specializt当然不是完美的-例如,上面的解决方案仍然缺少提取时的递归联接。但是,您是否认为仅因为这个原因我不应该分享自己的想法?恕我直言,共享知识是StackOverfow背后的主要思想。
G. Demecki 2015年

数据库上的递归始终是可想象的最糟糕的解决方案……这是初学者的错误。
specializt

@specializt recursion on databases?我在谈论API级别的递归。不要混淆这些概念:-) JPA带有非常强大的/复杂的 API,使您可以在单个查询中执行多个联接/获取/聚合/别名等。您必须在数数时处理它。
G. Demecki

1
显然,您尚未了解JPA的工作原理-绝大多数准则将映射到适当的数据库查询,包括这些(极其奇怪的)联接。激活SQL输出并观察您的错误-没有“ API层”,JPA是抽象层
specializt

最有可能的是,您会看到许多级联的JOIN-因为JPA尚无法自动创建SQL函数。但这会在某个时间改变...可能是JPA 3,我记得关于这些事情的讨论
specializt

5

这有点棘手,具体取决于您使用的JPA 2实现,该方法适用于EclipseLink 2.4.1,但不适用于Hibernate,这是EclipseLink的通用CriteriaQuery计数:

public static Long count(final EntityManager em, final CriteriaQuery<?> criteria)
  {
    final CriteriaBuilder builder=em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class);
    countCriteria.select(builder.count(criteria.getRoots().iterator().next()));
    final Predicate
            groupRestriction=criteria.getGroupRestriction(),
            fromRestriction=criteria.getRestriction();
    if(groupRestriction != null){
      countCriteria.having(groupRestriction);
    }
    if(fromRestriction != null){
      countCriteria.where(fromRestriction);
    }
    countCriteria.groupBy(criteria.getGroupList());
    countCriteria.distinct(criteria.isDistinct());
    return em.createQuery(countCriteria).getSingleResult();
  }

前几天,我从EclipseLink迁移到了Hibernate,不得不将我的count函数更改为以下代码,因此请随意使用,因为这是一个很难解决的问题,可能不适用于您的情况,自Hibernate以来一直在使用4.x,请注意,我不尝试猜测哪个是根,而是从查询中传递了它,因此问题得以解决,太多模棱两可的案例无法尝试:

  public static <T> long count(EntityManager em,Root<T> root,CriteriaQuery<T> criteria)
  {
    final CriteriaBuilder builder=em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class);

    countCriteria.select(builder.count(root));

    for(Root<?> fromRoot : criteria.getRoots()){
      countCriteria.getRoots().add(fromRoot);
    }

    final Predicate whereRestriction=criteria.getRestriction();
    if(whereRestriction!=null){
      countCriteria.where(whereRestriction);
    }

    final Predicate groupRestriction=criteria.getGroupRestriction();
    if(groupRestriction!=null){
      countCriteria.having(groupRestriction);
    }

    countCriteria.groupBy(criteria.getGroupList());
    countCriteria.distinct(criteria.isDistinct());
    return em.createQuery(countCriteria).getSingleResult();
  }

如果查询具有联接怎么办?
戴夫

我认为唯一危险的情况是当您有左联接并且所选择的根不是主要实体时。否则就没有关系,因为无论所选择的实体如何,计数都将相同。至于左联接实体,我很确定选择中的第一个实体是参考实体,例如,如果您有学生离开联接课程,那么选择学生应该是很自然的事情,因为可能存在某些课程,而该学生不是报名参加。
Guido Medina 2014年

1
如果原始查询是groupBy查询,则结果将是每个组一个计数。如果我们可以将CriteriaQuery变成SubQuery,然后计算该子查询,则在所有情况下都可以使用。我们可以做到吗?
戴夫2014年

@Dave,您好,我得出的结论是,真正的解决方案是能够将查询转换为子查询,该查询将适用于所有情况,即使是对groupBy之后的行进行计数。实际上,对于CriteriaQuery和Subquery的不同分类,或者似乎它们共享的公共接口AbstractQuery没有定义select方法的事实,我似乎找不到原因。因此,几乎没有任何方法可以重用。您是否找到了一种干净的解决方案来重用按查询分组的行计数?
阿曼达·塔拉法·马斯

1

您也可以使用投影:

ProjectionList projection = Projections.projectionList();
projection.add(Projections.rowCount());
criteria.setProjection(projection);

Long totalRows = (Long) criteria.list().get(0);

1
恐怕Projections API是特定于Hibernate的,但问题是关于JPA 2的
。– gersonZaragocin

不过,我发现它是一个有用的补充,但也许应该是一条评论。您能否扩展答案以包括完整的特定于Hibernate的答案?
Benny Bottema,

gersonZaragocin同意,但注释中没有代码块
Pavel Evstigneev

0

使用Spring Data Jpa,我们可以使用以下方法:

    /*
     * (non-Javadoc)
     * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification)
     */
    @Override
    public long count(@Nullable Specification<T> spec) {
        return executeCountQuery(getCountQuery(spec, getDomainClass()));
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.