【问题标题】:Using the same predicates for two criteria queries对两个条件查询使用相同的谓词
【发布时间】:2020-06-18 10:10:43
【问题描述】:

我想使用相同的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();

这行得通。我们得到了我们想要的,谓词是正确的等等。

但是,使用相同谓词创建另一个查询来确定计数会失败。我尝试了两种不同的方法:

(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,但也许我不能重新使用从不同的CriteriaQuery 创建的Root?好的,让我们从同一个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 的数组是使用与CriteriaQuery&lt;Long&gt; 不同的Root 构建的。

那么,如果这两种方法都不起作用,我将如何重新使用相同的 Predicate 数组?我真的必须用第二个Root 重新创建所有这些吗?这对我来说似乎太过分了,特别是因为他们都是Root&lt;FooEntity&gt;。我觉得一定有更好的方法。

【问题讨论】:

  • 像你一样指定root ntityQuery.from(FooEntity.class);
  • 我不明白你的评论。我的选项 (1) 是一样的,但只是为了后代,我尝试从实体查询创建一个新根:countQuery.select(criteriaBuilder.count(entityQuery.from(FooEntity.class))); ...但正如预期的那样,它产生了相同的错误(“未指定条件查询根” )。

标签: java hibernate jpa criteria-api


【解决方案1】:

您必须为每个查询创建新的RootCriteriaQueryCriteriaBuiler

如果您使用 Spring,则可以选择 Specification 使 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 方法将它们组合起来

【讨论】:

    【解决方案2】:

    您应该为两个 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();
    

    这种方式 JPA 使用相同的限制来理解和查询

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-24
      • 1970-01-01
      • 1970-01-01
      • 2017-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多