Polishing.

Move SliceUtils to another package for reuse. Slightly alter its API, add tests and update javadoc.

Original Pull Request: #4890
This commit is contained in:
Christoph Strobl
2025-02-11 14:31:01 +01:00
parent 63783e6d09
commit 49db28dc77
7 changed files with 122 additions and 25 deletions

View File

@@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -87,7 +88,7 @@ interface MongoQueryExecution {
int pageSize = pageable.getPageSize();
// Apply Pageable but tweak limit to peek into next page
Query modifiedQuery = query.with(pageable).limit(pageSize + 1);
Query modifiedQuery = SliceUtils.limitResult(query, pageable).with(pageable.getSort());
List result = find.matching(modifiedQuery).all();
boolean hasNext = result.size() > pageSize;

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.support;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -113,14 +114,16 @@ class ReactiveSpringDataMongodbQuery<K> extends SpringDataMongodbQuerySupport<Re
/**
* Fetch all matching query results as Slice.
*
* @param pageable defines range and sort of requested slice
* @return {@link Mono} emitting the requested Slice.
* @since 4.5
*/
Mono<Slice<K>> fetchSlice(Pageable pageable) {
Mono<List<K>> content = createQuery().map(it -> SliceUtils.getQuery(it, pageable))
Mono<List<K>> content = createQuery().map(it -> SliceUtils.limitResult(it, pageable).with(pageable.getSort()))
.flatMapMany(it -> find.matching(it).all()).collectList();
return content.map(it -> SliceUtils.getSlice(it, pageable));
return content.map(it -> SliceUtils.sliceResult(it, pageable));
}
/**

View File

@@ -43,6 +43,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
@@ -460,9 +461,9 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Assert.notNull(pageable, "Pageable must not be null");
List<T> resultList = createQuery(q -> SliceUtils.getQuery(q, pageable)).all();
List<T> resultList = createQuery(q -> SliceUtils.limitResult(q, pageable).with(pageable.getSort())).all();
return SliceUtils.getSlice(resultList, pageable);
return SliceUtils.sliceResult(resultList, pageable);
}
@Override

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.support;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -596,8 +597,8 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
@Override
public Mono<Slice<T>> slice(Pageable pageable) {
return createQuery(q -> SliceUtils.getQuery(q, pageable)).all().collectList()
.map(it -> SliceUtils.getSlice(it, pageable));
return createQuery(q -> SliceUtils.limitResult(q, pageable).with(pageable.getSort())).all().collectList()
.map(it -> SliceUtils.sliceResult(it, pageable));
}
@Override

View File

@@ -34,6 +34,7 @@ import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.FieldName;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
@@ -187,14 +188,15 @@ public class SpringDataMongodbQuery<T> extends SpringDataMongodbQuerySupport<Spr
/**
* Fetch a {@link Slice}.
*
* @param pageable
* @return
* @param pageable defines range and sort of requested slice
* @return new instance of {@link Slice} containing matching results within range.
* @since 4.5
*/
public Slice<T> fetchSlice(Pageable pageable) {
List<T> content = find.matching(SliceUtils.getQuery(createQuery(), pageable)).all();
List<T> content = find.matching(SliceUtils.limitResult(createQuery(), pageable).with(pageable.getSort())).all();
return SliceUtils.getSlice(content, pageable);
return SliceUtils.sliceResult(content, pageable);
}
@Override

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.support;
package org.springframework.data.mongodb.repository.util;
import java.util.List;
@@ -26,19 +26,21 @@ import org.springframework.data.mongodb.core.query.Query;
* Utility methods for {@link Slice} handling.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 4.5
*/
class SliceUtils {
public class SliceUtils {
/**
* Creates a {@link Slice} given {@link Pageable} and {@link List} of results.
*
* @param <T>
* @param resultList
* @param pageable
* @return
* @param <T> the element type.
* @param resultList the source list holding the result of the request. If the result list contains more elements
* (indicating a next slice is available) it is trimmed to the {@link Pageable#getPageSize() page size}.
* @param pageable the source pageable.
* @return new instance of {@link Slice}.
*/
public static <T> Slice<T> getSlice(List<T> resultList, Pageable pageable) {
public static <T> Slice<T> sliceResult(List<T> resultList, Pageable pageable) {
boolean hasNext = resultList.size() > pageable.getPageSize();
@@ -50,16 +52,23 @@ class SliceUtils {
}
/**
* Customize query for slice retrieval.
* Customize query for {@link #sliceResult sliced result} retrieval. If {@link Pageable#isPaged() paged} the
* {@link Query#limit(int) limit} is set to {@code pagesize + 1} in order to determine if more data is available.
*
* @param query
* @param pageable
* @return
* @param query the source query
* @param pageable paging to apply.
* @return new instance of {@link Query} if either {@link Pageable#isPaged() paged}, the source query otherwise.
*/
public static Query getQuery(Query query, Pageable pageable) {
public static Query limitResult(Query query, Pageable pageable) {
query.with(pageable);
if (pageable.isUnpaged()) {
return query;
}
return pageable.isPaged() ? query.limit(pageable.getPageSize() + 1) : query;
Query target = Query.of(query);
target.skip(pageable.getOffset());
target.limit(pageable.getPageSize() + 1);
return target;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verifyNoInteractions;
import java.util.stream.Stream;
import org.bson.Document;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
/**
* Unit test for {@link SliceUtils}.
*
* @author Christoph Strobl
*/
class SliceUtilsUnitTests {
@ParameterizedTest // GH-4889
@MethodSource("paged")
void pagedPageableModifiesQuery(Pageable page) {
Query source = new BasicQuery(Document.parse("{ 'spring' : 'data' }"));
Query target = SliceUtils.limitResult(source, page);
assertThat(target.getQueryObject()).isEqualTo(source.getQueryObject());
assertThat(target).isNotSameAs(source);
assertThat(target.isLimited()).isTrue();
assertThat(target.getSkip()).isEqualTo(page.getOffset());
assertThat(target.getLimit()).isEqualTo(page.toLimit().max() + 1);
assertThat(target.getSortObject()).isEqualTo(source.getSortObject());
}
@ParameterizedTest // GH-4889
@MethodSource("unpaged")
void unpagedPageableDoesNotModifyQuery(Pageable page) {
Query source = spy(new BasicQuery(Document.parse("{ 'spring' : 'data' }")));
Query target = SliceUtils.limitResult(source, page);
verifyNoInteractions(source);
assertThat(target).isSameAs(source);
assertThat(target.isLimited()).isFalse();
}
public static Stream<Arguments> paged() {
return Stream.of(Arguments.of(Pageable.ofSize(1)), Arguments.of(PageRequest.of(0, 10)),
Arguments.of(PageRequest.of(0, 10, Direction.ASC, "name")));
}
public static Stream<Arguments> unpaged() {
return Stream.of(Arguments.of(Pageable.unpaged()), Arguments.of(Pageable.unpaged(Sort.by("name"))));
}
}