diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java index 0da7b7659..ec845510c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java @@ -27,6 +27,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; import org.springframework.data.mongodb.core.MongoOperations; @@ -271,6 +272,14 @@ public class QuerydslMongoPredicateExecutor extends QuerydslPredicateExecutor return createQuery().fetchPage(pageable); } + @Override + public Slice slice(Pageable pageable) { + + Assert.notNull(pageable, "Pageable must not be null"); + + return createQuery().fetchSlice(pageable); + } + @Override public Stream stream() { return createQuery().stream(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutor.java index bb0284f91..9a1482823 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutor.java @@ -24,11 +24,13 @@ import java.util.function.Function; import org.bson.Document; import org.reactivestreams.Publisher; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Window; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Window; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.repository.query.MongoEntityInformation; @@ -239,6 +241,14 @@ public class ReactiveQuerydslMongoPredicateExecutor extends QuerydslPredicate return createQuery().fetchPage(pageable); } + @Override + public Mono> slice(Pageable pageable) { + + Assert.notNull(pageable, "Pageable must not be null"); + + return createQuery().fetchSlice(pageable); + } + @Override public Mono count() { return createQuery().fetchCount(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java index c23ca5a97..042751d30 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java @@ -27,6 +27,7 @@ import org.bson.Document; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Window; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.ReactiveFindOperation; @@ -109,6 +110,19 @@ class ReactiveSpringDataMongodbQuery extends SpringDataMongodbQuerySupport ReactivePageableExecutionUtils.getPage(it, pageable, fetchCount())); } + /** + * Fetch all matching query results as Slice. + * + * @return {@link Mono} emitting the requested Slice. + */ + Mono> fetchSlice(Pageable pageable) { + + Mono> content = createQuery().map(it -> SliceUtils.getQuery(it, pageable)) + .flatMapMany(it -> find.matching(it).all()).collectList(); + + return content.map(it -> SliceUtils.getSlice(it, pageable)); + } + /** * Fetch the one matching query result. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java index ce6dcdd3a..b62bc73f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java @@ -33,6 +33,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; import org.springframework.data.mongodb.core.ExecutableFindOperation; @@ -136,8 +137,7 @@ public class SimpleMongoRepository implements MongoRepository { Query query = getIdQuery(id); getReadPreference().ifPresent(query::withReadPreference); - return mongoOperations.exists(query, entityInformation.getJavaType(), - entityInformation.getCollectionName()); + return mongoOperations.exists(query, entityInformation.getJavaType(), entityInformation.getCollectionName()); } @Override @@ -455,6 +455,16 @@ public class SimpleMongoRepository implements MongoRepository { return PageableExecutionUtils.getPage(list, pageable, this::count); } + @Override + public Slice slice(Pageable pageable) { + + Assert.notNull(pageable, "Pageable must not be null"); + + List resultList = createQuery(q -> SliceUtils.getQuery(q, pageable)).all(); + + return SliceUtils.getSlice(resultList, pageable); + } + @Override public Stream stream() { return createQuery().stream(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java index e8ff60de4..af7a5f57c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java @@ -37,6 +37,7 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; import org.springframework.data.mongodb.core.MongoTemplate; @@ -592,6 +593,13 @@ public class SimpleReactiveMongoRepository implement return items.flatMap(content -> ReactivePageableExecutionUtils.getPage(content, pageable, this.count())); } + @Override + public Mono> slice(Pageable pageable) { + + return createQuery(q -> SliceUtils.getQuery(q, pageable)).all().collectList() + .map(it -> SliceUtils.getSlice(it, pageable)); + } + @Override public Mono count() { return createQuery().count(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SliceUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SliceUtils.java new file mode 100644 index 000000000..167875481 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SliceUtils.java @@ -0,0 +1,65 @@ +/* + * 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.support; + +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.mongodb.core.query.Query; + +/** + * Utility methods for {@link Slice} handling. + * + * @author Mark Paluch + * @since 4.5 + */ +class SliceUtils { + + /** + * Creates a {@link Slice} given {@link Pageable} and {@link List} of results. + * + * @param + * @param resultList + * @param pageable + * @return + */ + public static Slice getSlice(List resultList, Pageable pageable) { + + boolean hasNext = resultList.size() > pageable.getPageSize(); + + if (hasNext) { + resultList = resultList.subList(0, pageable.getPageSize()); + } + + return new SliceImpl<>(resultList, pageable, hasNext); + } + + /** + * Customize query for slice retrieval. + * + * @param query + * @param pageable + * @return + */ + public static Query getQuery(Query query, Pageable pageable) { + + query.with(pageable); + + return pageable.isPaged() ? query.limit(pageable.getPageSize() + 1) : query; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java index c8cad08c0..cc5f44de2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java @@ -22,10 +22,12 @@ import java.util.function.Consumer; import java.util.stream.Stream; import org.bson.Document; + import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Window; import org.springframework.data.mongodb.core.ExecutableFindOperation; import org.springframework.data.mongodb.core.MongoOperations; @@ -182,6 +184,19 @@ public class SpringDataMongodbQuery extends SpringDataMongodbQuerySupport fetchSlice(Pageable pageable) { + + List content = find.matching(SliceUtils.getQuery(createQuery(), pageable)).all(); + + return SliceUtils.getSlice(content, pageable); + } + @Override public T fetchFirst() { try { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java index 8ab45e596..44235c54e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java @@ -607,6 +607,33 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware, }).verifyComplete(); } + @Test // GH-4889 + void findByShouldApplySlice() { + + ReactivePerson probe = new ReactivePerson(); + probe.setLastname(oliver.getLastname()); + + repository + .findBy(Example.of(probe, matching().withIgnorePaths("age")), + it -> it.slice(PageRequest.of(0, 1, Sort.by("firstname")))) // + .as(StepVerifier::create) // + .assertNext(it -> { + + assertThat(it.hasNext()).isTrue(); + assertThat(it.getContent()).contains(dave); + }).verifyComplete(); + + repository + .findBy(Example.of(probe, matching().withIgnorePaths("age")), + it -> it.slice(PageRequest.of(1, 1, Sort.by("firstname")))) // + .as(StepVerifier::create) // + .assertNext(it -> { + + assertThat(it.hasNext()).isFalse(); + assertThat(it.getContent()).contains(oliver); + }).verifyComplete(); + } + @Test // GH-3757 void findByShouldCount() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java index 18d86d02d..7d9024e2f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java @@ -31,6 +31,7 @@ import org.springframework.dao.PermissionDeniedDataAccessException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.MongoDatabaseFactory; @@ -314,6 +315,7 @@ public class QuerydslMongoPredicateExecutorIntegrationTests { Page first = repository.findBy(person.lastname.eq(oliver.getLastname()), it -> it.page(PageRequest.of(0, 1, Sort.by("firstname")))); + assertThat(first.getTotalElements()).isEqualTo(2); assertThat(first.getContent()).contains(dave); @@ -324,6 +326,22 @@ public class QuerydslMongoPredicateExecutorIntegrationTests { assertThat(next.getContent()).contains(oliver); } + @Test // GH-4889 + public void findByShouldApplySlice() { + + Slice first = repository.findBy(person.lastname.eq(oliver.getLastname()), + it -> it.slice(PageRequest.of(0, 1, Sort.by("firstname")))); + + assertThat(first.hasNext()).isTrue(); + assertThat(first.getContent()).contains(dave); + + Slice next = repository.findBy(person.lastname.eq(oliver.getLastname()), + it -> it.slice(PageRequest.of(1, 1, Sort.by("firstname")))); + + assertThat(next.hasNext()).isFalse(); + assertThat(next.getContent()).contains(oliver); + } + @Test // GH-3757 public void findByShouldCount() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java index 7e6f6e499..807b7aec2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java @@ -31,6 +31,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -50,8 +51,8 @@ import org.springframework.data.mongodb.repository.QUser; import org.springframework.data.mongodb.repository.User; import org.springframework.data.mongodb.repository.query.MongoEntityInformation; import org.springframework.data.mongodb.test.util.MongoTestUtils; -import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.mongodb.test.util.ReactiveMongoClientClosingTestConfiguration; +import org.springframework.data.repository.query.FluentQuery; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -414,6 +415,28 @@ public class ReactiveQuerydslMongoPredicateExecutorTests { }).verifyComplete(); } + @Test // GH-4889 + public void findByShouldApplySlice() { + + repository + .findBy(person.lastname.eq(oliver.getLastname()), it -> it.slice(PageRequest.of(0, 1, Sort.by("firstname")))) // + .as(StepVerifier::create) // + .assertNext(it -> { + + assertThat(it.hasNext()).isTrue(); + assertThat(it.getContent()).containsOnly(dave); + }).verifyComplete(); + + repository + .findBy(person.lastname.eq(oliver.getLastname()), it -> it.slice(PageRequest.of(1, 1, Sort.by("firstname")))) // + .as(StepVerifier::create) // + .assertNext(it -> { + + assertThat(it.hasNext()).isFalse(); + assertThat(it.getContent()).containsOnly(oliver); + }).verifyComplete(); + } + @Test // GH-3757 public void findByShouldCount() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java index 98cbab868..60c02ee77 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java @@ -38,6 +38,7 @@ import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.MongoTransactionManager; @@ -579,6 +580,24 @@ class SimpleMongoRepositoryTests implements StateFunctions { assertThat(next.getContent()).contains(oliver); } + @Test // GH-4889 + void findByShouldApplySlice() { + + Person probe = new Person(); + probe.setLastname(oliver.getLastname()); + + Slice first = repository.findBy(Example.of(probe, getMatcher()), + it -> it.slice(PageRequest.of(0, 1, Sort.by("firstname")))); + assertThat(first.hasNext()).isTrue(); + assertThat(first.getContent()).contains(dave); + + Slice next = repository.findBy(Example.of(probe, getMatcher()), + it -> it.slice(PageRequest.of(1, 1, Sort.by("firstname")))); + + assertThat(next.hasNext()).isFalse(); + assertThat(next.getContent()).contains(oliver); + } + @Test // GH-3757 void findByShouldCount() {