-
-
Save tcollins/0ebd1dfa78028ecdef0b to your computer and use it in GitHub Desktop.
| If you use the findAll(Specification, Pageable) method, a count query is first executed and then the | |
| data query is executed if the count returns a value greater than the offset. | |
| For what I was doing I did not need pageable, but simply wanted to limit my results. This is easy | |
| to do with static named queries and methodNameMagicGoodness queries, but from my research (googling | |
| for a few hours) I couldn't find a way to do it with dynamic criteria queries using Specifications. | |
| During my search I found two things that helped me to figure out how to just do it myself. | |
| 1.) A stackoverflow question. | |
| How to disable count when Specification and Pageable are used together? | |
| http://stackoverflow.com/questions/26738199/how-to-disable-count-when-specification-and-pageable-are-used-together | |
| (where I will add a link to this gist) | |
| 2.) Spring documentation - Adding custom behavior to all repositories | |
| http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-behaviour-for-all-repositories | |
| I followed the Spring documentation pretty closely and got this all working pretty quickly without | |
| any real problems. |
| @NoRepositoryBean | |
| public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { | |
| List<T> findAll(Specification<T> spec, int offset, int maxResults, Sort sort); | |
| List<T> findAll(Specification<T> spec, int offset, int maxResults); | |
| } |
| public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> { | |
| @SuppressWarnings("rawtypes") | |
| @Override | |
| protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { | |
| return new BaseRepositoryFactory(entityManager); | |
| } | |
| private static class BaseRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory { | |
| private final EntityManager em; | |
| public BaseRepositoryFactory(EntityManager em) { | |
| super(em); | |
| this.em = em; | |
| } | |
| @SuppressWarnings({ "unchecked", "rawtypes", "hiding" }) | |
| protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryMetadata metadata, EntityManager entityManager) { | |
| SimpleJpaRepository<?, ?> repo = new BaseRepositoryImpl(metadata.getDomainType(), entityManager); | |
| return repo; | |
| } | |
| protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { | |
| return BaseRepositoryImpl.class; | |
| } | |
| } | |
| } |
| public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> { | |
| private final EntityManager entityManager; | |
| public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) { | |
| super(domainClass, entityManager); | |
| this.entityManager = entityManager; | |
| } | |
| public List<T> findAll(Specification<T> spec, int offset, int maxResults) { | |
| return findAll(spec, offset, maxResults, null); | |
| } | |
| public List<T> findAll(Specification<T> spec, int offset, int maxResults, Sort sort) { | |
| TypedQuery<T> query = getQuery(spec, sort); | |
| if (offset < 0) { | |
| throw new IllegalArgumentException("Offset must not be less than zero!"); | |
| } | |
| if (maxResults < 1) { | |
| throw new IllegalArgumentException("Max results must not be less than one!"); | |
| } | |
| query.setFirstResult(offset); | |
| query.setMaxResults(maxResults); | |
| return query.getResultList(); | |
| } | |
| } |
| @SpringBootApplication | |
| @EnableJpaRepositories(repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class) | |
| public class MySpringBootApplication { | |
| public static void main(String[] args) { | |
| SpringApplication app = new SpringApplication(MySpringBootApplication.class); | |
| app.run(args); | |
| } | |
| } |
| // This is just to show an example of a repo | |
| public interface UserRepository extends BaseRepository<User, Long>, JpaSpecificationExecutor<User> { | |
| } |
@josergdev's suggestion is a perfect solution, compact and relying on Spring standard code.
Beware, though, the gist posted above includes an off-by-one error, due to ScrollPosition excluding the bound and Pageable including it (e.g. the initial offset is -1 for ScrollPosition and 0 for Pageable).
Follow @josergdev's link to find the corrected version : https://gist.github.com/josergdev/06c82891a719eca4834410339885ad23
Thank you @Draynam!
The error was caused by a phantom change in Spring: https://github.com/spring-projects/spring-data-commons/wiki/Spring-Data-2024.0-Release-Notes#behavior-changes-in-offset-based-scrolling
Ha, nice way to create bugs for all users of that feature ! I hope everyone read that changelog closely, because I've seen firsthand those last few days how hard to spot an off-by-one error on pagination can be...
Sorry for casting doubt on your code when it was actually correct at the time of its writing :)
Thank you @josergdev , a very good suggestion!