当规范和Pageable一起使用时如何禁用计数?

时间:2014-11-04 14:53:56

标签: spring spring-data-jpa spring-data

JpaSpecificationExecutor附带的方法不充分,没有一个能给我我想要的东西:

Page<T> findAll(Specification<T> spec, Pageable pageable)

List<T> findAll(Specification<T> spec)

List<T> findAll(Specification<T> spec, Sort sort)

第一种方法执行分页查询和计数查询。接下来的2个根本不执行分页。我需要的是以下之一:

Slice<T> findAll(Specification<T> spec, Pageable pageable)

List<T> findAll(Specification<T> spec, Pageable pageable)

通过不扩展JpaSpecificationExecutor,我能够执行两个查询,但计数查询也是如此。在我的情况下,必须避免计数查询,因为它非常昂贵。问题是如何?

3 个答案:

答案 0 :(得分:13)

查看SimpleJpaRepositoryfindAll(Specification, Pageable)readPage(TypedQuery, Pageable, Specification)方法。 Spring的实现似乎总是执行计数查询并在执行select查询之前检查startIndex是否在范围之外:

protected Page<T> readPage(TypedQuery<T> query, Pageable pageable, Specification<T> spec) {

    query.setFirstResult(pageable.getOffset());
    query.setMaxResults(pageable.getPageSize());

    Long total = QueryUtils.executeCountQuery(getCountQuery(spec));
    List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T> emptyList();

    return new PageImpl<T>(content, pageable, total);
}

我不相信这始终是最好的做法。例如,在我的用例中,我们很乐意预先执行计数查询而不是后续调用,因为我们知道新数据不足以保证计数更新并且计数查询非常执行起来很昂贵。

如果Spring Data可以提供标志或替代方法来禁用条件查询计数,那就太棒了,类似于simple find queries

与此同时,这是我的解决方案:

创建一个子类SimpleJpaRepository的内部类。覆盖readPage以禁用计数查询。创建DAO,使用@Repository注释它并实例化此内部类以传递正确的EntityManager。最后,无论在哪里&#34;不计数&#34;标准搜索适用:

@Repository
public class CriteriaNoCountDao {

    @PersistenceContext
    protected EntityManager em;

    public <T, ID extends Serializable> Page<T> findAll(Specification<T> spec, Pageable pageable, Class<T> clazz){
        SimpleJpaNoCountRepository<T, ID> noCountDao = new SimpleJpaNoCountRepository<T, ID>(clazz, em);
        return noCountDao.findAll(spec, pageable);
    }

    /**
     * Custom repository type that disable count query.
     */
    public static class SimpleJpaNoCountRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {

        public SimpleJpaNoCountRepository(Class<T> domainClass, EntityManager em) {
            super(domainClass, em);
        }

        /**
         * Override {@link SimpleJpaRepository#readPage(TypedQuery, Pageable, Specification)}
         */
        protected Page<T> readPage(TypedQuery<T> query, Pageable pageable, Specification<T> spec) {
            query.setFirstResult(pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());

            List<T> content = query.getResultList();

            return new PageImpl<T>(content, pageable, content.size());
        }
    }
}

答案 1 :(得分:1)

创建您的自定义基础存储库impl,如该链接所示: https://www.baeldung.com/spring-data-jpa-method-in-all-repositories

创建类似的方法:

 public List<T> findAllBy(Specification<T> aSpecification, Pageable aPageable) {
    TypedQuery<T> query = getQuery(aSpecification, aPageable);
    query.setFirstResult((int) aPageable.getOffset());
    query.setMaxResults(aPageable.getPageSize());
    return query.getResultList();
}

答案 2 :(得分:0)

存在一个针对此问题的简单解决方法。在不执行计数的情况下,可以使用Specification来获取数据。我之前有类似的情况,当时我需要根据一些条件从全局表中获取分页结果,但是直到org.springframework.data:spring-data-jpa:2.3.3.RELEASE之前,org.springframework.data.jpa.repository.JpaSpecificationExecutor类尚未提供对获取分页{给定Slice的{​​1}}。截至2020年9月,只有Specification返回类型如下所示。

Page<T>

Spring Data JPA门户中仍然存在一个用于此功能请求的open ticket

尽管如此,使用 public interface JpaSpecificationExecutor<T> { ... ... Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); ... ... } 类似于JpaQueryExecution.SlicedExecution#doExecute的逻辑,共享我在应用程序中为Slice<T>结果集构造实现的工作解决方案(一个简单的解决方法)。

Specification只是一种可恢复的org.springframework.data.jpa.domain.Specification<T>类型,

javax.persistence.criteria.Predicate

为了简单起见,我在 public interface Specification<T> { Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb); } 中直接使用了具有相等条件的谓词数组。

代码段

  • 存储库类:
javax.persistence.criteria.CriteriaBuilder
  • 存储库测试类:
    @Repository
    public class CommonRepository {

        @PersistenceContext
        private EntityManager entityManager;

        public <T> Slice<T> conditionalFindAll(Class<T> entityClass, Map<String, Object> conditionsToApply, Pageable pageable) {
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
            Root<T> entityRoot = criteriaQuery.from(entityClass);
            List<Predicate> predicates = new ArrayList<>();

            //applying condition using predicates(a vanilla flavor of Specification)
            conditionsToApply.entrySet().stream()
                    .filter(Objects::nonNull)
                    .forEach(entry ->
                            predicates.add(criteriaBuilder.equal(entityRoot.get(entry.getKey()),
                                    entry.getValue())));
            criteriaQuery.select(entityRoot)
                    .where(criteriaBuilder.and(predicates.toArray(new Predicate[0])));
            TypedQuery<T> query = entityManager.createQuery(criteriaQuery);

            //limit of the returning result
            int pageSize = pageable.getPageSize();
            //calculating offset from page-number and page-size
            int offset = pageable.getPageNumber() > 0 ? pageable.getPageNumber() * pageSize : 0;
        //https://github.com/spring-projects/spring-data-jpa/blob/48597dca246178c0d7e6952425004849d3fb02c0/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java#L156
        // always fetch additional one element and skip it based on the pageSize to findout whether next set of results present or not.
            query.setMaxResults(pageSize + 1);
            query.setFirstResult(offset);
            List<T> resultList = query.getResultList();
            boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
            return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
        }
    }