У 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;
Bhavesh

шукаючи відповідь нижче, але замість qb.count використовуйте qb.distinctCount @Bhavesh
Tonino

Відповіді:


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

Очевидно, ви хочете наростити свій вираз з будь-якими обмеженнями та угрупованнями тощо, які ви пропустили в прикладі.


3
Ось що я зрозумів сам, дякую. Але це означає, що я не можу використовувати один і той же екземпляр запиту для запиту щодо кількості результатів і фактичних результатів, які я знаю, аналогічні SQL, але це зробило би цей API набагато більш подібним до OOP. Ну, принаймні, я можу повторно використовувати деякі предикати, я думаю.
Шон Патрік Флойд

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 .
Басейн

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

Сподіваюся, це допомагає :)


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

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

Сподіваюся, це економить чийсь час.

Оскільки API критеріїв IMHO JPA не є інтуїтивно зрозумілим і не читабельним.


2
@specializt, звичайно, це не ідеально - наприклад, у вищезгаданому рішенні все ще відсутня рекурсивна приєднання на виборах. Але ви вважаєте, що саме через це я не повинен ділитися своїми думками? Обмін знаннями IMHO є основною ідеєю StackOverfow.
Г. Демецький

Рекурсії на базах даних завжди є найгіршим можливим рішенням, яке можна уявити ... ось помилка початківців.
спеціаліз

@specializt recursion on databases? Я говорив про рекурсію на рівні API. Не плутайте ці поняття :-) JPA оснащений дуже потужним / складним API, який дозволяє вам робити кілька об'єднань / вибору / агрегацій / псевдонімів тощо в одному запиті. Ви повинні мати справу з цим під час підрахунку.
Г. Демецький

1
Мабуть, ви ще не зрозуміли, як працює JPA - переважна більшість ваших критеріїв буде відображено на відповідні запити бази даних, включаючи ці (надзвичайно дивні) приєднання. Активуйте вихід SQL і спостерігайте за своєю помилкою - немає "шару API", JPA - рівень АБСТРАЦІЇ
specializt

швидше за все, ви побачите багато каскадних ПРИЄДНАЙТЕСЬ - адже JPA ще не може автоматично створювати функції SQL; але це колись зміниться ... напевно, з JPA 3, я пригадую дискусії з цього приводу
specializt

5

Це трохи хитро, залежно від реалізації JPA 2, яку ви використовуєте, ця працює для EclipseLink 2.4.1, але не для зимуючого сну, тут є загальне число критеріїв запитів для 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 в режим глибокого сну, і мені довелося змінити свою функцію підрахунку на наступну, тому сміливо використовуйте будь-яке, оскільки це важка проблема для вирішення, вона може не працювати у вашому випадку, вона використовується з режиму сплячки. 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();
  }

що робити, якщо запит має приєднатись?
Дейв

Я думаю, що єдиний випадок, який може бути небезпечним, - це те, що ти з’єднався ліворуч, а вибраний корінь - не головна сутність. Інакше це не має значення, оскільки кількість рахунків буде однаковою незалежно від обраної сутності. Що стосується лівих об'єднань, я впевнений, що першою особою у виборі є референтна, наприклад, якщо у вас є студенти, які залишили курси приєднання, то вибір студента повинен бути природним явищем, оскільки можуть бути курси, якими студент не зарахований.
Гвідо Медіна

1
Якщо вихідний запит - це груповий запит, результатом буде одна кількість для кожної групи. Якщо ми можемо зробити CriteriaQuery в SubQuery, то порахуємо підзапит, він би працював у всіх випадках. Чи можемо ми це зробити?
Дейв

Привіт @Dave, я прийшов до того ж висновку, що і ви, справжнє рішення полягало б у тому, щоб мати можливість перетворювати запити в підзапити, які працювали б у всіх випадках, навіть для підрахунку рядків після групиBy. Насправді я не можу знайти причину, чому різні застереження для CriteriaQuery і Subquery, або в тому, що загальний інтерфейс, яким вони поділяються, AbstractQuery, не визначає метод вибору. Через це майже нічого не можна використовувати повторно. Ви знайшли чисте рішення для повторного використання групування за запитом для підрахунку рядків?
Аманда Тарафа Мас

1

Ви також можете використовувати Проекції:

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

Long totalRows = (Long) criteria.list().get(0);

1
Я боюся, API Projections є специфічним для сплячки, але питання про JPA 2.
gersonZaragocin

Все-таки я вважаю це корисним доповненням, але, можливо, це мав бути коментар. Чи можете ви розширити свою відповідь, щоб включити повну відповідь, що стосується сплячки?
Бенні Боттема

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()));
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.