使用CriteriaBuilder进行JPA + Hibernate计数(*) - 使用generatedAlias

时间:2013-01-16 16:57:07

标签: hibernate jpa criteria-api

尝试使用count(*)创建CriteriaBuilder类型查询时,我会遇到以下别名问题。

我应该对下面的代码进行哪些更改才能获得计数?

约束:

  1. 我必须使用CriteriaBuilder / Query,因为必须根据值动态构建where子句。
  2. 我只需要COUNT,而不是内存中的对象列表。
  3. 代码示例摘要:

     Class<ReqStatCumulative> entityClass = ReqStatCumulative.class;
     @Override
        public long getCountForAlertConfig(AlertConfig cfg) {
            long count = 0L;
            if (null != cfg) {
                CriteriaBuilder qb = entityManager.getCriteriaBuilder();
    
                Metamodel model = entityManager.getMetamodel();
                EntityType<ReqStatCumulative> reqStatEntType_ = model.entity(entityClass);
                CriteriaQuery<ReqStatCumulative> cq = qb.createQuery(entityClass);
                Root<ReqStatCumulative> rootReqStatEnt = cq.from(reqStatEntType_);
                Path<Long> processTimeSeconds = rootReqStatEnt.<Long> get("processTimeSeconds");
                cq.where(qb.and(qb.greaterThan(processTimeSeconds, (long) cfg.getProcessTimeExceedsSec()),//
                        qb.lessThan(processTimeSeconds, (long) cfg.getProcessTimeExceedsSec() + 100))//
                );//
                findCountByCriteria(entityManager, cq, qb);
                log.debug("\n\t#####Alert desc:" + cfg.getDescription());
                log.debug("\n\t#####Alert count= " + count);
            } else {
                // Do nothing
            }
            return count;
        }
    
        public <T> Long findCountByCriteria(EntityManager em, CriteriaQuery<T> cqEntity, CriteriaBuilder qb) {
            CriteriaBuilder builder = qb;
            CriteriaQuery<Long> cqCount = builder.createQuery(Long.class);
            Root<?> entityRoot = cqCount.from(cqEntity.getResultType());
            cqCount.select(builder.count(entityRoot));
            cqCount.where(cqEntity.getRestriction());
            return em.createQuery(cqCount).getSingleResult();
        }
    

    日志:我希望generateAlias0用于所有where子句属性而不是generatedAlias1。

    select count(*) from abc.domain.ReqStatCumulative as **generatedAlias0** where ( **generatedAlias1**.processTimeSeconds>5L ) and ( **generatedAlias1**.processTimeSeconds<200L )
    
    10:48:57.169 [main] DEBUG o.h.h.i.ast.QueryTranslatorImpl - parse() - HQL: select count(*) from abc.domain.ReqStatCumulative as generatedAlias0 where ( generatedAlias1.processTimeSeconds>5L ) and ( generatedAlias1.processTimeSeconds<200L )
    10:48:57.169 [main] DEBUG o.h.h.i.ast.QueryTranslatorImpl - --- HQL AST ---
     \-[QUERY] Node: 'query'
        +-[SELECT_FROM] Node: 'SELECT_FROM'
        |  +-[FROM] Node: 'from'
        |  |  \-[RANGE] Node: 'RANGE'
        |  |     +-[DOT] Node: '.'
        |  |     |  +-[DOT] Node: '.'
        |  |     |  |  +-[IDENT] Node: 'abc'
        |  |     |  |  \-[IDENT] Node: 'domain'
        |  |     |  \-[IDENT] Node: 'ReqStatCumulative'
        |  |     \-[ALIAS] Node: '**generatedAlias0**'
        |  \-[SELECT] Node: 'select'
        |     \-[COUNT] Node: 'count'
        |        \-[ROW_STAR] Node: '*'
        \-[WHERE] Node: 'where'
           \-[AND] Node: 'and'
              +-[GT] Node: '>'
              |  +-[DOT] Node: '.'
              |  |  +-[IDENT] Node: '**generatedAlias1**'
              |  |  \-[IDENT] Node: 'processTimeSeconds'
              |  \-[NUM_LONG] Node: '5L'
              \-[LT] Node: '<'
                 +-[DOT] Node: '.'
                 |  +-[IDENT] Node: '**generatedAlias1**'
                 |  \-[IDENT] Node: 'processTimeSeconds'
                 \-[NUM_LONG] Node: '200L'
    
    10:48:57.169 [main] DEBUG o.h.hql.internal.ast.ErrorCounter - throwQueryException() : no errors
    10:48:57.169 [main] DEBUG o.h.h.i.antlr.HqlSqlBaseWalker - select << begin [level=1, statement=select]
    10:48:57.169 [main] DEBUG o.h.h.internal.ast.tree.FromElement - FromClause{level=1} : erf.domain.ReqStatCumulative (generatedAlias0) -> reqstatcum0_
    10:48:57.169 [main] ERROR o.h.hql.internal.ast.ErrorCounter -  Invalid path: 'generatedAlias1.processTimeSeconds'
    10:48:57.215 [main] ERROR o.h.hql.internal.ast.ErrorCounter -  Invalid path: 'generatedAlias1.processTimeSeconds'
    org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'generatedAlias1.processTimeSeconds'
    

