JPA 2.0:任意 CriteriaQuery 的计数?

发布于 2024-09-28 18:29:36 字数 1251 浏览 7 评论 0原文

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

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

I am trying to implement the following convenience method:

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

In Hibernate, this is done by

criteria.setProjection(Projections.rowCount());

What is the equivalent to the above in JPA? I found numerous simple count examples, but none of them made use of a CriteriaQuery whose row count should be determined.

EDIT:

I unfortunately found out that @Pascal's answer is not the correct one. The problem is very subtle and only shows up when you use joins:

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

When calling findCountByCriteria(query), it dies with the following exception:

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

Is there any other way to provide such a CountByCriteria method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

孤独岁月 2024-10-05 18:29:36

我编写了一个实用程序类 JDAL JpaUtils 来执行此操作:

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

等等...

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

I wrote a utility class, JDAL JpaUtils to do it:

  • count results: Long count = JpaUtils.count(em, criteriaQuery);
  • copy CriteriaQueries: JpaUtils.copyCriteria(em, criteriaQueryFrom, criteriaQueryTo);
  • get count criteria: CriteriaQuery<Long> countCriteria = JpaUtils.countCriteria(em, criteria)

and so on...

If you are interested in the source code, see JpaUtils.java

苏璃陌 2024-10-05 18:29:36

我已经使用 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 () 具有相同条件限制

I've sorted this out using the cb.createQuery() (without the result type parameter):

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

Hope it helps :)

What I did is something like a builder of a CriteriaBuilder where you can build a query and call list() or count() with the same criteria restrictions

鲸落 2024-10-05 18:29:36

上述解决方案都不适用于 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();
  }

None of the above solutions work for EclipseLink 2.4.1, they all ended with a count on a Cartesian product (N^2), here is a small hack for EclipseLink, the only drawback is that I don't know what will happen if you are selecting FROM more than one Entity, it will try to count from the 1st found Root of your CriteriaQuery, this solution DOESN'T work for Hibernate though (JDAL does, but JDAL doesn't work for 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();
  }
人│生佛魔见 2024-10-05 18:29:36

您在寻找这样的东西吗?

/**
 * 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...

Are you looking for something like this?

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

That you could use like this:

// 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: I don't know if this is the right/best way to implement this, still learning the Criteria API...

夏至、离别 2024-10-05 18:29:36

如果您想要结果和所有元素的计数,例如 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(...); 的 操作
列出用户 = yourCriteriaClassInstance.getUsers(...)

if you want the result and the count of all elements like Spring Data's Page-Element you can do two queries. What you can do is to separate the criteria from the query-execution.

Example to find Users by City

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

addiional to the getUser Method you can build a second that will count your elements

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

and the createConditions Method will handle both, so you do not have to duplicate your logic for the criteria.

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

in your contoller you can do something like

long elementCount = yourCriteriaClassInstance.getElementCount(...);
List users = yourCriteriaClassInstance.getUsers(...)

不知在何时 2024-10-05 18:29:36

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

The whole idea of criteria queries is that they are strongly typed. So each solution, where you're using raw types (without generics in CriteriaQuery or Root or Root) - these solutions are against that main idea. I just run into the same issue and I'm struggling to solve it in a "proper" (along with JPA2) way.

一绘本一梦想 2024-10-05 18:29:36

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

}

希望有帮助

i do somethig like thath with hibernate and 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();        

}

hope help

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文