[英]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.您必须为每个查询创建新的
Root
、 CriteriaQuery
和CriteriaBuiler
。
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.