Использование одинаковых предикатов для двух критериальных запросов - PullRequest
0 голосов
/ 05 марта 2020

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

Так что это часть, которая выбирает сущности:

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
EntityType<ENTITY> entityType = entityManager.getMetamodel().entity(FooEntity.class);
CriteriaQuery<FooEntity> entityQuery = criteriaBuilder.createQuery(FooEntity.class);
Root<FooEntity> entityRoot = entityQuery.from(FooEntity.class);

// Use the criteria builder, root, and type to create some predicates.
Predicate[] predicates = createPredicates(criteriaBuilder, entityRoot, entityType );

// Fetch the entities.
entityQuery.select(entityRoot);
entityQuery.where(predicates);
List<FooEntity> entities = entityManager.createQuery(entityQuery)
    .setFirstResult(0) // Just get the first page
    .setMaxResults(50)
    .getResultList();

Это работает. Мы получаем то, что хотим, предикаты верны и т. Д. c.

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

(1) Повторное использование Root:

CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
countQuery.select(criteriaBuilder.count(entityRoot));
countQuery.where(predicates);
long count = entityManager.createQuery(countQuery).getSingleResult();

Это не работает и дает мне java.lang.IllegalStateException: No criteria query roots were specified. Странно, поскольку я четко указал Root, но, возможно, я не могу повторно использовать Root, который был создан из другого CriteriaQuery? Хорошо, давайте создадим один из тех же CriteriaQuery ...

(2) Создание нового Root:

CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
countQuery.select(criteriaBuilder.count(countQuery.from(FooEntity.class));
countQuery.where(predicates);
long count = entityManager.createQuery(countQuery).getSingleResult();

Теперь мы получаем другую ошибку: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.fooProperty' [select count(generatedAlias0) from com.foo.FooEntity as generatedAlias0 where ( generatedAlias1.fooProperty = :param0 )]

Глядя на созданный HQL, выясняется, что в предложении "from" задается generatedAlias0, но все содержимое в предложении "where" ссылается на generatedAlias1. Я предполагаю, что массив Predicate был построен с использованием Root, отличного от того, который использовался в CriteriaQuery<Long>.

Так что, если он не работает в любом случае, как бы я использовал повторно? тот же массив Predicate? Неужели мне нужно воссоздать их все со вторым Root? Это кажется мне чрезмерным, тем более что они оба Root<FooEntity>. Я чувствую, что должен быть лучший способ.

1 Ответ

0 голосов
/ 05 марта 2020

Вы должны создать новые Root, CriteriaQuery и CriteriaBuiler для каждого запроса.

Если вы используете Spring , Спецификация может быть выбрана для Predicate многоразового использования. В противном случае вы можете создать свой собственный Specification функциональный интерфейс, подобный этому

@FunctionalInterface
public interface Specification<T> {
    Predicate toPredicate(
          Root<T> root, 
          CriteriaQuery<?> query, 
          CriteriaBuilder criteriaBuilder);
}

Использование:

public Specification<FooEntity> createSpecification(YourParameters parameters) {
    return (root, query, criteriaBuilder) -> {
        Predicate fullPredicate;

        // create predicates using parameters, root, query, criteriaBuilder 
        // and concatenate them into one: fullPredicate = predicate.and(anotherPredicate);

        return fullPredicate;
    };
}

И затем вы можете получить предикат для каждого запроса следующим образом

Predicate predicate = createSpecification(parameters)
    .toPredicate(entityRoot, entityQuery, criteriaBuilder);

Лучший подход - создать служебный класс с разделенными методами для каждой спецификации и объединить их, как вам нужно, используя Specification.and метод

...