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
This commit is contained in:
Mark Paluch
2025-01-23 11:26:38 +01:00
parent e5df40b0e2
commit 7a6cddf720
3 changed files with 77 additions and 1 deletions

View File

@@ -19,6 +19,8 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import java.util.Arrays;
import java.util.Collection;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -31,6 +33,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.DeleteSpecification;
import org.springframework.data.jpa.domain.PredicateSpecification;
@@ -229,7 +232,7 @@ public interface JpaSpecificationExecutor<T> {
* @since 4.0
*/
default <S extends T, R> R findBy(PredicateSpecification<T> spec,
Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
Function<? super SpecificationFluentQuery<S>, R> queryFunction) {
return findBy(Specification.where(spec), queryFunction);
}
@@ -275,6 +278,21 @@ public interface JpaSpecificationExecutor<T> {
@Override
SpecificationFluentQuery<T> project(Collection<String> properties);
/**
* Get a page of matching elements for {@link Pageable} and provide a custom {@link Specification count
* specification}.
*
* @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be
* {@literal null}. The given {@link Pageable} will override any previously specified {@link Sort sort} if
* the {@link Sort} object is not {@link Sort#isUnsorted()}. Any potentially specified {@link #limit(int)}
* will be overridden by {@link Pageable#getPageSize()}.
* @param countSpec specification used to count results.
* @return
*/
default Page<T> page(Pageable pageable, PredicateSpecification<?> countSpec) {
return page(pageable, Specification.where(countSpec));
}
/**
* Get a page of matching elements for {@link Pageable} and provide a custom {@link Specification count
* specification}.

View File

@@ -226,6 +226,26 @@ class FetchableFluentQueryBySpecification<S, R> extends FluentQuerySupport<S, R>
private Slice<R> readSlice(Pageable pageable) {
TypedQuery<S> pagedQuery = createSortedAndProjectedQuery();
if (pageable.isPaged()) {
pagedQuery.setFirstResult(PageableUtils.getOffsetAsInteger(pageable));
pagedQuery.setMaxResults(pageable.getPageSize() + 1);
}
List<S> resultList = pagedQuery.getResultList();
boolean hasNext = resultList.size() > pageable.getPageSize();
if (hasNext) {
resultList = resultList.subList(0, pageable.getPageSize());
}
List<R> slice = convert(resultList);
return new SliceImpl<>(slice, pageable, hasNext);
}
private Slice<R> readSlice(Pageable pageable, @Nullable Specification<S> countSpec) {
TypedQuery<S> pagedQuery = createSortedAndProjectedQuery(pageable.getSort());
if (pageable.isPaged()) {

View File

@@ -2800,6 +2800,44 @@ class UserRepositoryTests {
assertThat(page0.getTotalElements()).isEqualTo(3L);
}
@Test // GH-2274
void findByFluentSpecificationSlice() {
flushTestUsers();
Slice<User> slice = repository.findBy(userHasFirstnameLike("v"),
q -> q.sortBy(Sort.by("firstname")).slice(PageRequest.of(0, 2)));
assertThat(slice).isNotInstanceOf(Page.class);
assertThat(slice.getContent()).containsExactly(thirdUser, firstUser);
assertThat(slice.hasNext()).isTrue();
slice = repository.findBy(userHasFirstnameLike("v"),
q -> q.sortBy(Sort.by("firstname")).slice(PageRequest.of(0, 3)));
assertThat(slice).isNotInstanceOf(Page.class);
assertThat(slice).hasSize(3);
assertThat(slice.hasNext()).isFalse();
}
@Test // GH-3727
void findByFluentSpecificationPageCustomCountSpec() {
flushTestUsers();
Page<User> page0 = repository.findBy(userHasFirstnameLike("v"),
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2), (root, query, criteriaBuilder) -> null));
assertThat(page0.getContent()).containsExactly(thirdUser, firstUser);
assertThat(page0.getTotalElements()).isEqualTo(4L);
page0 = repository.findBy(userHasFirstnameLike("v"),
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2)));
assertThat(page0.getContent()).containsExactly(thirdUser, firstUser);
assertThat(page0.getTotalElements()).isEqualTo(3L);
}
@Test // GH-2274, GH-3716
void findByFluentSpecificationWithInterfaceBasedProjection() {