问题描述
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));