如何在Spring Boot JPA分页场景下获取分组总数量?
优化分组分页查询的总数量统计方案
你当前通过两个重复的Criteria查询实现分组分页和总分组数统计,存在代码冗余和效率问题,可从复用查询逻辑和优化统计方式两方面改进:
1. 优化总分组数的统计方式
你当前的统计查询是先分组再统计结果集大小,需要数据库返回所有分组数据后再计数,效率较低。改用countDistinct直接统计唯一分组值的数量,避免执行完整的分组查询:
final CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); final CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class); final Root<Topic> countRoot = countQuery.from(Topic.class); var countJoin = countRoot.join(Topic_.room, JoinType.LEFT); // 复用同一个组合Specification Specification<Topic> combinedSpecification = filterSpec.and(keysSpec); Predicate countPredicate = combinedSpecification.toPredicate(countRoot, countQuery, criteriaBuilder); countQuery.where(countPredicate); // 直接统计distinct的Room关联实体数量,即总分组数 countQuery.select(criteriaBuilder.countDistinct(countJoin)); // 直接获取单一结果,无需处理结果集大小 Long totalCount = em.createQuery(countQuery).getSingleResult();
2. 复用公共查询构建逻辑
为避免主查询和统计查询重复创建Root、Join、Predicate,可抽离公共逻辑,复用相同的构建结果:
// 1. 构建公共查询组件 final CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); Specification<Topic> combinedSpecification = filterSpec.and(keysSpec); // 主查询部分 final CriteriaQuery<Tuple> mainQuery = criteriaBuilder.createQuery(Tuple.class); final Root<Topic> mainRoot = mainQuery.from(Topic.class); var mainJoin = mainRoot.join(Topic_.room, JoinType.LEFT); Predicate mainPredicate = combinedSpecification.toPredicate(mainRoot, mainQuery, criteriaBuilder); mainQuery.multiselect(mainJoin) .groupBy(mainJoin) .where(mainPredicate); var startAndSize = getStartAndSize(paginationParameter); final List<Tuple> mainQueryResult = em.createQuery(mainQuery) .setFirstResult(startAndSize.getFirst()) .setMaxResults(startAndSize.getSecond()) .getResultList(); // 统计查询部分(复用Specification,无需重复构建Join逻辑) final CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class); final Root<Topic> countRoot = countQuery.from(Topic.class); var countJoin = countRoot.join(Topic_.room, JoinType.LEFT); Predicate countPredicate = combinedSpecification.toPredicate(countRoot, countQuery, criteriaBuilder); countQuery.where(countPredicate) .select(criteriaBuilder.countDistinct(countJoin)); Long totalCount = em.createQuery(countQuery).getSingleResult();
若想进一步简化,可封装工具方法负责创建Root、Join和Predicate,供两个查询调用,彻底消除代码重复。
关键说明
countDistinct让数据库直接计算分组总数,无需返回所有分组数据,比分组后统计结果集大小更高效。- 复用
Specification和Join逻辑,避免重复编写相同的查询条件构建代码,提升可维护性。
内容的提问来源于stack exchange,提问作者Eoban
相关产品推荐
相关产品推荐

