В JPA 2, используя CriteriaQuery, как подсчитывать результаты

114

Я новичок в JPA 2 и его API CriteriaBuilder / CriteriaQuery:

CriteriaQuery Javadoc

CriteriaQuery в руководстве по Java EE 6

Я хотел бы подсчитать результаты CriteriaQuery, не получая их на самом деле. Возможно ли, что я не нашел такого метода, единственный способ сделать это:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery<MyEntity> cq = cb
        .createQuery(MyEntityclass);

// initialize predicates here

return entityManager.createQuery(cq).getResultList().size();

И это не может быть правильным способом ...

Есть решение?

Шон Патрик Флойд
источник
Было бы очень полезно, если бы кто-нибудь мог помочь или включить в ответы ниже. Как выполнить следующий запрос подсчета с использованием API критериев JPA? выберите количество (отличный col1, col2, col3) из my_table;
Бхавеш
глядя на ответ ниже, но вместо qb.count используйте qb.distinctCount @Bhavesh
Тонино

Ответы:

220

Запрос типа MyEntityбудет возвращен MyEntity. Вам нужен запрос для файла Long.

CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(MyEntity.class)));
cq.where(/*your stuff*/);
return entityManager.createQuery(cq).getSingleResult();

Очевидно, вы захотите построить свое выражение с любыми ограничениями, группировками и т. Д., Которые вы пропустили в примере.

Affe
источник
3
Я сам так понял, спасибо. Но это означает, что я не могу использовать один и тот же экземпляр запроса для запроса количества результатов и фактических результатов, которые, как я знаю, аналогичны SQL, но которые сделают этот API намного более похожим на ООП. Что ж, по крайней мере, я могу повторно использовать некоторые из предикатов.
Шон Патрик Флойд
6
@Barett, если это довольно большое количество, вы, вероятно, не захотите загружать список из сотен или тысяч объектов в память только для того, чтобы узнать, сколько их!
Affe
@Barett часто используется в случае разбивки на страницы. Следовательно, необходимо общее количество и только подмножество фактических строк.
gkephorus
2
Имейте в виду, что qb.countэто выполняется по Root<MyEntity>вашему запросу ( Root<MyEntity>myEntity = cq.from (MyEntity.class)), и это часто уже находится в вашем обычном коде выбора, и когда вы забываете, вы получаете соединение с собой.
gkephorus
2
Чтобы повторно использовать те же критерии для получения объектов и подсчета, вам может потребоваться использовать псевдонимы в корне, см. Например forum.hibernate.org/viewtopic.php?p=2471522#p2471522 .
Pool
31

Я разобрался с этим, используя 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();
    }
}

Надеюсь, поможет :)

reyiyo
источник
23
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
cq.select(cb.count(cq.from(MyEntity.class)));

return em.createQuery(cq).getSingleResult();
axtavt
источник
12

Как и другие правильные ответы, но слишком просто, поэтому для полноты картины я представляю ниже фрагмент кода для выполнения SELECT COUNTна сложный запрос JPA Criteria (с нескольких объединений, распаковывает, условия).

Этот ответ немного изменен .

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> selectQuery,
        Root<T> root) {
    CriteriaQuery<Long> query = createCountQuery(cb, selectQuery, root);
    return this.entityManager.createQuery(query).getSingleResult();
}

private <T> CriteriaQuery<Long> createCountQuery(final CriteriaBuilder cb,
        final CriteriaQuery<T> criteria, final Root<T> root) {

    final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
    final Root<T> countRoot = countQuery.from(criteria.getResultType());

    doJoins(root.getJoins(), countRoot);
    doJoinsOnFetches(root.getFetches(), countRoot);

    countQuery.select(cb.count(countRoot));
    countQuery.where(criteria.getRestriction());

    countRoot.alias(root.getAlias());

    return countQuery.distinct(criteria.isDistinct());
}

@SuppressWarnings("unchecked")
private void doJoinsOnFetches(Set<? extends Fetch<?, ?>> joins, Root<?> root) {
    doJoins((Set<? extends Join<?, ?>>) joins, root);
}

private void doJoins(Set<? extends Join<?, ?>> joins, Root<?> root) {
    for (Join<?, ?> join : joins) {
        Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
        joined.alias(join.getAlias());
        doJoins(join.getJoins(), joined);
    }
}

private void doJoins(Set<? extends Join<?, ?>> joins, Join<?, ?> root) {
    for (Join<?, ?> join : joins) {
        Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
        joined.alias(join.getAlias());
        doJoins(join.getJoins(), joined);
    }
}

Надеюсь, это сэкономит чье-то время.

Потому что IMHO JPA Criteria API не интуитивно понятен и не совсем читаем.

