diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java index fdc3bb28a..e19e7bf0e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java @@ -132,7 +132,7 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp @Override public R oneValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .limit(2) // Never need more than 2 values .fetch(); @@ -146,7 +146,7 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp @Override public R firstValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .limit(1) // Never need more than 1 value .fetch(); @@ -155,7 +155,11 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp @Override public List all() { - return convert(createSortedAndProjectedQuery().fetch()); + return all(this.sort); + } + + private List all(Sort sort) { + return convert(createSortedAndProjectedQuery(sort).fetch()); } @Override @@ -168,13 +172,13 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp @Override public Page page(Pageable pageable) { - return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable); + return pageable.isUnpaged() ? new PageImpl<>(all(pageable.getSortOr(this.sort))) : readPage(pageable); } @Override public Stream stream() { - return createSortedAndProjectedQuery() // + return createSortedAndProjectedQuery(this.sort) // .stream() // .map(getConversionFunction()); } @@ -189,7 +193,7 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp return existsOperation.apply(predicate); } - private AbstractJPAQuery createSortedAndProjectedQuery() { + private AbstractJPAQuery createSortedAndProjectedQuery(Sort sort) { AbstractJPAQuery query = finder.apply(sort); @@ -206,6 +210,7 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp private Page readPage(Pageable pageable) { + Sort sort = pageable.getSortOr(this.sort); AbstractJPAQuery query = pagedFinder.apply(sort, pageable); if (!properties.isEmpty()) { @@ -214,7 +219,8 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp List paginatedResults = convert(query.fetch()); - return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(predicate)); + return PageableExecutionUtils.getPage(paginatedResults, withSort(pageable, sort), + () -> countOperation.apply(predicate)); } private List convert(List resultList) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java index 534cd6133..4c9f2b501 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java @@ -89,8 +89,14 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport Assert.notNull(sort, "Sort must not be null"); - return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, countOperation, existsOperation, entityManager, projectionFactory); + Sort sort1 = this.sort.and(sort); + + if (this.sort == sort1) { + return this; + } + + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort1, limit, properties, finder, + scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -98,8 +104,8 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport Assert.isTrue(limit >= 0, "Limit must not be negative"); - return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, - properties, finder, scroll, countOperation, existsOperation, entityManager, projectionFactory); + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, + scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -124,7 +130,7 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport @Override public R oneValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .setMaxResults(2) // Never need more than 2 values .getResultList(); @@ -138,7 +144,7 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport @Override public R firstValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .setMaxResults(1) // Never need more than 1 value .getResultList(); @@ -147,7 +153,11 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport @Override public List all() { - return convert(createSortedAndProjectedQuery().getResultList()); + return all(this.sort); + } + + private List all(Sort sort) { + return convert(createSortedAndProjectedQuery(sort).getResultList()); } @Override @@ -160,13 +170,13 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport @Override public Page page(Pageable pageable) { - return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable); + return pageable.isUnpaged() ? new PageImpl<>(all(pageable.getSortOr(this.sort))) : readPage(pageable); } @Override public Stream stream() { - return createSortedAndProjectedQuery() // + return createSortedAndProjectedQuery(this.sort) // .getResultStream() // .map(getConversionFunction()); } @@ -181,7 +191,7 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport return existsOperation.apply(spec); } - private TypedQuery createSortedAndProjectedQuery() { + private TypedQuery createSortedAndProjectedQuery(Sort sort) { TypedQuery query = finder.apply(sort); @@ -198,7 +208,8 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport private Page readPage(Pageable pageable) { - TypedQuery pagedQuery = createSortedAndProjectedQuery(); + Sort sort = pageable.getSortOr(this.sort); + TypedQuery pagedQuery = createSortedAndProjectedQuery(sort); if (pageable.isPaged()) { pagedQuery.setFirstResult(PageableUtils.getOffsetAsInteger(pageable)); @@ -207,7 +218,7 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport List paginatedResults = convert(pagedQuery.getResultList()); - return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(spec)); + return PageableExecutionUtils.getPage(paginatedResults, withSort(pageable, sort), () -> countOperation.apply(spec)); } private List convert(List resultList) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java index 253610e78..27fd7d6b6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java @@ -24,6 +24,8 @@ import java.util.Set; import java.util.function.Function; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.projection.ProjectionFactory; @@ -49,7 +51,7 @@ abstract class FluentQuerySupport { protected final ProjectionFactory projectionFactory; FluentQuerySupport(Class resultType, Sort sort, int limit, @Nullable Collection properties, - Class entityType, ProjectionFactory projectionFactory) { + Class entityType, ProjectionFactory projectionFactory) { this.resultType = resultType; this.sort = sort; @@ -87,6 +89,15 @@ abstract class FluentQuerySupport { return o -> DefaultConversionService.getSharedInstance().convert(o, targetType); } + Pageable withSort(Pageable pageable, Sort sort) { + + if (pageable instanceof PageRequest pr && pageable.getSort() != sort) { + return pr.withSort(sort); + } + + return pageable; + } + interface ScrollQueryFactory { Query createQuery(Sort sort, ScrollPosition scrollPosition); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index c433415b9..016300697 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -2434,6 +2434,24 @@ class UserRepositoryTests { assertThat(page1.getContent()).containsExactly(fourthUser); } + @Test // GH-3762 + void findByFluentExamplePageSortOverride() { + + flushTestUsers(); + + User prototype = new User(); + prototype.setFirstname("v"); + + Example userProbe = of(prototype, matching().withIgnorePaths("age", "createdAt", "active") + .withMatcher("firstname", GenericPropertyMatcher::contains)); + + Page page = repository.findBy(userProbe, // + q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2, Sort.by(DESC, "firstname")))); + + assertThat(page.getContent()).containsExactly(fourthUser, firstUser); + assertThat(repository.findAll(page.nextPageable())).containsExactly(secondUser, thirdUser); + } + @Test // GH-2294 void findByFluentExampleWithInterfaceBasedProjection() { @@ -2689,6 +2707,18 @@ class UserRepositoryTests { assertThat(page1.getContent()).containsExactly(fourthUser); } + @Test // GH-3762 + void findByFluentSpecificationSortOverridePage() { + + flushTestUsers(); + + Page page = repository.findBy(userHasFirstnameLike("v"), + q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2, Sort.by(DESC, "firstname")))); + + assertThat(page.getContent()).containsExactly(fourthUser, firstUser); + assertThat(repository.findAll(page.nextPageable())).containsExactly(secondUser, thirdUser); + } + @Test // GH-2274 void findByFluentSpecificationWithInterfaceBasedProjection() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java index ef8d1c6c8..87c469019 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java @@ -399,6 +399,19 @@ class QuerydslJpaPredicateExecutorUnitTests { assertThat(page1.getContent()).containsExactly(oliver); } + @Test // GH-3762 + void findByFluentPredicateSortOverridePage() { + + Predicate predicate = user.firstname.contains("v"); + + Page page = predicateExecutor.findBy(predicate, + q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 1, Sort.by(Direction.DESC, "firstname")))); + + assertThat(page.getContent()).containsOnly(oliver); + assertThat(predicateExecutor.findAll(predicate, page.nextPageable())).containsOnly(dave); + + } + @Test // GH-2294 void findByFluentPredicateWithInterfaceBasedProjection() {