Skip to content

Commit 6f4adf5

Browse files
committed
Polishing.
Add SpecificationFluentQuery to include specification-related overloads. Also, add slice(…) terminal method to obtain a slice only without running a count query. See #3727
1 parent c6eff4f commit 6f4adf5

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import jakarta.persistence.criteria.CriteriaQuery;
2020
import jakarta.persistence.criteria.Root;
2121

22+
import java.util.Arrays;
23+
import java.util.Collection;
2224
import java.util.Arrays;
2325
import java.util.Collection;
2426
import java.util.List;
@@ -31,6 +33,7 @@
3133
import org.springframework.dao.InvalidDataAccessApiUsageException;
3234
import org.springframework.data.domain.Page;
3335
import org.springframework.data.domain.Pageable;
36+
import org.springframework.data.domain.Slice;
3437
import org.springframework.data.domain.Sort;
3538
import org.springframework.data.jpa.domain.DeleteSpecification;
3639
import org.springframework.data.jpa.domain.PredicateSpecification;
@@ -229,7 +232,7 @@ default long delete(PredicateSpecification<T> spec) {
229232
* @since 4.0
230233
*/
231234
default <S extends T, R> R findBy(PredicateSpecification<T> spec,
232-
Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
235+
Function<? super SpecificationFluentQuery<S>, R> queryFunction) {
233236
return findBy(Specification.where(spec), queryFunction);
234237
}
235238

@@ -275,6 +278,21 @@ default SpecificationFluentQuery<T> project(String... properties) {
275278
@Override
276279
SpecificationFluentQuery<T> project(Collection<String> properties);
277280

281+
/**
282+
* Get a page of matching elements for {@link Pageable} and provide a custom {@link Specification count
283+
* specification}.
284+
*
285+
* @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be
286+
* {@literal null}. The given {@link Pageable} will override any previously specified {@link Sort sort} if
287+
* the {@link Sort} object is not {@link Sort#isUnsorted()}. Any potentially specified {@link #limit(int)}
288+
* will be overridden by {@link Pageable#getPageSize()}.
289+
* @param countSpec specification used to count results.
290+
* @return
291+
*/
292+
default Page<T> page(Pageable pageable, PredicateSpecification<?> countSpec) {
293+
return page(pageable, Specification.where(countSpec));
294+
}
295+
278296
/**
279297
* Get a page of matching elements for {@link Pageable} and provide a custom {@link Specification count
280298
* specification}.

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,26 @@ private TypedQuery<S> createSortedAndProjectedQuery(Sort sort) {
226226

227227
private Slice<R> readSlice(Pageable pageable) {
228228

229+
TypedQuery<S> pagedQuery = createSortedAndProjectedQuery();
230+
231+
if (pageable.isPaged()) {
232+
pagedQuery.setFirstResult(PageableUtils.getOffsetAsInteger(pageable));
233+
pagedQuery.setMaxResults(pageable.getPageSize() + 1);
234+
}
235+
236+
List<S> resultList = pagedQuery.getResultList();
237+
boolean hasNext = resultList.size() > pageable.getPageSize();
238+
if (hasNext) {
239+
resultList = resultList.subList(0, pageable.getPageSize());
240+
}
241+
242+
List<R> slice = convert(resultList);
243+
244+
return new SliceImpl<>(slice, pageable, hasNext);
245+
}
246+
247+
private Slice<R> readSlice(Pageable pageable, @Nullable Specification<S> countSpec) {
248+
229249
TypedQuery<S> pagedQuery = createSortedAndProjectedQuery(pageable.getSort());
230250

231251
if (pageable.isPaged()) {

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2800,6 +2800,44 @@ void findByFluentSpecificationPageCustomCountSpec() {
28002800
assertThat(page0.getTotalElements()).isEqualTo(3L);
28012801
}
28022802

2803+
@Test // GH-2274
2804+
void findByFluentSpecificationSlice() {
2805+
2806+
flushTestUsers();
2807+
2808+
Slice<User> slice = repository.findBy(userHasFirstnameLike("v"),
2809+
q -> q.sortBy(Sort.by("firstname")).slice(PageRequest.of(0, 2)));
2810+
2811+
assertThat(slice).isNotInstanceOf(Page.class);
2812+
assertThat(slice.getContent()).containsExactly(thirdUser, firstUser);
2813+
assertThat(slice.hasNext()).isTrue();
2814+
2815+
slice = repository.findBy(userHasFirstnameLike("v"),
2816+
q -> q.sortBy(Sort.by("firstname")).slice(PageRequest.of(0, 3)));
2817+
2818+
assertThat(slice).isNotInstanceOf(Page.class);
2819+
assertThat(slice).hasSize(3);
2820+
assertThat(slice.hasNext()).isFalse();
2821+
}
2822+
2823+
@Test // GH-3727
2824+
void findByFluentSpecificationPageCustomCountSpec() {
2825+
2826+
flushTestUsers();
2827+
2828+
Page<User> page0 = repository.findBy(userHasFirstnameLike("v"),
2829+
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2), (root, query, criteriaBuilder) -> null));
2830+
2831+
assertThat(page0.getContent()).containsExactly(thirdUser, firstUser);
2832+
assertThat(page0.getTotalElements()).isEqualTo(4L);
2833+
2834+
page0 = repository.findBy(userHasFirstnameLike("v"),
2835+
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2)));
2836+
2837+
assertThat(page0.getContent()).containsExactly(thirdUser, firstUser);
2838+
assertThat(page0.getTotalElements()).isEqualTo(3L);
2839+
}
2840+
28032841
@Test // GH-2274, GH-3716
28042842
void findByFluentSpecificationWithInterfaceBasedProjection() {
28052843

0 commit comments

Comments
 (0)