Pagination without count query in Spring Data JPA

2 minute read

Hi, in this post I’m going to show you how to implement pagination without count query. By default, Spring Data JPA doesn’t provide pagination method without count query when one want to use Specification interface in methods. Yes, there are methods that return Slice, but there isn’t any method that returns Slice when we want to use Specification. In this post, we’re going to implement our own Repository.

If we take a look at SimpleJpaRepository’s readPage method:

 1protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable, @Nullable Specification<S> spec) {
 2    if (pageable.isPaged()) {
 3        query.setFirstResult((int)pageable.getOffset());
 4        query.setMaxResults(pageable.getPageSize());
 5    }
 6
 7    return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> {
 8        return executeCountQuery(this.getCountQuery(spec, domainClass));
 9    });
10}

At the end of the method, method executes the count query, and this can be costly for large datasets. Slice interface holds only previous and next page information. There is no total page count information in Slice as it should be.↳

First we need to create our own interface for custom repository:

1@NoRepositoryBean
2public interface ExtendedRepository<T, ID extends Serializable> extends JpaRepositoryImplementation<T, ID> {
3    Slice<T> findAllAsSlice(Specification<T> specification, Pageable pageable);
4}

We marked the interface as NoRepositoryBean which means that Spring Data JPA won’t try to provide the implementation of this interface.

After declaring the interface, we can implement the interface:

 1public class SimpleExtendedRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements ExtendedRepository<T, ID> {
 2
 3    public SimpleExtendedRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
 4        super(entityInformation, entityManager);
 5    }
 6
 7    @Override
 8    public Slice<T> findAllAsSlice(Specification<T> specification, Pageable pageable) {
 9        TypedQuery<T> query = getQuery(specification, pageable);
10        return pageable.isUnpaged() ? new SliceImpl<>(query.getResultList()) : readSlice(query, pageable, specification);
11    }
12
13    private Slice<T> readSlice(TypedQuery<T> query, Pageable pageable, Specification<T> specification){
14        if (pageable.isPaged()){
15            query.setFirstResult((int) pageable.getOffset());
16            query.setMaxResults(pageable.getPageSize() + 1); // We should get 1 more row to understand there is a next page or not
17        }
18        List<T> content = query.getResultList();
19        boolean hasNextPage = content.size() > pageable.getPageSize();
20        if (content.size() > pageable.getPageSize()){ // If the result set contains 1 more row than the desired page count, we normalize the result set
21            content = content.subList(0, pageable.getPageSize());
22        }
23        return new SliceImpl<>(content, pageable, hasNextPage);
24    }
25}

After concrete implementation, we need to tell the Spring Data JPA that it should use SimpleExtendedRepository class when creating implementations of Repository interfaces. To do that just simply add the repositoryBaseClass property to the @EnableJpaRepositories annotation:

1@SpringBootApplication
2@EnableJpaRepositories(repositoryBaseClass = SimpleExtendedRepository.class)
3public class Application {
4
5	public static void main(String[] args) {
6		SpringApplication.run(Application.class, args);
7	}
8}

And that’s it, now you can start to use our custom method by implementing ExtendedRepository interface like following:

1@Repository
2public interface EmployeeRepository extends ExtendedRepository<Employee, Integer> {
3
4}

This is the end of the post, hope you enjoyed! Please feel free to contact me if you have any suggestions, questions, concerns.