简体   繁体   English

对两个条件查询使用相同的谓词

[英]Using the same predicates for two criteria queries

I want to run a pair of queries using the same array of Predicate : one to count the records, one to get a certain page of records.我想使用相同的Predicate数组运行一对查询:一个计算记录,一个获取特定记录页。 This seems like a pretty normal use case, to me, so there must be a good way to do it, but I have yet to find it.对我来说,这似乎是一个非常正常的用例,所以必须有一个很好的方法来做到这一点,但我还没有找到它。

So this is the part that fetches entities:所以这是获取实体的部分:

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

This works.这有效。 We get what we desire, predicates are correct etc.我们得到了我们想要的,谓词是正确的等等。

However, creating another query to determine the count using the same predicates fails.但是,使用相同的谓词创建另一个查询来确定计数会失败。 I've tried two different ways:我尝试了两种不同的方法:

(1) Re-using the Root : (1) 重用Root

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

This doesn't work and gives me java.lang.IllegalStateException: No criteria query roots were specified .这不起作用并给我java.lang.IllegalStateException: No criteria query roots were specified Odd, since I clearly specified the Root , but perhaps I can't re-use a Root that was created from a different CriteriaQuery ?奇怪,因为我清楚地指定了Root ,但也许我不能重用从不同的CriteriaQuery创建的Root Fine, let's create one from the same CriteriaQuery ...好的,让我们从同一个CriteriaQuery创建一个...

(2) Creating a new Root : (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();

Now we get a different error: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.fooProperty' [select count(generatedAlias0) from com.foo.FooEntity as generatedAlias0 where ( generatedAlias1.fooProperty = :param0 )]现在我们得到一个不同的错误: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.fooProperty' [select count(generatedAlias0) from com.foo.FooEntity as generatedAlias0 where ( generatedAlias1.fooProperty = :param0 )]

Looking at the HQL that was created, it appears that the "from" clause sets a generatedAlias0 , but all the stuff in the "where" clause references generatedAlias1 .查看创建的 HQL,似乎“from”子句设置了一个generatedAlias0 ,但“where”子句中的所有内容都引用了generatedAlias1 My guess is because the array of Predicate was built using a different Root than was used in the CriteriaQuery<Long> .我的猜测是因为Predicate数组是使用与CriteriaQuery<Long>使用的Root不同的Root构建的。

So, if it doesn't work either way, how would I re-use the same array of Predicate ?因此,如果它不起作用,我将如何重新使用相同的Predicate数组? Do I really have to re-create all of them with the second Root ?我真的必须用第二个Root重新创建所有这些吗? That seems super excessive to me, especially since they're both Root<FooEntity> .这对我来说似乎Root<FooEntity>过分了,尤其是因为它们都是Root<FooEntity> I feel like there must be a better way.我觉得一定有更好的方法。

You have to create new Root , CriteriaQuery and CriteriaBuiler for every query.您必须为每个查询创建新的RootCriteriaQueryCriteriaBuiler

If you use Spring , Specification can be opted for making Predicate reusable.如果您使用Spring ,则可以选择Specification使Predicate可重用。 Otherwise you can create your own Specification functional interface like this否则,您可以像这样创建自己的Specification功能接口

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

Usage:用法:

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

And then you can get predicate for every query this way然后你可以通过这种方式获得每个查询的谓词

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

The best approach is to create utility class with separated methods for every specification and combine them you need using Specification.and method最好的方法是为每个规范创建具有分离方法的实用程序类,并使用Specification.and方法将它们组合起来

You should use the same alias for the both Root elements, example:您应该为两个 Root 元素使用相同的别名,例如:

Root<FooEntity> entityRoot = entityQuery.from(FooEntity.class);
entityRoot.alias("alias1") // Property alias

Predicate[] predicates = createPredicates(criteriaBuilder, entityRoot, entityType );

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


CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Root<FooEntity> newRoot = countQuery.from(FooEntity.class);
newRoot.alias("alias1"); // Same property alias

countQuery.select(builder.count(newRoot));
countQuery.where(predicates);
Long count = em.createQuery(countQuery).getSingleResult();

This way JPA understands and query using the same restrictions这样 JPA 使用相同的限制来理解和查询

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM