T save(T objectToSave, String collectionName) {
return source.isVersionedEntity() //
? doSaveVersioned(source, collectionName) //
: (T) doSave(collectionName, objectToSave, this.mongoConverter);
-
}
@SuppressWarnings("unchecked")
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/BasicQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/BasicQuery.java
index 4de789e12f..cc2ae28c23 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/BasicQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/BasicQuery.java
@@ -164,9 +164,9 @@ public boolean isSorted() {
* @throws IllegalArgumentException when {@code fieldsObject} is {@literal null}.
* @since 1.6
*/
- protected void setFieldsObject(Document fieldsObject) {
+ public void setFieldsObject(Document fieldsObject) {
- Assert.notNull(sortObject, "Field document must not be null");
+ Assert.notNull(fieldsObject, "Field document must not be null");
this.fieldsObject = fieldsObject;
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableFluentQuerySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableFluentQuerySupport.java
new file mode 100644
index 0000000000..d910106bd0
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableFluentQuerySupport.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 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.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.repository.query.FluentQuery;
+import org.springframework.util.Assert;
+
+/**
+ * Support class for {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} implementations.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+abstract class FetchableFluentQuerySupport implements FluentQuery.FetchableFluentQuery {
+
+ private final P predicate;
+ private final Sort sort;
+ private final Class resultType;
+ private final List fieldsToInclude;
+
+ FetchableFluentQuerySupport(P predicate, Sort sort, Class resultType, List fieldsToInclude) {
+ this.predicate = predicate;
+ this.sort = sort;
+ this.resultType = resultType;
+ this.fieldsToInclude = fieldsToInclude;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#sortBy(org.springframework.data.domain.Sort)
+ */
+ @Override
+ public FluentQuery.FetchableFluentQuery sortBy(Sort sort) {
+
+ Assert.notNull(sort, "Sort must not be null!");
+
+ return create(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#as(java.lang.Class)
+ */
+ @Override
+ public FluentQuery.FetchableFluentQuery as(Class projection) {
+
+ Assert.notNull(projection, "Projection target type must not be null!");
+
+ return create(predicate, sort, projection, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#project(java.util.Collection)
+ */
+ @Override
+ public FluentQuery.FetchableFluentQuery project(Collection properties) {
+
+ Assert.notNull(properties, "Projection properties must not be null!");
+
+ return create(predicate, sort, resultType, new ArrayList<>(properties));
+ }
+
+ protected abstract FetchableFluentQuerySupport create(P predicate, Sort sort, Class resultType,
+ List fieldsToInclude);
+
+ P getPredicate() {
+ return predicate;
+ }
+
+ Sort getSort() {
+ return sort;
+ }
+
+ Class getResultType() {
+ return resultType;
+ }
+
+ List getFieldsToInclude() {
+ return fieldsToInclude;
+ }
+}
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 569273afb5..5255dedaa4 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
@@ -15,18 +15,24 @@
*/
package org.springframework.data.mongodb.repository.support;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
+import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.util.Assert;
@@ -184,6 +190,21 @@ public boolean exists(Predicate predicate) {
return createQueryFor(predicate).fetchCount() > 0;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public R findBy(Predicate predicate,
+ Function, R> queryFunction) {
+
+ Assert.notNull(predicate, "Predicate must not be null!");
+ Assert.notNull(queryFunction, "Query function must not be null!");
+
+ return queryFunction.apply(new FluentQuerydsl<>(predicate, (Class) typeInformation().getJavaType()));
+ }
+
/**
* Creates a {@link SpringDataMongodbQuery} for the given {@link Predicate}.
*
@@ -232,4 +253,113 @@ private SpringDataMongodbQuery applySorting(SpringDataMongodbQuery query,
toOrderSpecifiers(sort).forEach(query::orderBy);
return query;
}
+
+ /**
+ * {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using Querydsl
+ * {@link Predicate}.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+ class FluentQuerydsl extends FetchableFluentQuerySupport {
+
+ FluentQuerydsl(Predicate predicate, Class resultType) {
+ this(predicate, Sort.unsorted(), resultType, Collections.emptyList());
+ }
+
+ FluentQuerydsl(Predicate predicate, Sort sort, Class resultType, List fieldsToInclude) {
+ super(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ @Override
+ protected FluentQuerydsl create(Predicate predicate, Sort sort, Class resultType,
+ List fieldsToInclude) {
+ return new FluentQuerydsl<>(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
+ */
+ @Override
+ public T oneValue() {
+ return createQuery().fetchOne();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
+ */
+ @Override
+ public T firstValue() {
+ return createQuery().fetchFirst();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
+ */
+ @Override
+ public List all() {
+ return createQuery().fetch();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
+ */
+ @Override
+ public Page page(Pageable pageable) {
+
+ Assert.notNull(pageable, "Pageable must not be null!");
+
+ return createQuery().fetchPage(pageable);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
+ */
+ @Override
+ public Stream stream() {
+ return createQuery().stream();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
+ */
+ @Override
+ public long count() {
+ return createQuery().fetchCount();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
+ */
+ @Override
+ public boolean exists() {
+ return count() > 0;
+ }
+
+ private SpringDataMongodbQuery createQuery() {
+ return new SpringDataMongodbQuery<>(mongoOperations, typeInformation().getJavaType(), getResultType(),
+ mongoOperations.getCollectionName(typeInformation().getJavaType()), this::customize).where(getPredicate());
+ }
+
+ private void customize(BasicQuery query) {
+
+ List fieldsToInclude = getFieldsToInclude();
+ if (!fieldsToInclude.isEmpty()) {
+ Document fields = new Document();
+ fieldsToInclude.forEach(field -> fields.put(field, 1));
+ query.setFieldsObject(fields);
+ }
+
+ if (getSort().isSorted()) {
+ query.with(getSort());
+ }
+ }
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveFluentQuerySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveFluentQuerySupport.java
new file mode 100644
index 0000000000..ee5ef32555
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveFluentQuerySupport.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 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.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.repository.query.FluentQuery;
+import org.springframework.util.Assert;
+
+/**
+ * Support class for {@link org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery} implementations.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+abstract class ReactiveFluentQuerySupport implements FluentQuery.ReactiveFluentQuery {
+
+ private final P predicate;
+ private final Sort sort;
+ private final Class resultType;
+ private final List fieldsToInclude;
+
+ ReactiveFluentQuerySupport(P predicate, Sort sort, Class resultType, List fieldsToInclude) {
+ this.predicate = predicate;
+ this.sort = sort;
+ this.resultType = resultType;
+ this.fieldsToInclude = fieldsToInclude;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#sortBy(org.springframework.data.domain.Sort)
+ */
+ @Override
+ public ReactiveFluentQuery sortBy(Sort sort) {
+
+ Assert.notNull(sort, "Sort must not be null!");
+
+ return create(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#as(java.lang.Class)
+ */
+ @Override
+ public ReactiveFluentQuery as(Class projection) {
+
+ Assert.notNull(projection, "Projection target type must not be null!");
+
+ return create(predicate, sort, projection, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#project(java.util.Collection)
+ */
+ @Override
+ public ReactiveFluentQuery project(Collection properties) {
+
+ Assert.notNull(properties, "Projection properties must not be null!");
+
+ return create(predicate, sort, resultType, new ArrayList<>(properties));
+ }
+
+ protected abstract ReactiveFluentQuerySupport create(P predicate, Sort sort, Class resultType,
+ List fieldsToInclude);
+
+ P getPredicate() {
+ return predicate;
+ }
+
+ Sort getSort() {
+ return sort;
+ }
+
+ Class getResultType() {
+ return resultType;
+ }
+
+ List getFieldsToInclude() {
+ return fieldsToInclude;
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactivePageableExecutionUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactivePageableExecutionUtils.java
new file mode 100644
index 0000000000..2bcbdba6ae
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactivePageableExecutionUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2021 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 reactor.core.publisher.Mono;
+
+import java.util.List;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.util.Assert;
+
+/**
+ * Support for query execution using {@link Pageable}. Using {@link ReactivePageableExecutionUtils} assumes that data
+ * queries are cheaper than {@code COUNT} queries and so some cases can take advantage of optimizations.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+abstract class ReactivePageableExecutionUtils {
+
+ private ReactivePageableExecutionUtils() {}
+
+ /**
+ * Constructs a {@link Page} based on the given {@code content}, {@link Pageable} and {@link Mono} applying
+ * optimizations. The construction of {@link Page} omits a count query if the total can be determined based on the
+ * result size and {@link Pageable}.
+ *
+ * @param content must not be {@literal null}.
+ * @param pageable must not be {@literal null}.
+ * @param totalSupplier must not be {@literal null}.
+ * @return the {@link Page}.
+ */
+ public static Mono> getPage(List content, Pageable pageable, Mono totalSupplier) {
+
+ Assert.notNull(content, "Content must not be null!");
+ Assert.notNull(pageable, "Pageable must not be null!");
+ Assert.notNull(totalSupplier, "TotalSupplier must not be null!");
+
+ if (pageable.isUnpaged() || pageable.getOffset() == 0) {
+
+ if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
+ return Mono.just(new PageImpl<>(content, pageable, content.size()));
+ }
+
+ return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
+ }
+
+ if (content.size() != 0 && pageable.getPageSize() > content.size()) {
+ return Mono.just(new PageImpl<>(content, pageable, pageable.getOffset() + content.size()));
+ }
+
+ return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
+ }
+}
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 1da48bfc8e..1abf4c75d5 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
@@ -18,13 +18,23 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import java.util.Collections;
+import java.util.List;
+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.Sort;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
+import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
+import org.springframework.data.repository.query.FluentQuery;
import org.springframework.util.Assert;
import com.querydsl.core.types.EntityPath;
@@ -159,6 +169,20 @@ public Mono exists(Predicate predicate) {
return createQueryFor(predicate).fetchCount().map(it -> it != 0);
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
+ */
+ @Override
+ public > P findBy(Predicate predicate,
+ Function, P> queryFunction) {
+
+ Assert.notNull(predicate, "Predicate must not be null!");
+ Assert.notNull(queryFunction, "Query function must not be null!");
+
+ return queryFunction.apply(new ReactiveFluentQuerydsl(predicate, (Class) typeInformation().getJavaType()));
+ }
+
/**
* Creates a {@link ReactiveSpringDataMongodbQuery} for the given {@link Predicate}.
*
@@ -177,8 +201,8 @@ private ReactiveSpringDataMongodbQuery createQueryFor(Predicate predicate) {
private ReactiveSpringDataMongodbQuery createQuery() {
Class javaType = typeInformation().getJavaType();
- return new ReactiveSpringDataMongodbQuery<>(mongodbSerializer(), mongoOperations, javaType,
- mongoOperations.getCollectionName(javaType));
+ return new ReactiveSpringDataMongodbQuery<>(mongoOperations, javaType, javaType,
+ mongoOperations.getCollectionName(javaType), it -> {});
}
/**
@@ -194,4 +218,105 @@ private ReactiveSpringDataMongodbQuery applySorting(ReactiveSpringDataMongodb
return query;
}
+ /**
+ * {@link org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery} using Querydsl {@link Predicate}.
+ *
+ * @since 3.3
+ * @author Mark Paluch
+ */
+ class ReactiveFluentQuerydsl extends ReactiveFluentQuerySupport {
+
+ ReactiveFluentQuerydsl(Predicate predicate, Class resultType) {
+ this(predicate, Sort.unsorted(), resultType, Collections.emptyList());
+ }
+
+ ReactiveFluentQuerydsl(Predicate predicate, Sort sort, Class resultType, List fieldsToInclude) {
+ super(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ @Override
+ protected ReactiveFluentQuerydsl create(Predicate predicate, Sort sort, Class resultType,
+ List fieldsToInclude) {
+ return new ReactiveFluentQuerydsl<>(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#one()
+ */
+ @Override
+ public Mono one() {
+ return createQuery().fetchOne();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#first()
+ */
+ @Override
+ public Mono first() {
+ return createQuery().fetchFirst();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#all()
+ */
+ @Override
+ public Flux all() {
+ return createQuery().fetch();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#page(org.springframework.data.domain.Pageable)
+ */
+ @Override
+ public Mono> page(Pageable pageable) {
+
+ Assert.notNull(pageable, "Pageable must not be null!");
+
+ return createQuery().fetchPage(pageable);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#count()
+ */
+ @Override
+ public Mono count() {
+ return createQuery().fetchCount();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#exists()
+ */
+ @Override
+ public Mono exists() {
+ return count().map(it -> it > 0).defaultIfEmpty(false);
+ }
+
+ private ReactiveSpringDataMongodbQuery createQuery() {
+
+ return new ReactiveSpringDataMongodbQuery<>(mongoOperations, typeInformation().getJavaType(), getResultType(),
+ mongoOperations.getCollectionName(typeInformation().getJavaType()), this::customize).where(getPredicate());
+ }
+
+ private void customize(BasicQuery query) {
+
+ List fieldsToInclude = getFieldsToInclude();
+
+ if (!fieldsToInclude.isEmpty()) {
+ Document fields = new Document();
+ fieldsToInclude.forEach(field -> fields.put(field, 1));
+ query.setFieldsObject(fields);
+ }
+
+ if (getSort().isSorted()) {
+ query.with(getSort());
+ }
+ }
+ }
+
}
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 8b30e585e6..d00d8873e5 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
@@ -21,11 +21,14 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
import org.bson.Document;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
+import org.springframework.data.mongodb.core.ReactiveFindOperation;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
@@ -44,7 +47,6 @@
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.mongodb.MongodbOps;
-import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* MongoDB query with utilizing {@link ReactiveMongoOperations} for command execution.
@@ -59,21 +61,23 @@
class ReactiveSpringDataMongodbQuery extends SpringDataMongodbQuerySupport> {
private final ReactiveMongoOperations mongoOperations;
- private final FindWithProjection find;
+ private final Consumer queryCustomizer;
+ private final ReactiveFindOperation.FindWithQuery find;
ReactiveSpringDataMongodbQuery(ReactiveMongoOperations mongoOperations, Class extends K> entityClass) {
- this(new SpringDataMongodbSerializer(mongoOperations.getConverter()), mongoOperations, entityClass, null);
+ this(mongoOperations, entityClass, entityClass, null, it -> {});
}
@SuppressWarnings("unchecked")
- ReactiveSpringDataMongodbQuery(MongodbDocumentSerializer serializer, ReactiveMongoOperations mongoOperations,
- Class extends K> entityClass, @Nullable String collection) {
+ ReactiveSpringDataMongodbQuery(ReactiveMongoOperations mongoOperations, Class> domainType,
+ Class extends K> resultType, @Nullable String collection, Consumer queryCustomizer) {
- super(serializer);
+ super(new SpringDataMongodbSerializer(mongoOperations.getConverter()));
this.mongoOperations = mongoOperations;
- this.find = StringUtils.hasText(collection) ? mongoOperations.query((Class) entityClass).inCollection(collection)
- : mongoOperations.query((Class) entityClass);
+ this.queryCustomizer = queryCustomizer;
+ this.find = (StringUtils.hasText(collection) ? mongoOperations.query(domainType).inCollection(collection)
+ : mongoOperations.query(domainType)).as((Class) resultType);
}
/**
@@ -86,7 +90,19 @@ Flux fetch() {
}
/**
- * Fetch the first matching query result.
+ * Fetch all matching query results as page.
+ *
+ * @return {@link Mono} emitting the requested page.
+ */
+ Mono> fetchPage(Pageable pageable) {
+
+ Mono> content = createQuery().flatMapMany(it -> find.matching(it).all()).collectList();
+
+ return content.flatMap(it -> ReactivePageableExecutionUtils.getPage(it, pageable, fetchCount()));
+ }
+
+ /**
+ * Fetch the one matching query result.
*
* @return {@link Mono} emitting the first query result or {@link Mono#empty()} if there are none.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
@@ -95,6 +111,16 @@ Mono fetchOne() {
return createQuery().flatMap(it -> find.matching(it).one());
}
+ /**
+ * Fetch the first matching query result. @return {@link Mono} emitting the first query result or {@link Mono#empty()}
+ * if there are none.
+ *
+ * @since 3.3
+ */
+ Mono fetchFirst() {
+ return createQuery().flatMap(it -> find.matching(it).first());
+ }
+
/**
* Fetch the count of matching query results.
*
@@ -144,6 +170,8 @@ protected Mono createQuery(Mono filter, @Nullable Expression
basicQuery.setSortObject(createSort(orderBy));
}
+ queryCustomizer.accept(basicQuery);
+
return basicQuery;
});
}
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 1443474b8f..6b8b2fac9a 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
@@ -22,7 +22,10 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Example;
@@ -30,6 +33,7 @@
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
@@ -211,8 +215,7 @@ public void deleteAllById(Iterable extends ID> ids) {
Assert.notNull(ids, "The given Iterable of ids must not be null!");
- mongoOperations.remove(getIdQuery(ids), entityInformation.getJavaType(),
- entityInformation.getCollectionName());
+ mongoOperations.remove(getIdQuery(ids), entityInformation.getJavaType(), entityInformation.getCollectionName());
}
/*
@@ -362,8 +365,8 @@ public Page findAll(Example example, Pageable pageable) {
List list = mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
- return PageableExecutionUtils.getPage(list, pageable,
- () -> mongoOperations.count(Query.of(query).limit(-1).skip(-1), example.getProbeType(), entityInformation.getCollectionName()));
+ return PageableExecutionUtils.getPage(list, pageable, () -> mongoOperations
+ .count(Query.of(query).limit(-1).skip(-1), example.getProbeType(), entityInformation.getCollectionName()));
}
/*
@@ -396,6 +399,20 @@ public boolean exists(Example example) {
return mongoOperations.exists(query, example.getProbeType(), entityInformation.getCollectionName());
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryByExampleExecutor#findBy(org.springframework.data.domain.Example, java.util.function.Function)
+ */
+ @Override
+ public R findBy(Example example,
+ Function, R> queryFunction) {
+
+ Assert.notNull(example, "Sample must not be null!");
+ Assert.notNull(queryFunction, "Query function must not be null!");
+
+ return queryFunction.apply(new FluentQueryByExample<>(example, example.getProbeType()));
+ }
+
// -------------------------------------------------------------------------
// Utility methods
// -------------------------------------------------------------------------
@@ -410,8 +427,7 @@ private Criteria getIdCriteria(Object id) {
private Query getIdQuery(Iterable extends ID> ids) {
- return new Query(new Criteria(entityInformation.getIdAttribute())
- .in(toCollection(ids)));
+ return new Query(new Criteria(entityInformation.getIdAttribute()).in(toCollection(ids)));
}
private static Collection toCollection(Iterable ids) {
@@ -428,4 +444,119 @@ private List findAll(@Nullable Query query) {
return mongoOperations.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
}
+ /**
+ * {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using {@link Example}.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+ class FluentQueryByExample extends FetchableFluentQuerySupport, T> {
+
+ FluentQueryByExample(Example example, Class resultType) {
+ this(example, Sort.unsorted(), resultType, Collections.emptyList());
+ }
+
+ FluentQueryByExample(Example example, Sort sort, Class resultType, List fieldsToInclude) {
+ super(example, sort, resultType, fieldsToInclude);
+ }
+
+ @Override
+ protected FluentQueryByExample create(Example predicate, Sort sort, Class resultType,
+ List fieldsToInclude) {
+ return new FluentQueryByExample<>(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
+ */
+ @Override
+ public T oneValue() {
+ return createQuery().oneValue();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#first()
+ */
+ @Override
+ public T firstValue() {
+ return createQuery().firstValue();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
+ */
+ @Override
+ public List all() {
+ return createQuery().all();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
+ */
+ @Override
+ public Page page(Pageable pageable) {
+
+ Assert.notNull(pageable, "Pageable must not be null!");
+
+ List list = createQuery(q -> q.with(pageable)).all();
+
+ return PageableExecutionUtils.getPage(list, pageable, this::count);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
+ */
+ @Override
+ public Stream stream() {
+ return createQuery().stream();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
+ */
+ @Override
+ public long count() {
+ return createQuery().count();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
+ */
+ @Override
+ public boolean exists() {
+ return createQuery().exists();
+ }
+
+ private ExecutableFindOperation.TerminatingFind createQuery() {
+ return createQuery(UnaryOperator.identity());
+ }
+
+ private ExecutableFindOperation.TerminatingFind createQuery(UnaryOperator queryCustomizer) {
+
+ Query query = new Query(new Criteria().alike(getPredicate())) //
+ .collation(entityInformation.getCollation());
+
+ if (getSort().isSorted()) {
+ query.with(getSort());
+ }
+
+ if (!getFieldsToInclude().isEmpty()) {
+ query.fields().include(getFieldsToInclude().toArray(new String[0]));
+ }
+
+ query = queryCustomizer.apply(query);
+
+ return mongoOperations.query(getPredicate().getProbeType()).inCollection(entityInformation.getCollectionName())
+ .as(getResultType()).matching(query);
+ }
+
+ }
+
}
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 325547abee..f238f87774 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
@@ -22,19 +22,26 @@
import java.io.Serializable;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
-
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.ReactiveFindOperation;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
+import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
@@ -210,7 +217,6 @@ public Mono count() {
return mongoOperations.count(new Query(), entityInformation.getCollectionName());
}
-
/*
* (non-Javadoc)
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#deleteById(java.lang.Object)
@@ -466,6 +472,20 @@ public Mono exists(Example example) {
return mongoOperations.exists(query, example.getProbeType(), entityInformation.getCollectionName());
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.ReactiveQueryByExampleExecutor#findBy(org.springframework.data.domain.Example, java.util.function.Function)
+ */
+ @Override
+ public > P findBy(Example example,
+ Function, P> queryFunction) {
+
+ Assert.notNull(example, "Sample must not be null!");
+ Assert.notNull(queryFunction, "Query function must not be null!");
+
+ return queryFunction.apply(new ReactiveFluentQueryByExample<>(example, example.getProbeType()));
+ }
+
private Query getIdQuery(Object id) {
return new Query(getIdCriteria(id));
}
@@ -486,4 +506,110 @@ private static Collection toCollection(Iterable ids) {
private Flux findAll(Query query) {
return mongoOperations.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
}
+
+ /**
+ * {@link org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery} using {@link Example}.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+ class ReactiveFluentQueryByExample extends ReactiveFluentQuerySupport, T> {
+
+ ReactiveFluentQueryByExample(Example example, Class resultType) {
+ this(example, Sort.unsorted(), resultType, Collections.emptyList());
+ }
+
+ ReactiveFluentQueryByExample(Example example, Sort sort, Class resultType, List fieldsToInclude) {
+ super(example, sort, resultType, fieldsToInclude);
+ }
+
+ @Override
+ protected ReactiveFluentQueryByExample create(Example predicate, Sort sort, Class resultType,
+ List fieldsToInclude) {
+ return new ReactiveFluentQueryByExample<>(predicate, sort, resultType, fieldsToInclude);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#one()
+ */
+ @Override
+ public Mono one() {
+ return createQuery().one();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#first()
+ */
+ @Override
+ public Mono first() {
+ return createQuery().first();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#all()
+ */
+ @Override
+ public Flux all() {
+ return createQuery().all();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#page(org.springframework.data.domain.Pageable)
+ */
+ @Override
+ public Mono> page(Pageable pageable) {
+
+ Assert.notNull(pageable, "Pageable must not be null!");
+
+ Mono> items = createQuery(q -> q.with(pageable)).all().collectList();
+
+ return items.flatMap(content -> ReactivePageableExecutionUtils.getPage(content, pageable, this.count()));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#count()
+ */
+ @Override
+ public Mono count() {
+ return createQuery().count();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#exists()
+ */
+ @Override
+ public Mono exists() {
+ return createQuery().exists();
+ }
+
+ private ReactiveFindOperation.TerminatingFind createQuery() {
+ return createQuery(UnaryOperator.identity());
+ }
+
+ private ReactiveFindOperation.TerminatingFind createQuery(UnaryOperator queryCustomizer) {
+
+ Query query = new Query(new Criteria().alike(getPredicate())) //
+ .collation(entityInformation.getCollation());
+
+ if (getSort().isSorted()) {
+ query.with(getSort());
+ }
+
+ if (!getFieldsToInclude().isEmpty()) {
+ query.fields().include(getFieldsToInclude().toArray(new String[0]));
+ }
+
+ query = queryCustomizer.apply(query);
+
+ return mongoOperations.query(getPredicate().getProbeType()).inCollection(entityInformation.getCollectionName())
+ .as(getResultType()).matching(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 d62aa99c5e..5a914afec1 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
@@ -16,14 +16,21 @@
package org.springframework.data.mongodb.repository.support;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+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.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
import com.mysema.commons.lang.CloseableIterator;
@@ -35,7 +42,6 @@
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
-import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* Spring Data specific simple {@link com.querydsl.core.Fetchable} {@link com.querydsl.core.SimpleQuery Query}
@@ -48,10 +54,9 @@
public class SpringDataMongodbQuery extends SpringDataMongodbQuerySupport>
implements Fetchable {
- private final Class entityClass;
- private final String collection;
private final MongoOperations mongoOperations;
- private final ExecutableFindOperation.FindWithProjection find;
+ private final Consumer queryCustomizer;
+ private final ExecutableFindOperation.FindWithQuery find;
/**
* Creates a new {@link SpringDataMongodbQuery}.
@@ -72,18 +77,26 @@ public SpringDataMongodbQuery(MongoOperations operations, Class extends T> typ
*/
public SpringDataMongodbQuery(MongoOperations operations, Class extends T> type,
String collectionName) {
- this(new SpringDataMongodbSerializer(operations.getConverter()), operations, type, collectionName);
+ this(operations, type, type, collectionName, it -> {});
}
- private SpringDataMongodbQuery(MongodbDocumentSerializer serializer, MongoOperations operations,
- Class extends T> type, String collectionName) {
-
- super(serializer);
+ /**
+ * Creates a new {@link SpringDataMongodbQuery}.
+ *
+ * @param operations must not be {@literal null}.
+ * @param domainType must not be {@literal null}.
+ * @param resultType must not be {@literal null}.
+ * @param collectionName must not be {@literal null} or empty.
+ * @since 3.3
+ */
+ SpringDataMongodbQuery(MongoOperations operations, Class> domainType, Class extends T> resultType,
+ String collectionName, Consumer queryCustomizer) {
+ super(new SpringDataMongodbSerializer(operations.getConverter()));
- this.entityClass = (Class) type;
- this.collection = collectionName;
+ Class resultType1 = (Class) resultType;
this.mongoOperations = operations;
- this.find = mongoOperations.query(this.entityClass).inCollection(collection);
+ this.queryCustomizer = queryCustomizer;
+ this.find = mongoOperations.query(domainType).inCollection(collectionName).as(resultType1);
}
/*
@@ -94,19 +107,19 @@ private SpringDataMongodbQuery(MongodbDocumentSerializer serializer, MongoOperat
public CloseableIterator iterate() {
try {
- org.springframework.data.util.CloseableIterator extends T> stream = mongoOperations.stream(createQuery(),
- entityClass, collection);
+ Stream stream = stream();
+ Iterator iterator = stream.iterator();
return new CloseableIterator() {
@Override
public boolean hasNext() {
- return stream.hasNext();
+ return iterator.hasNext();
}
@Override
public T next() {
- return stream.next();
+ return iterator.next();
}
@Override
@@ -124,6 +137,20 @@ public void close() {
}
}
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#iterable()
+ */
+ @Override
+ public Stream stream() {
+
+ try {
+ return find.matching(createQuery()).stream();
+ } catch (RuntimeException e) {
+ return handleException(e, Stream.empty());
+ }
+ }
+
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetch()
@@ -137,6 +164,24 @@ public List fetch() {
}
}
+ /**
+ * Fetch a {@link Page}.
+ *
+ * @param pageable
+ * @return
+ */
+ public Page fetchPage(Pageable pageable) {
+
+ try {
+
+ List content = find.matching(createQuery().with(pageable)).all();
+
+ return PageableExecutionUtils.getPage(content, pageable, this::fetchCount);
+ } catch (RuntimeException e) {
+ return handleException(e, new PageImpl<>(Collections.emptyList(), pageable, 0));
+ }
+ }
+
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchFirst()
@@ -215,6 +260,8 @@ protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullabl
basicQuery.setSortObject(createSort(orderBy));
}
+ queryCustomizer.accept(basicQuery);
+
return basicQuery;
}
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 0067eb3bf1..176fd175e2 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
@@ -40,12 +40,14 @@
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Example;
+import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository;
+import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
@@ -475,6 +477,150 @@ void findOneByExampleWithoutResultShouldCompleteEmpty() {
repository.findOne(example).as(StepVerifier::create).verifyComplete();
}
+ @Test // GH-3757
+ void findByShouldReturnFirstResult() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setFirstname(oliver.getFirstname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::first) //
+ .as(StepVerifier::create) //
+ .expectNext(oliver) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ void findByShouldReturnOneResult() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setFirstname(oliver.getFirstname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::one) //
+ .as(StepVerifier::create) //
+ .expectNext(oliver) //
+ .verifyComplete();
+
+ probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::one) //
+ .as(StepVerifier::create) //
+ .verifyError(IncorrectResultSizeDataAccessException.class);
+ }
+
+ @Test // GH-3757
+ void findByShouldReturnAll() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::all) //
+ .as(StepVerifier::create) //
+ .expectNextCount(2) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ void findByShouldApplySortAll() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), it -> it.sortBy(Sort.by("firstname")).all()) //
+ .as(StepVerifier::create) //
+ .expectNext(dave, oliver) //
+ .verifyComplete();
+
+ repository
+ .findBy(Example.of(probe, matching().withIgnorePaths("age")),
+ it -> it.sortBy(Sort.by(Direction.DESC, "firstname")).all()) //
+ .as(StepVerifier::create) //
+ .expectNext(oliver, dave) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ void findByShouldApplyProjection() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), it -> it.project("firstname").first()) //
+ .as(StepVerifier::create) //
+ .assertNext(it -> {
+
+ assertThat(it.getFirstname()).isNotNull();
+ assertThat(it.getLastname()).isNull();
+ }).verifyComplete();
+ }
+
+ @Test // GH-3757
+ void findByShouldApplyPagination() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository
+ .findBy(Example.of(probe, matching().withIgnorePaths("age")),
+ it -> it.page(PageRequest.of(0, 1, Sort.by("firstname")))) //
+ .as(StepVerifier::create) //
+ .assertNext(it -> {
+
+ assertThat(it.getTotalElements()).isEqualTo(2);
+ assertThat(it.getContent()).contains(dave);
+ }).verifyComplete();
+
+ repository
+ .findBy(Example.of(probe, matching().withIgnorePaths("age")),
+ it -> it.page(PageRequest.of(1, 1, Sort.by("firstname")))) //
+ .as(StepVerifier::create) //
+ .assertNext(it -> {
+
+ assertThat(it.getTotalElements()).isEqualTo(2);
+ assertThat(it.getContent()).contains(oliver);
+ }).verifyComplete();
+ }
+
+ @Test // GH-3757
+ void findByShouldCount() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::count) //
+ .as(StepVerifier::create) //
+ .expectNext(2L) //
+ .verifyComplete();
+
+ probe = new ReactivePerson();
+ probe.setLastname("foo");
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::count) //
+ .as(StepVerifier::create) //
+ .expectNext(0L) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ void findByShouldReportExists() {
+
+ ReactivePerson probe = new ReactivePerson();
+ probe.setLastname(oliver.getLastname());
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::exists) //
+ .as(StepVerifier::create) //
+ .expectNext(true) //
+ .verifyComplete();
+
+ probe = new ReactivePerson();
+ probe.setLastname("foo");
+
+ repository.findBy(Example.of(probe, matching().withIgnorePaths("age")), FluentQuery.ReactiveFluentQuery::exists) //
+ .as(StepVerifier::create) //
+ .expectNext(false) //
+ .verifyComplete();
+ }
+
interface ReactivePersonRepository extends ReactiveMongoRepository {
Flux findByLastname(String lastname);
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 782e46b134..01f5df84ba 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
@@ -24,9 +24,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
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.Sort;
@@ -41,6 +43,7 @@
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.repository.query.FluentQuery;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -240,4 +243,104 @@ protected MongoDatabase doGetDatabase() {
repository.findOne(person.firstname.contains("batman"));
}
+
+ @Test // GH-3757
+ public void findByShouldReturnFirstResult() {
+
+ Person result = repository.findBy(person.firstname.eq(oliver.getFirstname()),
+ FluentQuery.FetchableFluentQuery::oneValue);
+
+ assertThat(result).isEqualTo(oliver);
+ }
+
+ @Test // GH-3757
+ public void findByShouldReturnOneResult() {
+
+ Person result = repository.findBy(person.firstname.eq(oliver.getFirstname()),
+ FluentQuery.FetchableFluentQuery::oneValue);
+
+ assertThat(result).isEqualTo(oliver);
+
+ assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(
+ () -> repository.findBy(person.lastname.eq(oliver.getLastname()), FluentQuery.FetchableFluentQuery::one));
+ }
+
+ @Test // GH-3757
+ public void findByShouldReturnAll() {
+
+ List result = repository.findBy(person.lastname.eq(oliver.getLastname()),
+ FluentQuery.FetchableFluentQuery::all);
+
+ assertThat(result).hasSize(2);
+ }
+
+ @Test // GH-3757
+ public void findByShouldApplySortAll() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ List result = repository.findBy(person.lastname.eq(oliver.getLastname()),
+ it -> it.sortBy(Sort.by("firstname")).all());
+ assertThat(result).containsSequence(dave, oliver);
+
+ result = repository.findBy(person.lastname.eq(oliver.getLastname()),
+ it -> it.sortBy(Sort.by(Sort.Direction.DESC, "firstname")).all());
+ assertThat(result).containsSequence(oliver, dave);
+ }
+
+ @Test // GH-3757
+ public void findByShouldApplyProjection() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ Person result = repository.findBy(person.lastname.eq(oliver.getLastname()),
+ it -> it.project("firstname").firstValue());
+
+ assertThat(result.getFirstname()).isNotNull();
+ assertThat(result.getLastname()).isNull();
+ }
+
+ @Test // GH-3757
+ public void findByShouldApplyPagination() {
+
+ 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);
+
+ Page next = repository.findBy(person.lastname.eq(oliver.getLastname()),
+ it -> it.page(PageRequest.of(1, 1, Sort.by("firstname"))));
+
+ assertThat(next.getTotalElements()).isEqualTo(2);
+ assertThat(next.getContent()).contains(oliver);
+ }
+
+ @Test // GH-3757
+ public void findByShouldCount() {
+
+ long count = repository.findBy(person.lastname.eq(oliver.getLastname()), FluentQuery.FetchableFluentQuery::count);
+ assertThat(count).isEqualTo(2L);
+
+ count = repository.findBy(person.lastname.eq("foo"), FluentQuery.FetchableFluentQuery::count);
+ assertThat(count).isEqualTo(0L);
+ }
+
+ @Test // GH-3757
+ public void findByShouldReportExists() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ boolean exists = repository.findBy(person.lastname.eq(oliver.getLastname()),
+ FluentQuery.FetchableFluentQuery::exists);
+ assertThat(exists).isTrue();
+
+ probe = new Person();
+ probe.setLastname("foo");
+
+ exists = repository.findBy(person.lastname.eq("foo"), FluentQuery.FetchableFluentQuery::exists);
+ assertThat(exists).isFalse();
+ }
}
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 e16a4d7b8c..fbd5a495a8 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
@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.repository.support;
+import static org.assertj.core.api.Assertions.*;
+
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -34,6 +36,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.PermissionDeniedDataAccessException;
+import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
@@ -49,6 +52,7 @@
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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -269,12 +273,11 @@ public void queryShouldTerminateWithUnsupportedOperationOnJoinWithNoResults() {
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
- ;
+
operations.save(person2) //
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
- ;
Flux result = new ReactiveSpringDataMongodbQuery<>(operations, Person.class).where()
.join(person.coworker, QUser.user).on(QUser.user.username.eq("does-not-exist")).fetch();
@@ -330,4 +333,112 @@ protected Mono doGetDatabase() {
.expectError(PermissionDeniedDataAccessException.class) //
.verify();
}
+
+ @Test // GH-3757
+ public void findByShouldReturnFirstResult() {
+
+ repository.findBy(person.firstname.eq(oliver.getFirstname()), FluentQuery.ReactiveFluentQuery::first) //
+ .as(StepVerifier::create) //
+ .expectNext(oliver) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ public void findByShouldReturnOneResult() {
+
+ repository.findBy(person.firstname.eq(oliver.getFirstname()), FluentQuery.ReactiveFluentQuery::one) //
+ .as(StepVerifier::create) //
+ .expectNext(oliver) //
+ .verifyComplete();
+
+ repository.findBy(person.lastname.eq(oliver.getLastname()), FluentQuery.ReactiveFluentQuery::one) //
+ .as(StepVerifier::create) //
+ .verifyError(IncorrectResultSizeDataAccessException.class);
+ }
+
+ @Test // GH-3757
+ public void findByShouldReturnAll() {
+
+ repository.findBy(person.lastname.eq(oliver.getLastname()), FluentQuery.ReactiveFluentQuery::all) //
+ .as(StepVerifier::create) //
+ .expectNextCount(2) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ public void findByShouldApplySortAll() {
+
+ repository.findBy(person.lastname.eq(oliver.getLastname()), it -> it.sortBy(Sort.by("firstname")).all()) //
+ .as(StepVerifier::create) //
+ .expectNext(dave, oliver) //
+ .verifyComplete();
+
+ repository
+ .findBy(person.lastname.eq(oliver.getLastname()), it -> it.sortBy(Sort.by(Direction.DESC, "firstname")).all()) //
+ .as(StepVerifier::create) //
+ .expectNext(oliver, dave) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ public void findByShouldApplyProjection() {
+
+ repository.findBy(person.lastname.eq(oliver.getLastname()), it -> it.project("firstname").first()) //
+ .as(StepVerifier::create) //
+ .assertNext(it -> {
+
+ assertThat(it.getFirstname()).isNotNull();
+ assertThat(it.getLastname()).isNull();
+ }).verifyComplete();
+ }
+
+ @Test // GH-3757
+ public void findByShouldApplyPagination() {
+
+ repository
+ .findBy(person.lastname.eq(oliver.getLastname()), it -> it.page(PageRequest.of(0, 1, Sort.by("firstname")))) //
+ .as(StepVerifier::create) //
+ .assertNext(it -> {
+
+ assertThat(it.getTotalElements()).isEqualTo(2);
+ assertThat(it.getContent()).contains(dave);
+ }).verifyComplete();
+
+ repository
+ .findBy(person.lastname.eq(oliver.getLastname()), it -> it.page(PageRequest.of(1, 1, Sort.by("firstname")))) //
+ .as(StepVerifier::create) //
+ .assertNext(it -> {
+
+ assertThat(it.getTotalElements()).isEqualTo(2);
+ assertThat(it.getContent()).contains(oliver);
+ }).verifyComplete();
+ }
+
+ @Test // GH-3757
+ public void findByShouldCount() {
+
+ repository.findBy(person.lastname.eq(oliver.getLastname()), FluentQuery.ReactiveFluentQuery::count) //
+ .as(StepVerifier::create) //
+ .expectNext(2L) //
+ .verifyComplete();
+
+ repository.findBy(person.lastname.eq("foo"), FluentQuery.ReactiveFluentQuery::count) //
+ .as(StepVerifier::create) //
+ .expectNext(0L) //
+ .verifyComplete();
+ }
+
+ @Test // GH-3757
+ public void findByShouldReportExists() {
+
+ repository.findBy(person.lastname.eq(oliver.getLastname()), FluentQuery.ReactiveFluentQuery::exists) //
+ .as(StepVerifier::create) //
+ .expectNext(true) //
+ .verifyComplete();
+
+ repository.findBy(person.lastname.eq("foo"), FluentQuery.ReactiveFluentQuery::exists) //
+ .as(StepVerifier::create) //
+ .expectNext(false) //
+ .verifyComplete();
+ }
}
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 61cd78ea93..f5e14fdf70 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
@@ -31,10 +31,13 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Example;
+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.Sort;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
@@ -51,6 +54,7 @@
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
import org.springframework.data.mongodb.test.util.Template;
+import org.springframework.data.repository.query.FluentQuery;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.support.TransactionTemplate;
@@ -459,6 +463,126 @@ void deleteAllByIds() {
.hasSize(all.size() - 2).doesNotContain(dave, carter);
}
+ @Test // GH-3757
+ void findByShouldReturnFirstResult() {
+
+ Person probe = new Person();
+ probe.setFirstname(oliver.getFirstname());
+
+ Person result = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::firstValue);
+
+ assertThat(result).isEqualTo(oliver);
+ }
+
+ @Test // GH-3757
+ void findByShouldReturnOneResult() {
+
+ Person probe = new Person();
+ probe.setFirstname(oliver.getFirstname());
+
+ Person result = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::oneValue);
+
+ assertThat(result).isEqualTo(oliver);
+
+ Person probeByLastname = new Person();
+ probeByLastname.setLastname(oliver.getLastname());
+
+ assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(
+ () -> repository.findBy(Example.of(probeByLastname, getMatcher()), FluentQuery.FetchableFluentQuery::one));
+ }
+
+ @Test // GH-3757
+ void findByShouldReturnAll() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ List result = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::all);
+
+ assertThat(result).hasSize(2);
+ }
+
+ @Test // GH-3757
+ void findByShouldApplySortAll() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ List result = repository.findBy(Example.of(probe, getMatcher()),
+ it -> it.sortBy(Sort.by("firstname")).all());
+ assertThat(result).containsSequence(dave, oliver);
+
+ result = repository.findBy(Example.of(probe, getMatcher()),
+ it -> it.sortBy(Sort.by(Sort.Direction.DESC, "firstname")).all());
+ assertThat(result).containsSequence(oliver, dave);
+ }
+
+ @Test // GH-3757
+ void findByShouldApplyProjection() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ Person result = repository.findBy(Example.of(probe, getMatcher()), it -> it.project("firstname").firstValue());
+
+ assertThat(result.getFirstname()).isNotNull();
+ assertThat(result.getLastname()).isNull();
+ }
+
+ @Test // GH-3757
+ void findByShouldApplyPagination() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ Page first = repository.findBy(Example.of(probe, getMatcher()),
+ it -> it.page(PageRequest.of(0, 1, Sort.by("firstname"))));
+ assertThat(first.getTotalElements()).isEqualTo(2);
+ assertThat(first.getContent()).contains(dave);
+
+ Page next = repository.findBy(Example.of(probe, getMatcher()),
+ it -> it.page(PageRequest.of(1, 1, Sort.by("firstname"))));
+
+ assertThat(next.getTotalElements()).isEqualTo(2);
+ assertThat(next.getContent()).contains(oliver);
+ }
+
+ @Test // GH-3757
+ void findByShouldCount() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ long count = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::count);
+ assertThat(count).isEqualTo(2L);
+
+ probe = new Person();
+ probe.setLastname("foo");
+
+ count = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::count);
+ assertThat(count).isEqualTo(0L);
+ }
+
+ @Test // GH-3757
+ void findByShouldReportExists() {
+
+ Person probe = new Person();
+ probe.setLastname(oliver.getLastname());
+
+ boolean exists = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::exists);
+ assertThat(exists).isTrue();
+
+ probe = new Person();
+ probe.setLastname("foo");
+
+ exists = repository.findBy(Example.of(probe, getMatcher()), FluentQuery.FetchableFluentQuery::exists);
+ assertThat(exists).isFalse();
+ }
+
+ private ExampleMatcher getMatcher() {
+ return matching().withIgnorePaths("age", "createdAt", "sex", "email", "id");
+ }
+
private void assertThatAllReferencePersonsWereStoredCorrectly(Map references, List saved) {
for (Person person : saved) {