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:
@@ -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}.
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user