Г. Демеки
источник
2
@specializt, конечно, не идеально - например, в приведенном выше решении по-прежнему отсутствуют рекурсивные соединения при выборке. Но неужели вы думаете, что из-за этого я не должен делиться своими мыслями? IMHO обмен знаниями - основная идея StackOverfow.
Г. Демеки
Рекурсия в базах данных - всегда худшее из возможных решений, которое только можно вообразить ... это ошибка новичков.
specializt
@specializt recursion on databases? Я говорил о рекурсии на уровне API. Не путайте эти концепции :-) JPA поставляется с очень мощным / сложным API, который позволяет вам выполнять несколько соединений / выборок / агрегаций / псевдонимов и т. Д. В одном запросе. С этим нужно иметь дело при подсчете.
G. Demecki
1
По-видимому, вы еще не поняли, как работает JPA - подавляющее большинство ваших критериев будет сопоставлено с соответствующими запросами к базе данных, включая эти (чрезвычайно странные) объединения. Активируйте вывод SQL и обратите внимание на свою ошибку - нет «слоя API», JPA - это уровень АБСТРАКЦИИ
специализируется
скорее всего, вы увидите много каскадных JOIN, потому что JPA еще не может автоматически создавать функции SQL; но это изменится когда - нибудь ... возможно , с JPA 3, я вспоминаю дискуссии об этих вещах
specializt
5

Это немного сложно, в зависимости от используемой вами реализации JPA 2, этот работает для EclipseLink 2.4.1, но не для Hibernate, здесь общий счетчик CriteriaQuery для 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 на Hibernate и мне пришлось изменить свою функцию подсчета на следующую, поэтому не стесняйтесь использовать либо, поскольку это сложная проблема для решения, она может не сработать для вашего случая, она использовалась с Hibernate 4.x, обратите внимание, что я не пытаюсь угадать, что является корнем, вместо этого я передаю его из запроса, поэтому проблема решена, слишком много двусмысленных угловых случаев, чтобы попытаться угадать:

  public static <T> long count(EntityManager em,Root<T> root,CriteriaQuery<T> criteria)
  {
    final CriteriaBuilder builder=em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class);

    countCriteria.select(builder.count(root));

    for(Root<?> fromRoot : criteria.getRoots()){
      countCriteria.getRoots().add(fromRoot);
    }

    final Predicate whereRestriction=criteria.getRestriction();
    if(whereRestriction!=null){
      countCriteria.where(whereRestriction);
    }

    final Predicate groupRestriction=criteria.getGroupRestriction();
    if(groupRestriction!=null){
      countCriteria.having(groupRestriction);
    }

    countCriteria.groupBy(criteria.getGroupList());
    countCriteria.distinct(criteria.isDistinct());
    return em.createQuery(countCriteria).getSingleResult();
  }
Гвидо Медина
источник
что, если в запросе есть соединения?
Дэйв
Я думаю, что единственный случай, который был бы опасен, - это когда у вас есть левое соединение, а выбранный корень не является основным объектом. В противном случае это не имеет значения, потому что счетчик будет одинаковым независимо от выбранной сущности. Что касается сущностей левого соединения, я совершенно уверен, что первая сущность в выбранном является эталонной, например, если у вас есть студенты, которые покинули курсы, тогда выбор студента должен быть естественным, потому что могут быть курсы, которыми студент не является записан.
Guido Medina
1
Если исходный запрос - это запрос groupBy, результатом будет один счет для каждой группы. Если мы сможем превратить CriteriaQuery в SubQuery, а затем посчитать подзапрос, он будет работать во всех случаях. Мы можем это сделать?
Дэйв
Привет, @Dave, я пришел к такому же выводу, что и вы, реальным решением было бы иметь возможность преобразовывать запросы в подзапросы, которые будут работать во всех случаях, даже для подсчета строк после groupBy. На самом деле я не могу найти причину, почему разные классы для CriteriaQuery и Subquery, или, по крайней мере, тот факт, что общий интерфейс, который они разделяют, AbstractQuery, не определяет метод выбора. Из-за этого нет возможности повторно использовать почти что-либо. Вы нашли чистое решение для повторного использования группированного запроса для подсчета строк?
Аманда Тарафа Мас
1

Вы также можете использовать прогнозы:

ProjectionList projection = Projections.projectionList();
projection.add(Projections.rowCount());
criteria.setProjection(projection);

Long totalRows = (Long) criteria.list().get(0);
Павел Евстигнеев
источник
1
Боюсь, Projection API специфичен для Hibernate, но вопрос касается JPA 2.
gersonZaragocin
Тем не менее, я считаю это полезным дополнением, но, возможно, это должен был быть комментарий. Можете ли вы расширить свой ответ, включив в него полный ответ, относящийся к Hibernate?
Benny Bottema
gersonZaragocin согласен, но кодовых блоков в комментариях нет
Павел Евстигнеев
0

С Spring Data Jpa мы можем использовать этот метод:

    /*
     * (non-Javadoc)
     * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification)
     */
    @Override
    public long count(@Nullable Specification<T> spec) {
        return executeCountQuery(getCountQuery(spec, getDomainClass()));
    }
Kafkas
источник