Как подсчитать количество строк JPA 2 CriteriaQuery в обобщенном JPA DAO? - PullRequest
7 голосов
/ 29 января 2012

Я новичок в JPA и хочу реализовать общий JPA DAO, и мне нужно найти количество строк в наборе результатов запроса для реализации нумерации страниц. После поиска в Интернете я не могу найти практический способ сделать это. Вот код, предложенный во многих статьях:

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

Однако этот код не работает при использовании join. Можно ли подсчитать строки набора результатов запроса с помощью API критериев JPA?

ОБНОВЛЕНИЕ: Вот код, который создает CriteriaQuery:

    CriteriaQuery<T> queryDefinition = criteriaBuilder.createQuery(this.entityClass);
    Root<T> root = queryDefinition.from(this.entityClass);

и некоторые объединения могут быть добавлены в корень, пока запрос не будет выполнен:

public Predicate addPredicate(Root<T> root) {
                Predicate predicate = getEntityManager().getCriteriaBuilder().ge(root.join(Entity_.someList).get("id"), 13);
                return predicate;
}

и сгенерированное исключение выглядит так:

org.hibernate.hql.ast.QuerySyntaxException: неверный путь: 'generateAlias1.id' [выберите количество (generateAlias0) из entity.Entity как генерируемый Алиас0, где (генерированный Алиас0.id> = 13L) и ( (generateAlias1.id <= 34L))] </p>

, который генерировал Алиас1, должен быть в Entity, а генерируемый Алиас0 должен быть в ассоциации, к которой я присоединился. Обратите внимание, что я правильно реализую Join, потому что когда я выполняю запрос без запроса count, он выполняется без ошибок, и Join работает правильно, но когда я пытаюсь выполнить запрос count, выдается исключение.

Ответы [ 3 ]

7 голосов
/ 23 октября 2014

Я сделал это:

public Long getRowCount(CriteriaQuery criteriaQuery,CriteriaBuilder criteriaBuilder,Root<?> root){
    CriteriaQuery<Long> countCriteria = criteriaBuilder.createQuery(Long.class);
    Root<?> entityRoot = countCriteria.from(root.getJavaType());
    entityRoot.alias(root.getAlias());
    doJoins(root.getJoins(),entityRoot);
    countCriteria.select(criteriaBuilder.count(entityRoot));
    countCriteria.where(criteriaQuery.getRestriction());
    return this.entityManager.createQuery(countCriteria).getSingleResult();
}

private void doJoins(Set<? extends Join<?, ?>> joins,Root<?> root_){
    for(Join<?,?> join: joins){
        Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType());
        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());
        doJoins(join.getJoins(),joined);
    }
}

конечно, вам не нужен Root в качестве входного параметра, который вы можете получить из запроса критерия,

4 голосов
/ 19 мая 2015

@ lubo08 дал правильный ответ - слава ему . Но для двух угловых случаев его / ее код не будет работать:

  • Когда ограничения запроса критериев используют псевдонимы для объединений - тогда COUNT также требует установки этих псевдонимов.
  • Когда критерий запроса использует извлечение соединения [root.fetch(..) вместо root.join(..)]

Так что для полноты я осмелился улучшить его / ее решение и представить ниже:

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> criteria,
        Root<T> root) {
    CriteriaQuery<Long> query = createCountQuery(cb, criteria, 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);
    }
}

Хотя он все еще не совершенен , потому что учитывается только один корень.
Но я надеюсь, что это кому-нибудь поможет.

1 голос
/ 29 января 2012

Я не могу сказать вам, где ваша проблема, но я могу вам сказать, что подсчет запросов с объединениями работает хорошо, по крайней мере, в eclipselink jpa.Я предполагаю, что это стандартный материал, поэтому он должен работать и в спящем режиме.Я бы начал с упрощения вашего кода, чтобы понять, в чем проблема.Я вижу, что вы скопировали некоторые части вашего контрольного запроса из основного запроса.Может быть, вы можете попытаться немного изменить этот подход, просто для целей отладки.

Обычно я делаю следующее:

CriteriaQuery cqCount = builder.createQuery();
Root<T> root = cq.from(T.class);
cqCount.select(builder.count(root)); 
ListJoin<T, Entity> join = root.join(T_.someList);
Predicate predicate = builder.ge(join.get(Entity_.id), "myId"); 
cqCount.where(predicate);
TypedQuery<Long> q = em.createQuery(cqCount);

Глядя на свой псевдокод, кажется, что выиспользуя неправильный класс в методе соединения: это должен быть начальный класс, а не целевой класс.

...