【问题标题】:JPA 2.0: count for arbitrary CriteriaQuery?JPA 2.0:计算任意 CriteriaQuery?
【发布时间】:2011-04-29 04:41:42
【问题描述】:

我正在尝试实现以下便捷方法:

/**
 * Counts the number of results of a search.
 * @param criteria The criteria for the query.
 * @return The number of results of the query.
 */
public int findCountByCriteria(CriteriaQuery<?> criteria);

在 Hibernate 中,这是由

完成的
criteria.setProjection(Projections.rowCount());

JPA 中与上面的等价物是什么?我发现了许多简单的计数示例,但没有一个使用应确定行数的 CriteriaQuery。

编辑:

不幸的是,我发现@Pascal 的答案不正确。这个问题非常微妙,只有在您使用连接时才会出现:

// Same query, but readable:
// SELECT *
// FROM Brain b
// WHERE b.iq = 170

CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
Join<Object, Object> brainJoin = root.join("brain");
Predicate iqPredicate = cb.equal(brainJoin.<Integer>get("iq"), 170);
query.select(root).where(iqPredicate);

当调用findCountByCriteria(query)时,它会死掉并出现以下异常:

org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.iq' [select count(generatedAlias0) from xxx.tests.person.dom.Person as generatedAlias0 where generatedAlias1.iq=170]

有没有其他方法可以提供这样的CountByCriteria 方法?

【问题讨论】:

  • 在哪里可以找到解决方案?我在同一个路口,不知道如何继续。谢谢。
  • @Ittai:我很确定这毕竟是不可能的。
  • @Ittai:显然,毕竟这是可能的。请参阅何塞·路易斯·马丁的回答。我没有尝试过,但这对我来说似乎是真正的解决方案。
  • 非常感谢您的提醒,等我回来看看。

标签: orm jpa count jpa-2.0 criteria-api


【解决方案1】:

我写了一个实用类,JDALJpaUtils 来做:

  • 计数结果:Long count = JpaUtils.count(em, criteriaQuery);
  • 复制条件查询:JpaUtils.copyCriteria(em, criteriaQueryFrom, criteriaQueryTo);
  • 获取计数条件:CriteriaQuery&lt;Long&gt; countCriteria = JpaUtils.countCriteria(em, criteria)

等等……

如果您对源代码感兴趣,请查看JpaUtils.java

【讨论】:

  • 您应该提到您是该实用程序类的作者。除此之外,这个答案值得满分,只要 24 小时的强制等待期结束,我就会奖励这个答案。谢谢!
  • @blubb 谢谢,我只是编辑。未经过全面测试,但似乎适用于复杂的标准
  • @JoseLuisMartin - 您能否帮助了解如何使用 JPA 标准 API 实现以下计数查询? SELECT COUNT(DISTINCT col1, col2, col3) FROM my_table;
【解决方案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();
    }
}

希望对你有帮助:)

我所做的类似于 CriteriaBuilder 的构建器,您可以在其中构建查询并使用相同的条件限制调用 list() 或 count()