3 个答案:

答案 0 :(得分:13)

您的代码失败是因为您对count和where子句使用了不同的Root实例:第一个(按照定义顺序)生成generatedAlias1别名,另一个生成generatedAlias0 }。您需要重构代码才能在两个地方使用相同的Root实例:

CriteriaQuery<Long> cqCount = builder.createQuery(Long.class);
Root<ReqStatCumulative> entityRoot = cqCount.from(cqEntity.getResultType());
cqCount.select(builder.count(entityRoot));
Path<Long> processTimeSeconds = entityRoot.get("processTimeSeconds");
cqCount.where(qb.and(qb.greaterThan(processTimeSeconds, (long) cfg.getProcessTimeExceedsSec()),//
                qb.lessThan(processTimeSeconds, (long) cfg.getProcessTimeExceedsSec() + 100))//
                );//    
return em.createQuery(cqCount).getSingleResult();

答案 1 :(得分:1)

我遇到了同样的问题,我解决了:

CriteriaQuery<Long> countCriteria = cb.createQuery(Long.class);
Root<EntityA> countRoot = countCriteria.from(cq.getResultType());
Set<Join<EntityA, ?>> joins = originalEntityRoot.getJoins();
for (Join<EntityA, ?> join :  joins) {
    countRoot.join(join.getAttribute().getName());
}
countCriteria.select(cb.count(countRoot));
if(finalPredicate != null)
    countCriteria.where(finalPredicate);

TypedQuery<Long> queryCount = entityManager.createQuery(countCriteria);
Long count = queryCount.getSingleResult();

其中

originalEntityRoot是我使用where子句进行查询的主根。

答案 2 :(得分:0)

基于OP的问题,我一直在寻找更通用的解决方案。 并仅留下基于@Hector的示例的通用解决方案:

public class CountQueryHelper<T> {

    final Class<T> typeParameterClass;

    public CountQueryHelper(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public CriteriaQuery<Long> getCountQuery(CriteriaQuery<T> originalQuery, EntityManager em) {
        CriteriaBuilder cb = em.getCriteriaBuilder();

        // create count query
        CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);


        // start copying root/joins/restrictions from  the original query

        // copy roots
        for (Root r : originalQuery.getRoots()) {
            Root root = countQuery.from(r.getModel());
            root.alias(r.getAlias());
        }

        // copy joins
        for (Root r : originalQuery.getRoots()) {
            Set<Join<T, ?>> joins = r.getJoins();
            for (Join<T, ?> join : joins) {
                for (Root countRoot : countQuery.getRoots()) {
                    try {
                        Join joinOnCount = countRoot.join(join.getAttribute().getName());
                        joinRecursive(joinOnCount, join);
                    } catch (IllegalArgumentException e) {
                        // attribute does not exist on this root
                    }
                }
            }
        }

        countQuery.select(cb.count(countQuery.from(this.typeParameterClass)));

        //  copy restrictions
        if (originalQuery.getRestriction() != null) {
            countQuery.where(originalQuery.getRestriction());
        }

        return countQuery;
    }

    private void joinRecursive(Join countJoins, Join<T, ?> originalJoin) {
        for(Join original : originalJoin.getJoins()) {
            Join<Object, Object> childJoin = countJoins.join(original.getAttribute().getName());
            joinRecursive(childJoin, original);
        }
    }
}