JPA 2.0: подсчет произвольных CriteriaQuery?

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

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


person blubb    schedule 22.10.2010    source источник
comment
Где вы можете найти решение? Я на том же перекрестке и не знаю, как поступить. Спасибо.   -  person Ittai    schedule 22.12.2011
comment
@Ittai: я почти уверен, что это невозможно.   -  person blubb    schedule 22.12.2011
comment
@Ittai: Видимо, это все-таки возможно. Пожалуйста, смотрите ответ Хосе Луиса Мартина. Я не пробовал, но мне кажется, что это верное решение.   -  person blubb    schedule 21.02.2012
comment
Большое спасибо за наводку, посмотрю, когда вернусь к этому.   -  person Ittai    schedule 22.02.2012


Ответы (7)


Для этого я написал служебный класс JDAL JpaUtils:

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

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

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

person Jose Luis Martin    schedule 12.02.2012
comment
Вы должны упомянуть, что являетесь автором указанного служебного класса. Помимо этого, этот ответ заслуживает полной оценки, и я присужу награду за этот ответ, как только закончится обязательный период ожидания в 24 часа. Спасибо! - person blubb; 21.02.2012
comment
@blubb Спасибо, я просто редактирую. Не полностью протестировано, но, похоже, работает со сложными критериями. - person Jose Luis Martin; 21.02.2012
comment
@JoseLuisMartin - можете ли вы помочь, как выполнить следующий запрос подсчета с использованием API критериев JPA? SELECT COUNT (DISTINCT col1, col2, col3) FROM my_table; - person Bhavesh; 09.02.2019

Я разобрался с этим, используя 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() с теми же ограничениями критериев.

person reyiyo    schedule 11.05.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();
  }
person Guido Medina    schedule 21.11.2012
comment
Ой! EclipseLink добавляет корни из предиката в критерий. Я просто работаю над версией 2.0. Спасибо! - person Jose Luis Martin; 26.06.2013

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

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

person Pascal Thivent    schedule 22.10.2010
comment
@Simon: Рад, что это помогло. Тогда смело принимайте ответ (зеленая галочка под счетчиком голосов слева). - person Pascal Thivent; 26.10.2010
comment
К сожалению, это решение работает только для тривиальных запросов. (См. редактирование вопроса для объяснения, это слишком долго для комментария.) - person blubb; 04.09.2011

если вам нужен результат и количество всех элементов, таких как 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(...)

person humi    schedule 08.09.2015

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

person Marcin Cinik    schedule 19.09.2012

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

}

надеюсь помочь

person osdamv    schedule 13.09.2011
comment
Я знаю, что это возможно с помощью Hibernate API (как я уже говорил в вопросе). Меня интересует только альтернатива с использованием интерфейса JPA. - person blubb; 14.09.2011