Add support for findBy(…) using predicate that returns a Slice.

Closes: #4889
Original Pull Request: #4890
This commit is contained in:
Mark Paluch
2025-02-05 12:15:54 +01:00
committed by Christoph Strobl
parent fee8485988
commit 63783e6d09
11 changed files with 222 additions and 4 deletions

View File

@@ -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<T> extends QuerydslPredicateExecutor
return createQuery().fetchPage(pageable);
}
@Override
public Slice<T> slice(Pageable pageable) {
Assert.notNull(pageable, "Pageable must not be null");
return createQuery().fetchSlice(pageable);
}
@Override
public Stream<T> stream() {
return createQuery().stream();

View File

@@ -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<T> extends QuerydslPredicate
return createQuery().fetchPage(pageable);
}
@Override
public Mono<Slice<T>> slice(Pageable pageable) {
Assert.notNull(pageable, "Pageable must not be null");
return createQuery().fetchSlice(pageable);
}
@Override
public Mono<Long> count() {
return createQuery().fetchCount();

View File

@@ -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<K> extends SpringDataMongodbQuerySupport<Re
return content.flatMap(it -> ReactivePageableExecutionUtils.getPage(it, pageable, fetchCount()));
}
/**
* Fetch all matching query results as Slice.
*
* @return {@link Mono} emitting the requested Slice.
*/
Mono<Slice<K>> fetchSlice(Pageable pageable) {
Mono<List<K>> 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.
*

View File

@@ -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<T, ID> implements MongoRepository<T, ID> {
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<T, ID> implements MongoRepository<T, ID> {
return PageableExecutionUtils.getPage(list, pageable, this::count);
}
@Override
public Slice<T> slice(Pageable pageable) {
Assert.notNull(pageable, "Pageable must not be null");
List<T> resultList = createQuery(q -> SliceUtils.getQuery(q, pageable)).all();
return SliceUtils.getSlice(resultList, pageable);
}
@Override
public Stream<T> stream() {
return createQuery().stream();

View File

@@ -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<T, ID extends Serializable> implement
return items.flatMap(content -> ReactivePageableExecutionUtils.getPage(content, pageable, this.count()));
}
@Override
public Mono<Slice<T>> slice(Pageable pageable) {
return createQuery(q -> SliceUtils.getQuery(q, pageable)).all().collectList()
.map(it -> SliceUtils.getSlice(it, pageable));
}
@Override
public Mono<Long> count() {
return createQuery().count();

View File

@@ -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 <T>
* @param resultList
* @param pageable
* @return
*/
public static <T> Slice<T> getSlice(List<T> 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;
}
}

View File

@@ -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<T> extends SpringDataMongodbQuerySupport<Spr
}
}
/**
* Fetch a {@link Slice}.
*
* @param pageable
* @return
*/
public Slice<T> fetchSlice(Pageable pageable) {
List<T> content = find.matching(SliceUtils.getQuery(createQuery(), pageable)).all();
return SliceUtils.getSlice(content, pageable);
}
@Override
public T fetchFirst() {
try {

View File

@@ -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() {

View File

@@ -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<Person> 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<Person> 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<Person> 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() {

View File

@@ -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() {

View File

@@ -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<Person> 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<Person> 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() {