【问题标题】:In JPA 2, using a CriteriaQuery, how to count results在 JPA 2 中,使用 CriteriaQuery,如何计算结果
【发布时间】:2011-02-22 10:57:12
【问题描述】:

我对 JPA 2 比较陌生,它是 CriteriaBuilder / CriteriaQuery API:

CriteriaQuery javadoc

CriteriaQuery in the Java EE 6 tutorial

我想计算 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(distinct col1, col2, col3);
  • 寻找下面的答案,但不要使用 qb.count,而是使用 qb.distinctCount @Bhavesh

标签: java jpa-2.0 criteriaquery


【解决方案1】:

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

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

【讨论】:

  • 我自己就是这么想的,谢谢。但这意味着我不能使用相同的查询实例来查询结果的数量,而我知道的实际结果类似于 SQL,但这会使这个 API 更像 OOP。好吧,我猜至少我可以重用一些谓词。
  • @Barett 如果它是一个相当大的计数,您可能不想将数百或数千个实体的列表加载到内存中只是为了找出有多少!
  • 请注意,qb.count 是在查询的 Root&lt;MyEntity&gt; 上完成的(Root&lt;MyEntity&gt; myEntity = cq.from(MyEntity.class)),这通常已经在您的正常选择代码中当你忘记时,你最终会加入自我。
  • 要重用相同的标准来检索对象和计数,您可能需要在根目录上使用别名,请参阅forum.hibernate.org/viewtopic.php?p=2471522#p2471522 示例。
  • 在 where 条件下不适用于我。它给了我表中的记录总数。忽略where条件,有什么理由吗?
【解决方案2】:

我已经使用 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();
    }
}

希望对你有帮助:)

【讨论】:

    【解决方案3】:
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Long> cq = cb.createQuery(Long.class);
    cq.select(cb.count(cq.from(MyEntity.class)));
    
    return em.createQuery(cq).getSingleResult();
    

    【讨论】:

      【解决方案4】:

      由于其他人的答案是正确的,但太简单了,所以为了完整起见,我将在下面展示代码 sn-p 以在 sophisticated JPA Criteria 查询(具有多个连接、提取、条件)。

      稍作修改this answer。

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

      希望它能节省一些人的时间。

      因为恕我直言,JPA Criteria API 既不直观也不可读。

      【讨论】:

      • @specializt 当然它并不完美 - 例如上述解决方案仍然缺少获取的递归连接。但是你认为仅仅因为这个我就不应该分享我的想法吗?恕我直言,分享知识是 StackOverfow 背后的主要思想。
      • 对数据库的递归永远是可以想象到的最糟糕的解决方案......这是初学者的错误。
      • @specializt recursion on databases?我在谈论 API 级别的递归。不要混淆这些概念 :-) JPA 带有非常强大/复杂的 API,允许您在单个查询中执行多个连接/获取/聚合/别名等。您必须在计数时处理它。
      • 显然你还没有理解 JPA 是如何工作的——绝大多数标准将被映射到适当的数据库查询,包括这些(非常奇怪的)连接。激活 SQL 输出并观察你的错误 - 没有“API 层”,JPA 是一个抽象层
      • 很可能,您会看到许多级联 JOIN——因为 JPA 还不能自动创建 SQL 函数;但这有时会改变......可能是 JPA 3,我记得关于这些事情的讨论
      【解决方案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,不得不将我的计数函数更改为以下,所以请随意使用,因为这是一个很难解决的问题,它可能不适合你的情况,它已经在从 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();
        }
      

      【讨论】:

      • 如果查询有连接怎么办?
      • 我认为唯一危险的情况是当你有一个左连接并且选择的根不是主要实体时。否则没关系,因为无论选择的实体,计数都是相同的。至于左加入实体,我很确定选择中的第一个实体是参考实体,例如,如果您有学生左加入课程,那么选择学生应该是自然的事情,因为可能有学生没有的课程已注册。
      • 如果原始查询是 groupBy 查询,则结果将为每个组一个计数。如果我们可以将 CriteriaQuery 变成一个 SubQuery,然后计算子查询,它在所有情况下都可以工作。我们能做到吗?
      • 嗨@Dave,我得出了和你一样的结论,真正的解决方案是能够将查询转换为子查询,这适用于所有情况,即使是在通过...分组。实际上,我似乎找不到为什么 CriteriaQuery 和 Subquery 的不同类的原因,或者至少它们共享的公共接口 AbstractQuery 没有定义选择方法的事实。因此,几乎没有办法重用任何东西。您是否找到了一个干净的解决方案来重用按查询分组来计算行数?
      【解决方案6】:

      您也可以使用投影:

      ProjectionList projection = Projections.projectionList();
      projection.add(Projections.rowCount());
      criteria.setProjection(projection);
      
      Long totalRows = (Long) criteria.list().get(0);
      

      【讨论】:

      • 我担心 Projections API 是 Hibernate 特有的,但问题是关于 JPA 2。
      • 不过,我觉得这是一个有用的补充,但也许它应该是一个评论。您能否扩展您的答案以包含完整的特定于 Hibernate 的答案?
      • gersonZaragocin 同意,但是cmets中没有代码块
      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 2016-01-01
        • 2012-02-21
        • 1970-01-01
        • 2019-08-07
        • 2011-04-12
        • 2011-04-29
        • 2011-04-10
        • 2012-07-24
        • 1970-01-01
        相关资源
        最近更新 更多