Use spring boot after jpa + + tab grouping, count the number of pages wrong solution

1. Case reproduce the error

First, look at what bloggers want to do: Bloggers group would like a table, and the entries grouped paging

Page<Specification> page = repository.findAll(MatrixModel.createSpecification(model),
 MatrixModel.createSort(pageNum, pageSize, model.getSort()));

ok, finalAll method repository is mainly used, given that I was identified java OOP advocates, so rarely write sql, then I have a method which MatrixModel.createSpecification (model), in fact, created a bunch of spec, behind createSort do not see that the object is created pageAble, in line with the needs of findAll parameter method.

 

In the case of the landlord to confirm parameters without any problems, we found such a phenomenon:


This is the first page of data, we found that there are as many 1662 Elements After grouping, which is obviously wrong.

Then, when I extend the number of entries to a single page after 555 (as to why this number, see the back will know)



 OK When the page number of entries is 554, the total number of entries is 554, equivalent to over take me a target after all packets.

So in any case calls are no problem, why the total deviation calculation will happen? Here is the analysis process.

2. Analysis sql

Bloggers when page data is no problem, so the landlord wanted to see the focus of JPA count statement. I follow the above sequence and reproduce the printed hql

In the following order

2.1 The number of single-page 10

Hibernate: select specificat0_.id as id1_27_, specificat0_.create_time as create_t2_27_, specificat0_.update_time as update_t3_27_, specificat0_.head_img as head_img4_27_, specificat0_.intro as intro5_27_, specificat0_.name as name6_27_, specificat0_.specifications_id as specific7_27_, specificat0_.specifications_name as specific8_27_, specificat0_.status as status9_27_, specificat0_.weight as weight10_27_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id order by specificat0_.weight desc limit ?

 

Hibernate: select count(specificat0_.id) as col_0_0_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id

 

2.2 单页数目为555(刚好大于分组后所有元素的总和)

Hibernate: select specificat0_.id as id1_27_, specificat0_.create_time as create_t2_27_, specificat0_.update_time as update_t3_27_, specificat0_.head_img as head_img4_27_, specificat0_.intro as intro5_27_, specificat0_.name as name6_27_, specificat0_.specifications_id as specific7_27_, specificat0_.specifications_name as specific8_27_, specificat0_.status as status9_27_, specificat0_.weight as weight10_27_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id order by specificat0_.weight desc limit ?

 

2.2 比较结果

敲他大爷,为什么第一种结果回去多查询一次Hibernate: select count(specificat0_.id) as col_0_0_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id

 

而且这样的查询是sql中典型的group + count(*)的错误查询



 

如图所示,这样会查询出每个group之后的条目数。而我们需要的是总的group数目

所以正确查询应该嵌套查询:



 如图所示554 才应该是group之后的条目数。

所以我有疑问:难道TM的SPRING JPA 没有对group的判定?(敲他吗,还真没有)

 

3.不说了,看源码

先去找到findAll方法的实现

 继续前进



 

ok,来到spring JPA实现,可以看到那句return是一个重要的action,已知我们的pageale不为null,那么继续

进入方法readPage


 

getPage一定干了一些坏事,进去瞅瞅(还有个匿名内部类的实现哟,等会比较重要)



 仔细检查了逻辑,没有任何问题(主要是对查询出来的当页数量和设定的页面数量做比较,这是常规操作,spring也不会犯这样的问题),最后的猫腻在哪里呢?看到那个get方法没有,结果前面的情况二,总数的计算在这里有问题---》这里回到上面那个匿名内部内的实现,total是采用的:SimpleJpaRepository.executeCountQuery(SimpleJpaRepository.this.getCountQuery(spec, domainClass)).longValue();

 

咋们去看看这个executeCountQuery方法:

 


 

破案了,曹,totals是我们需要的总条目数554,但是spring把所有的数目累加了,相当于分组之前的总数了,不知道这算不算issue,但是目前是不符合我们的要求的。

 

4.解决方案

我的构想是能拿到那个条目数,并且能完美使用specRepo(OOP),所以不能使用sql来实现。

 

package com.swb.dreamweaver.dao;


import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.Collections;
import java.util.List;

/**
 * 特殊情况下数量统计异常处理(分页+分组,切仅用于此种情况)
 * 此类将返回正确的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;
        }
    }
}

写了个抽象类,然后仿写了源码。直接使用使用list.size()可以获取到我想要的分组后的group条目数(开心)。

 

 

最后附上使用的代码:

1.继承上面的抽象类

public class SpecificationService extends JPASpecialCountErrorHandler

2.注入em

@PersistenceContext
private EntityManager entityManager;


3.重写了getEm方法

@Override
protected EntityManager getEm() {
    return entityManager;
}


 4.简单的group判断及对pageView的重写

@Override
public <S extends EntityCURD> Code.ViewPage<S> gets(MatrixModel model, Integer pageNum, Integer pageSize) {
    Page<Specification> page = repository.findAll(MatrixModel.createSpecification(model), MatrixModel.createSort(pageNum, pageSize, model.getSort()));
//JPA ISSUE -- 分组+分页,总数显示不正确
if (model.getGroup() != null && model.getGroup().size() != 0) {
        return (Code.ViewPage<S>) code.JpaPageToViewPage(getPage(page.getContent(), MatrixModel.createSort(pageNum, pageSize, model.getSort()), getCount(MatrixModel.createSpecification(model), Specification.class)));
}
    return (Code.ViewPage<S>) code.JpaPageToViewPage(page);
}

 

OK 结果确认:

"last": false, "totalElements": 554, "totalPages": 51, "number": 0, "size": 11, "first": true, "numberOfElements": 11

没有问题

 

 

 

Guess you like

Origin liaoke0123.iteye.com/blog/2442188