【问题标题】:How to count a group by result with JPA and CriteriaBuilder?如何使用 JPA 和 CriteriaBuilder 按结果计算组?
【发布时间】:2012-06-23 15:17:24
【问题描述】:

我认为这几乎是不可能的或非常棘手的。我正在使用 CriteriaBuilder、JPA 2.0、Hibernate 和 MariaDB,并希望使用 CriteriaBuilder 构建以下查询:

SELECT COUNT(*) FROM
   (SELECT DISTINCT(SomeColumn) // I think this is not possible?
    FROM MyTable
    WHERE ... COMPLEX CLAUSE ...
    GROUP BY SomeColumn) MyTable

我的问题:可能吗?如果,如何?

感谢您对这个问题的思考!

标记

【问题讨论】:

  • 使用criteriabuilder,你的意思是Criteria API吗? Criteria API 支持复杂的 where 语句、分组依据、计数、单个字段和子查询的不同。我见过的最大限制是您无法执行自定义联接。
  • 标准 API,是的。但似乎没有 .from( Query ) 还是我错过了什么?
  • 您使用像 cb.select(subquery) 这样的子查询。无论如何,我认为这里不需要子查询,请参见下面的示例。

标签: jpa-2.0


【解决方案1】:

我有一个类似的问题,按元素计算不同的组,并偶然发现了这个线程,基于我想出了以下的现有答案

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Root<Jpa1> countRoot = countQuery.from(Jpa1.class);

Subquery<Long> query = countQuery.subquery(Long.class);
Root<Jpa1> root = query.from(Jpa1.class);

applyFilter(query, root);
query.groupBy(root.get("groupBy_column1"), root.get("groupBy_column2"));
query.select(builder.min(root.get("id_column")));

countQuery.where(builder.in(countRoot.get("id_column")).value(query));
return countQuery.select(builder.count(countRoot.get("id_column")));

【讨论】:

    【解决方案2】:

    此示例假设您正在使用元模型生成。

    CriteriaQuery<Long> cq = cb.createQuery(Long.class);
    Subquery<SomeColumnType> subcq = cq.subquery(SomeColumnType.class);
    Root<MyTable> from = subcq.from(MyTable.class);
    subcq.select(from.get(MyTable_.someColumn));
    subcq.where(** complex where statements **);
    subcq.groupBy(from.get(MyTable_.someColumn));
    cq.select(cb.count(subcq));
    

    【讨论】:

    • 不,实际上这两个查询并不等价。我的查询计数是组数(单个结果),因为您的查询将给出每个组的计数列表(计数列表)。
    • 啊,那是真的。将不得不考虑一下这个
    • 删除分组依据,count(distinct someColumn) 将按预期工作。 Distinct 是否适合您的分组工作。
    • 抱歉,这样不行。 Count(distinct) 和 group by 在内部查询中是“等效的”,但外部和内部查询的计数不是。感谢您解决这个问题,但如果没有 View 或 NativeQuery,这是行不通的(至少我认为)。
    • 您真正感兴趣的数据是什么?我以为是唯一 SomeColumn 值的数量?
    【解决方案3】:

    如果你有 Spring Boot 并且你想要做同样的事情,有一个更好的解决方法,它只能通过使用原生的 SQL_CALC_FOUND_ROWSFOUND_ROWS 用于 MySql 5/Maria(它不适用于 MySQL 8);

    这种集成的主要思想是 Spring-Data 的灵活性以及覆盖默认 JpaRepositoryFactoryBean 的能力,如下所示

    第 1 步 - 创建自定义 CustomJpaRepositoryFactoryBean

    /**
    * 
    * @author ehakawati
    *
    * @param <R>
    * @param <T>
    * @param <I>
    */
    public class CustomJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
            extends JpaRepositoryFactoryBean<R, T, I> {
    
        public CustomJpaRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
            super(repositoryInterface);
        }
    
        @Override
        protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
            RepositoryFactorySupport repositoryFactorySupport = super.createRepositoryFactory(entityManager);
            repositoryFactorySupport.setRepositoryBaseClass(CustomJpaRepository.class);
            return repositoryFactorySupport;
        }
    
    }
    

    第 2 步 - 创建自定义 CustomJpaRepository

     /**
     * 
     * @author ehakawati
     *
     * @param <T>
     * @param <ID>
     */
    public class CustomJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
            implements Repository<T, ID> {
    
        private final EntityManager em;
    
        /**
         * 
         * @param entityInformation
         * @param entityManager
         */
        public CustomJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
            super(entityInformation, entityManager);
            this.em = entityManager;
    
        }
    
        /**
         * 
         * @param domainClass
         * @param entityManager
         */
        public CustomJpaRepository(Class<T> domainClass, EntityManager entityManager) {
            super(domainClass, entityManager);
            this.em = entityManager;
        }
    
        /**
         * Reads the given {@link TypedQuery} into a {@link Page} applying the given
         * {@link Pageable} and {@link Specification}.
         *
         * @param query       must not be {@literal null}.
         * @param domainClass must not be {@literal null}.
         * @param spec        can be {@literal null}.
         * @param pageable    can be {@literal null}.
         * @return
         */
        protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable,
                @Nullable Specification<S> spec) {
    
            if (pageable.isUnpaged()) {
                return super.readPage(query, domainClass, pageable, spec);
            }
    
            query.setFirstResult((int) pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());
    
            return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> getNativeCount());
        }
    
        /**
         * 
         */
    
        protected long getNativeCount() {
    
            final Query query = em.createNativeQuery("SELECT FOUND_ROWS() as `count`");
    
            return ((BigInteger) query.getSingleResult()).longValue();
    
        }
    
    }
    

    步骤 #3 - PageableQueriesInterceptor:

     /**
     * 
     * @author ehakawati
     *
     * @param <T>
     * @param <ID>
     */
    public class PageableQueriesInterceptor extends EmptyInterceptor {
    
        private static final Pattern PATTERN = Pattern.compile(".*?limit \\?(, \\?)?$");
        private static final long serialVersionUID = 1L;
    
        @Override
        public String onPrepareStatement(String sql) {
    
            if (PATTERN.matcher(sql).find()) {
                sql = sql.replaceFirst("select", "select SQL_CALC_FOUND_ROWS ");
            }
    
            return sql;
        }
    }
    

    步骤#4 - 启用PageableQueriesInterceptor

    sprint.jpa.properties.hibernate.ejb.interceptor= com.****.****.******.betterpaging.PageableQueriesInterceptor
    

    享受

    【讨论】:

      【解决方案4】:

      CriteriaBuilder.countDistinct() 方法可以很好地计算 groupBy 结果。

      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Long> cq = cb.createQuery(Long.class);
      
      Root<MyEntity> root = cq.from(MyEntity);
              
      cq.select(cb.countDistinct(root("entityField")));
      cq.where(** your criteria **);
      
      var result = em.createQuery(cq).getSingleResult();
      var m = Math.toIntExact(result);
      

      这类似于 SQL 查询

      SELECT COUNT(DISTINCT myEntity.entityField) FROM myEntity WHERE **blah** ;
      

      当然,countDistinct(如 COUNT(DISTINCT ...))可以根据需要列出多个列以进行分组。

      【讨论】:

        猜你喜欢
        • 2014-10-25
        • 2019-04-10
        • 1970-01-01
        • 2011-02-22
        • 2012-03-08
        • 2019-02-12
        • 1970-01-01
        • 1970-01-01
        • 2019-04-09
        相关资源
        最近更新 更多