jpa分组分页查询 返回总数错误解决

3 篇文章 0 订阅
1 篇文章 0 订阅

问题描述

jpa分组分页查询之后,返回page分页数据错误解决方案`

例如:

 Specification<User> specification = new Specification<User>() {
    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        List<Predicate> predicates = new ArrayList<Predicate>() {
            {
                String id = serachVM.getId();
                criteriaQuery.groupBy(root.get("id"));//避免同个供方出现多次
            }
        };
        return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
    }
};
Page<User> page= Repository.findAll(specification , pageable);


原因分析:

此时,分组之后的page总数量为分组的后的每一个count(*)的累加结果,得到的结果比实际数量多,导致前端显示出现空页面,而我们想要的是每一组的数据,总数应该是分了多少组才对,不符合我们的要求。
那么问题出在哪里呢?我们看到JPA的findAll办法

public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {
     TypedQuery<T> query = this.getQuery(spec, pageable);
     return (Page)(isUnpaged(pageable) ? new PageImpl(query.getResultList()) : this.readPage(query, this.getDomainClass(), pageable, spec));
 }

已知为分页查询,所以来到readPage

protected <S extends T> Page<S> readPage(TypedQuery<S> query, Class<S> domainClass, Pageable pageable, @Nullable Specification<S> spec) {
    if (pageable.isPaged()) {
        query.setFirstResult((int)pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
    }

    return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> {
        return executeCountQuery(this.getCountQuery(spec, domainClass));
    });
}

前面是一些简单的赋值,再看到executeCountQuery这个方法(重点)

private static long executeCountQuery(TypedQuery<Long> query) {
    Assert.notNull(query, "TypedQuery must not be null!");
    List<Long> totals = query.getResultList();
    long total = 0L;

    Long element;
    for(Iterator var4 = totals.iterator(); var4.hasNext(); total += element == null ? 0L : element) {
        element = (Long)var4.next();
    }

    return total;
}

我们可以看到 query.getResultList().size()这个才是我们的数据结果,但是jpa把他进行进行迭代累加导致数据总数是分组前的总数。所以我们只需要对此方法进行重写即可。


解决方案:

新建一个JPASpecialCountErrorHandler抽象类

/**
 * 特殊情况下数量统计异常处理(分页+分组,切仅用于此种情况)
 * 此类将返回正确的count数量
 * Author: Liao Ke
 */
public abstract class JPASpecialCountErrorHandler {

    /**
     * 获取EntityManager实现
     * @return
     */
    protected abstract EntityManager getEm();


    /**
     * 获取分页数据
     * @param content 当前页面条目数
     * @param pageable 分页信息
     * @param count 真实总数
     * @return
     */
    protected <S> PageImpl getPage(List<S> content, Pageable pageable, int count){
        return new PageImpl(content, pageable, (long)count);
    }

    /**
     * 获取正确的count计数
     * @param spec
     * @param domainClass
     * @param <S>
     * @return
     */
    protected <S> int getCount(org.springframework.data.jpa.domain.Specification<S> spec, Class<S> domainClass) {
        return getCountQuery(spec, domainClass).getResultList().size();
    }

    /**
     * 来自源码,获取TypedQuery
     * org\springframework\data\jpa\repository\support\SimpleJpaRepository.class
     * @param spec
     * @param domainClass
     * @param <S>
     * @return
     */
    protected <S> TypedQuery<Long> getCountQuery(org.springframework.data.jpa.domain.Specification<S> spec, Class<S> domainClass) {
        CriteriaBuilder builder = getEm().getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);
        Root<S> root = this.applySpecificationToCriteria(spec, domainClass, (CriteriaQuery<S>) query);
        if (query.isDistinct()) {
            query.select(builder.countDistinct(root));
        } else {
            query.select(builder.count(root));
        }
        query.orderBy(Collections.emptyList());
        return getEm().createQuery(query);
    }

    /**
     * 来自源码,翻译spec
     * org\springframework\data\jpa\repository\support\SimpleJpaRepository.class
     * @param spec
     * @param domainClass
     * @param query
     * @param <S>
     * @return
     */
    private <S> Root<S> applySpecificationToCriteria(org.springframework.data.jpa.domain.Specification<S> spec, Class<S> domainClass, CriteriaQuery<S> query) {
        Assert.notNull(domainClass, "Domain class must not be null!");
        Assert.notNull(query, "CriteriaQuery must not be null!");
        Root<S> root = query.from(domainClass);
        if (spec == null) {
            return root;
        } else {
            CriteriaBuilder builder = getEm().getCriteriaBuilder();
            Predicate predicate = spec.toPredicate(root, query, builder);
            if (predicate != null) {
                query.where(predicate);
            }
            return root;
        }
    }
}

将处理改查询的service实现类继承JPASpecialCountErrorHandler,并重写getEm,注入EntityManager ,例如:

public class userServiceImpl extends JPASpecialCountErrorHandler implements userService@PersistenceContext
    private EntityManager entityManager;
    @Override
    protected EntityManager getEm() {
        return entityManager;
    }

这个使用,让我们对数据进行查询的时候,对返回的page进行二次封装即可得到正常数量(注意此方法仅用于对数据的分组查询后处理),该类编写在继承JPASpecialCountErrorHandler 的impl类,如上面所诉的userServiceImpl 。

Specification<User> specification = new Specification<User>() {
   @Override
   public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
       List<Predicate> predicates = new ArrayList<Predicate>() {
           {
               String id = serachVM.getId();
               criteriaQuery.groupBy(root.get("id"));//避免同个供方出现多次
           }
       };
       return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
   }
};
Page<User> page= Repository.findAll(specification , pageable);
//pageable为请求的分页参数 specification为jpa构建查询条件返回的实体
//page1中的数据就是最终分组后的正常结果
Page<User>page1=getPage(page.getContent(),pageable,getCount(specification ,User.class));
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值