代码之家  ›  专栏  ›  技术社区  ›  blubb

JPA 2.0:计算任意的CriteriaQuery?

  •  11
  • blubb  · 技术社区  · 15 年前

    我试图实现以下便利方法:

    /**
     * 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 方法?

    7 回复  |  直到 14 年前
        1
  •  17
  •   Jose Luis Martin    12 年前

    我写了一个实用类, JDAL J请执行以下操作:

    • 计算结果: Long count = JpaUtils.count(em, criteriaQuery);
    • 复制标准查询: JpaUtils.copyCriteria(em, criteriaQueryFrom, criteriaQueryTo);
    • 获取计数条件: CriteriaQuery<Long> countCriteria = JpaUtils.countCriteria(em, criteria)

    等等。。。

    如果您对源代码感兴趣,请参见 JpaUtils.java

        2
  •  4
  •   reyiyo    14 年前

    我已经使用cb.createQuery()对其进行了分类(没有result type参数):

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

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

        3
  •  2
  •   Guido Medina    13 年前

    上面的解决方案都不适用于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();
      }
    
        4
  •  1
  •   Pascal Thivent    15 年前

    你在找这样的东西吗?

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

    注:我不知道这是否是实现这一点的正确/最佳方法,仍然在学习标准API。。。

        5
  •  1
  •   Marcin Cinik    13 年前

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

        6
  •  1
  •   humi    10 年前

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

    按城市查找用户的示例

     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(…); List users=yourCriteriaClassInstance.getUsers(…)

        7
  •  -3
  •   osdamv    14 年前

    我用冬眠和 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();        
    

    }

    希望帮助