JPA 2.0: считать за произвольный CriteriaQuery? - PullRequest
11 голосов
/ 22 октября 2010

Я пытаюсь реализовать следующий удобный метод:

/**
 * 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, число строк которого должно быть определено.

EDIT:

Я, к сожалению, обнаружил, что @ 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 ]

17 голосов
/ 12 февраля 2012

Я написал служебный класс, JDAL JpaUtils, чтобы сделать это:

  • Результаты подсчета: Long count = JpaUtils.count(em, criteriaQuery);
  • copy CriteriaQueries: JpaUtils.copyCriteria(em, criteriaQueryFrom, criteriaQueryTo);
  • получить критерии подсчета: CriteriaQuery<Long> countCriteria = JpaUtils.countCriteria(em, criteria)

и так далее ...

Если вас интересует исходный код, см. JpaUtils.java

4 голосов
/ 11 мая 2012

Я разобрался с помощью 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, где вы можете создать запрос и список вызовов () или count () с теми же критериями ограничений

1 голос
/ 08 сентября 2015

если вы хотите получить результат и количество всех элементов, таких как Page -элемент Spring Data, вы можете сделать два запроса.Что вы можете сделать, это отделить критерии от выполнения запроса.

Пример поиска пользователей по городу

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

1 голос
/ 21 ноября 2012

Ни одно из вышеперечисленных решений не работает для EclipseLink 2.4.1, все они заканчиваются подсчетом на декартовом произведении (N ^ 2), вот небольшой взлом для EclipseLink, единственный недостаток в том, что я не знаю, чтопроизойдет, если вы выбираете из более чем одного объекта, он будет пытаться сосчитать с 1-го найденного корня вашего 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();
  }
1 голос
/ 19 сентября 2012

Вся идея критериальных запросов заключается в том, что они строго типизированы. Таким образом, каждое решение, где вы используете необработанные типы (без обобщений в CriteriaQuery или Root или Root) - эти решения противоречат этой основной идее. Я просто столкнулся с той же самой проблемой, и я изо всех сил пытаюсь решить ее "надлежащим" (наряду с JPA2) способом.

1 голос
/ 22 октября 2010

Вы ищете что-то подобное?

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

0 голосов
/ 13 сентября 2011

я делаю что-то подобное с Hibernate и критерий 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();        

}

надеюсь, что помощь

...