Skip to content

Commit e2c0d93

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 c5eedfd commit e2c0d93

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

+19-1
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

+20
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

+38
Original file line numberDiff line numberDiff line change
@@ -2801,6 +2801,44 @@ void findByFluentSpecificationPageCustomCountSpec() {
28012801
assertThat(page0.getTotalElements()).isEqualTo(3L);
28022802
}
28032803

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

0 commit comments

Comments
 (0)