【讨论】:

    【解决方案3】:

    以上解决方案都不适用于 EclipseLink 2.4.1,它们都以笛卡尔积 (N^2) 结束,这是 EclipseLink 的一个小技巧,唯一的缺点是我不知道是什么如果您从多个实体中选择,则会发生这种情况,它将尝试从您的 CriteriaQuery 的第一个找到的根开始计数,但此解决方案不适用于 Hibernate(JDAL 可以,但 JDAL 不适用于 EclipseLink)

    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 将根从谓词添加到条件。我只是为 2.0 版工作。谢谢!
    【解决方案4】:

    你在寻找这样的东西吗?

    /**
     * Counts the number of results of a search.
     * 
     * @param criteria The criteria for the query.
     * @return The number of results of the query.
     */
    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();
    }
    

    你可以这样使用:

    // a search based on the Criteria API
    CriteriaBuilder builder = em.getCriteriaBuilder();
    CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
    Root<Person> personRoot = criteria.from(Person.class);
    criteria.select(personRoot);
    Predicate personRestriction = builder.and(
        builder.equal(personRoot.get(Person_.gender), Gender.MALE),
        builder.equal(personRoot.get(Person_.relationshipStatus), RelationshipStatus.SINGLE)
    );
    criteria.where(personRestriction);
    //...
    
    // and to get the result count of the above query
    Long count = findCountByCriteria(criteria);
    

    PS:我不知道这是否是实现它的正确/最佳方式,仍在学习 Criteria API...

    【讨论】:

    • @Simon:很高兴它有帮助。然后随意接受答案(左侧投票柜台下方的绿色勾号)。
    • 不幸的是,这个解决方案只适用于琐碎的查询。 (请参阅问题的编辑以获取解释,评论太长了。)
    【解决方案5】:

    如果您想要 Spring Data 的 Page-Element 等所有元素的结果和计数,您可以执行两个查询。您可以做的是将条件与查询执行分开。

    按城市查找用户的示例

     public List<User> getUsers(int userid, String city, other values ...) {
    
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> q = cb.createQuery(User.class);
        Root<User> c = q.from(User.class);
    
        List<Predicate> conditions = createConditions(c, cb, userid, city, ...other values);
        List<User> users = em.createQuery(q.select(c).where(conditions.toArray(new Predicate[] {})).distinct(true))
                .setMaxResults(PAGE_ELEMENTS).setFirstResult(page * PAGE_ELEMENTS).getResultList();
        return users;
    }
    

    除了 getUser 方法,您还可以构建第二个计算元素的方法

    public Long getElemCount(int userid,  String city, ...other values) {
    
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Long> q = cb.createQuery(Long.class);
        Root<Location> root = q.from(Location.class);
    
        List<Predicate> conditions = createConditions(root, cb, userid, page, city, filter, module, isActive);
        Long userCount = em.createQuery(q.select(cb.count(root)).where(conditions.toArray(new Predicate[] {})).distinct(true))
                .getSingleResult();
    
        return userCount;
    }
    

    createConditions 方法将同时处理这两种情况,因此您不必为条件重复您的逻辑。

    <T> List<Predicate> createConditions(Root<T> root, CriteriaBuilder cb, int userid, String city, ... other values) {
    
        Join<User, SecondEntity> usr = root.join("someField");
        // add joins as you wish
    
        /*
         * Build Conditions
         */
        List<Predicate> conditions = new ArrayList<>();
    
        conditions.add(cb.equal(root.get("id"), userid));
    
        if (!city.equals("")) {
           conditions.add(cb.like(...));
        }
    
       // some more conditions...
    
        return conditions;
    }
    

    在您的控制器中,您可以执行类似的操作

    long elementCount = yourCriteriaClassInstance.getElementCount(...); 列出用户 = yourCriteriaClassInstance.getUsers(...)

    【讨论】:

      【解决方案6】:

      条件查询的整个想法是它们是强类型的。因此,您使用原始类型的每个解决方案(在 CriteriaQuery 或 Root 或 Root 中没有泛型) - 这些解决方案都违背了这个主要思想。我只是遇到了同样的问题,我正在努力以“正确”(以及 JPA2)的方式解决它。

      【讨论】:

        【解决方案7】:

        我用hibernate和criteria api做类似的事情

        public Long getRowsCount(List<Criterion> restrictions ) {
               Criteria criteria = getSession().createCriteria(ThePersistenclass.class);
               for (Criterion x : restrictions)
                     criteria.add(x);
           return criteria.setProjection(Projections.rowCount()).uniqueResult();        
        

        }

        希望帮助

        【讨论】:

        • 我知道 Hibernate API 可以做到这一点(正如我在问题中所述)。我只对使用 JPA 接口的替代方案感兴趣。
        猜你喜欢
        • 2019-08-07
        • 2011-08-03
        • 2012-02-21
        • 2011-02-22
        • 2015-08-24
        • 2015-12-16
        • 1970-01-01
        • 2016-01-01
        • 1970-01-01
        相关资源
        最近更新 更多