diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java
deleted file mode 100644
index 3f0d281cc..000000000
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * Copyright 2018-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.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import org.bson.BsonJavaScript;
-import org.bson.BsonRegularExpression;
-import org.bson.Document;
-import org.bson.types.ObjectId;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
-
-import com.mongodb.DBRef;
-import com.querydsl.core.types.*;
-import com.querydsl.mongodb.MongodbOps;
-
-/**
- *
- * Serializes the given Querydsl query to a Document query for MongoDB.
- *
- *
- * Original implementation source {@link com.querydsl.mongodb.MongodbSerializer} by {@literal The Querydsl Team}
- * (http://www.querydsl.com/team ) licensed under the Apache License, Version
- * 2.0.
- *
- * Modified to use {@link Document} instead of {@link com.mongodb.DBObject}, updated nullable types and code format. Use
- * Bson specific types and add {@link QuerydslMongoOps#NO_MATCH}.
- *
- * @author laimw
- * @author Mark Paluch
- * @author Christoph Strobl
- * @author Mikhail Kaduchka
- * @author Enrique Leon Molina
- * @since 2.1
- */
-abstract class MongodbDocumentSerializer implements Visitor {
-
- @Nullable
- Object handle(Expression> expression) {
- return expression.accept(this, null);
- }
-
- /**
- * Create the MongoDB specific query document.
- *
- * @param predicate must not be {@literal null}.
- * @return empty {@link Document} by default.
- */
- Document toQuery(Predicate predicate) {
-
- Object value = handle(predicate);
-
- if (value == null) {
- return new Document();
- }
-
- Assert.isInstanceOf(Document.class, value,
- () -> String.format("Invalid type. Expected Document but found %s", value.getClass()));
-
- return (Document) value;
- }
-
- /**
- * Create the MongoDB specific sort document.
- *
- * @param orderBys must not be {@literal null}.
- * @return empty {@link Document} by default.
- */
- Document toSort(List> orderBys) {
-
- Document sort = new Document();
-
- orderBys.forEach(orderSpecifier -> {
-
- Object key = orderSpecifier.getTarget().accept(this, null);
-
- Assert.notNull(key, () -> String.format("Mapped sort key for %s must not be null!", orderSpecifier));
- sort.append(key.toString(), orderSpecifier.getOrder() == Order.ASC ? 1 : -1);
- });
-
- return sort;
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.Constant, java.lang.Void)
- */
- @Override
- public Object visit(Constant> expr, Void context) {
-
- if (!Enum.class.isAssignableFrom(expr.getType())) {
- return expr.getConstant();
- }
-
- @SuppressWarnings("unchecked") // Guarded by previous check
- Constant extends Enum>> expectedExpr = (Constant extends Enum>>) expr;
- return expectedExpr.getConstant().name();
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.TemplateExpression, java.lang.Void)
- */
- @Override
- public Object visit(TemplateExpression> expr, Void context) {
- throw new UnsupportedOperationException();
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.FactoryExpression, java.lang.Void)
- */
- @Override
- public Object visit(FactoryExpression> expr, Void context) {
- throw new UnsupportedOperationException();
- }
-
- protected String asDBKey(Operation> expr, int index) {
-
- String key = (String) asDBValue(expr, index);
-
- Assert.hasText(key, () -> String.format("Mapped key must not be null nor empty for expression %s.", expr));
- return key;
- }
-
- @Nullable
- protected Object asDBValue(Operation> expr, int index) {
- return expr.getArg(index).accept(this, null);
- }
-
- private String regexValue(Operation> expr, int index) {
-
- Object value = expr.getArg(index).accept(this, null);
-
- Assert.notNull(value, () -> String.format("Regex for %s must not be null.", expr));
- return Pattern.quote(value.toString());
- }
-
- protected Document asDocument(String key, @Nullable Object value) {
- return new Document(key, value);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public Object visit(Operation> expr, Void context) {
-
- Operator op = expr.getOperator();
- if (op == Ops.EQ) {
-
- if (expr.getArg(0) instanceof Operation) {
- Operation> lhs = (Operation>) expr.getArg(0);
- if (lhs.getOperator() == Ops.COL_SIZE || lhs.getOperator() == Ops.ARRAY_SIZE) {
- return asDocument(asDBKey(lhs, 0), asDocument("$size", asDBValue(expr, 1)));
- } else {
- throw new UnsupportedOperationException("Illegal operation " + expr);
- }
- } else if (expr.getArg(0) instanceof Path) {
- Path> path = (Path>) expr.getArg(0);
- Constant> constant = (Constant>) expr.getArg(1);
- return asDocument(asDBKey(expr, 0), convert(path, constant));
- }
- } else if (op == Ops.STRING_IS_EMPTY) {
- return asDocument(asDBKey(expr, 0), "");
- } else if (op == Ops.AND) {
-
- Queue> pendingDocuments = collectConnectorArgs("$and", expr);
- List> unmergeableDocuments = new ArrayList<>();
- List> generatedDocuments = new ArrayList<>();
-
- while (!pendingDocuments.isEmpty()) {
-
- Map lhs = pendingDocuments.poll();
-
- for (Map rhs : pendingDocuments) {
- Set lhs2 = new LinkedHashSet<>(lhs.keySet());
- lhs2.retainAll(rhs.keySet());
- if (lhs2.isEmpty()) {
- lhs.putAll(rhs);
- } else {
- unmergeableDocuments.add(rhs);
- }
- }
-
- generatedDocuments.add(lhs);
- pendingDocuments = new LinkedList<>(unmergeableDocuments);
- unmergeableDocuments = new LinkedList<>();
- }
-
- return generatedDocuments.size() == 1 ? generatedDocuments.get(0) : asDocument("$and", generatedDocuments);
- } else if (op == Ops.NOT) {
- // Handle the not's child
- Operation> subOperation = (Operation>) expr.getArg(0);
- Operator subOp = subOperation.getOperator();
- if (subOp == Ops.IN) {
- return visit(
- ExpressionUtils.operation(Boolean.class, Ops.NOT_IN, subOperation.getArg(0), subOperation.getArg(1)),
- context);
- } else {
- Document arg = (Document) handle(expr.getArg(0));
- return negate(arg);
- }
-
- } else if (op == Ops.OR) {
- return asDocument("$or", collectConnectorArgs("$or", expr));
- } else if (op == Ops.NE) {
-
- Path> path = (Path>) expr.getArg(0);
- Constant> constant = (Constant>) expr.getArg(1);
- return asDocument(asDBKey(expr, 0), asDocument("$ne", convert(path, constant)));
-
- } else if (op == Ops.STARTS_WITH) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1)));
- } else if (op == Ops.STARTS_WITH_IC) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1), "i"));
- } else if (op == Ops.ENDS_WITH) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$"));
- } else if (op == Ops.ENDS_WITH_IC) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$", "i"));
- } else if (op == Ops.EQ_IGNORE_CASE) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1) + "$", "i"));
- } else if (op == Ops.STRING_CONTAINS) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*"));
- } else if (op == Ops.STRING_CONTAINS_IC) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*", "i"));
- } else if (op == Ops.MATCHES) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString()));
- } else if (op == Ops.MATCHES_IC) {
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString(), "i"));
- } else if (op == Ops.LIKE) {
-
- String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
- return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regex));
- } else if (op == Ops.BETWEEN) {
-
- Document value = new Document("$gte", asDBValue(expr, 1));
- value.append("$lte", asDBValue(expr, 2));
- return asDocument(asDBKey(expr, 0), value);
- } else if (op == Ops.IN) {
-
- int constIndex = 0;
- int exprIndex = 1;
- if (expr.getArg(1) instanceof Constant>) {
- constIndex = 1;
- exprIndex = 0;
- }
- if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
- @SuppressWarnings("unchecked") // guarded by previous check
- Collection> values = ((Constant extends Collection>>) expr.getArg(constIndex)).getConstant();
- return asDocument(asDBKey(expr, exprIndex), asDocument("$in", values));
- } else {
- Path> path = (Path>) expr.getArg(exprIndex);
- Constant> constant = (Constant>) expr.getArg(constIndex);
- return asDocument(asDBKey(expr, exprIndex), convert(path, constant));
- }
- } else if (op == Ops.NOT_IN) {
-
- int constIndex = 0;
- int exprIndex = 1;
- if (expr.getArg(1) instanceof Constant>) {
-
- constIndex = 1;
- exprIndex = 0;
- }
- if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
-
- @SuppressWarnings("unchecked") // guarded by previous check
- Collection> values = ((Constant extends Collection>>) expr.getArg(constIndex)).getConstant();
- return asDocument(asDBKey(expr, exprIndex), asDocument("$nin", values));
- } else {
-
- Path> path = (Path>) expr.getArg(exprIndex);
- Constant> constant = (Constant>) expr.getArg(constIndex);
- return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant)));
- }
- } else if (op == Ops.COL_IS_EMPTY) {
-
- List list = new ArrayList<>(2);
- list.add(asDocument(asDBKey(expr, 0), new ArrayList()));
- list.add(asDocument(asDBKey(expr, 0), asDocument("$exists", false)));
- return asDocument("$or", list);
- } else if (op == Ops.LT) {
- return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1)));
- } else if (op == Ops.GT) {
- return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1)));
- } else if (op == Ops.LOE) {
- return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1)));
- } else if (op == Ops.GOE) {
- return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1)));
- } else if (op == Ops.IS_NULL) {
- return asDocument(asDBKey(expr, 0), asDocument("$exists", false));
- } else if (op == Ops.IS_NOT_NULL) {
- return asDocument(asDBKey(expr, 0), asDocument("$exists", true));
- } else if (op == Ops.CONTAINS_KEY) {
-
- Path> path = (Path>) expr.getArg(0);
- Expression> key = expr.getArg(1);
- return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true));
- } else if (op == MongodbOps.NEAR) {
- return asDocument(asDBKey(expr, 0), asDocument("$near", asDBValue(expr, 1)));
- } else if (op == MongodbOps.NEAR_SPHERE) {
- return asDocument(asDBKey(expr, 0), asDocument("$nearSphere", asDBValue(expr, 1)));
- } else if (op == MongodbOps.ELEM_MATCH) {
- return asDocument(asDBKey(expr, 0), asDocument("$elemMatch", asDBValue(expr, 1)));
- } else if (op == QuerydslMongoOps.NO_MATCH) {
- return new Document("$where", new BsonJavaScript("function() { return false }"));
- }
-
- throw new UnsupportedOperationException("Illegal operation " + expr);
- }
-
- private Object negate(Document arg) {
-
- List list = new ArrayList<>();
- for (Map.Entry entry : arg.entrySet()) {
-
- if (entry.getKey().equals("$or")) {
- list.add(asDocument("$nor", entry.getValue()));
- } else if (entry.getKey().equals("$and")) {
-
- List list2 = new ArrayList<>();
- for (Object o : ((Collection) entry.getValue())) {
- list2.add(negate((Document) o));
- }
- list.add(asDocument("$or", list2));
- } else if (entry.getValue() instanceof Pattern || entry.getValue() instanceof BsonRegularExpression) {
- list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue())));
- } else if (entry.getValue() instanceof Document) {
- list.add(negate(entry.getKey(), (Document) entry.getValue()));
- } else {
- list.add(asDocument(entry.getKey(), asDocument("$ne", entry.getValue())));
- }
- }
- return list.size() == 1 ? list.get(0) : asDocument("$or", list);
- }
-
- private Object negate(String key, Document value) {
-
- if (value.size() == 1) {
- return asDocument(key, asDocument("$not", value));
- } else {
-
- List list2 = new ArrayList<>();
- for (Map.Entry entry2 : value.entrySet()) {
- list2.add(asDocument(key, asDocument("$not", asDocument(entry2.getKey(), entry2.getValue()))));
- }
-
- return asDocument("$or", list2);
- }
- }
-
- protected Object convert(Path> property, Constant> constant) {
-
- if (isReference(property)) {
- return asReference(constant.getConstant());
- } else if (isId(property)) {
-
- if (isReference(property.getMetadata().getParent())) {
- return asReferenceKey(property.getMetadata().getParent().getType(), constant.getConstant());
- } else if (constant.getType().equals(String.class) && isImplicitObjectIdConversion()) {
-
- String id = (String) constant.getConstant();
- return ObjectId.isValid(id) ? new ObjectId(id) : id;
- }
- }
- return visit(constant, null);
- }
-
- protected boolean isImplicitObjectIdConversion() {
- return true;
- }
-
- protected DBRef asReferenceKey(Class> entity, Object id) {
- // TODO override in subclass
- throw new UnsupportedOperationException();
- }
-
- protected abstract DBRef asReference(Object constant);
-
- protected abstract boolean isReference(@Nullable Path> arg);
-
- protected boolean isId(Path> arg) {
- // TODO override in subclass
- return false;
- }
-
- @Override
- public String visit(Path> expr, Void context) {
-
- PathMetadata metadata = expr.getMetadata();
-
- if (metadata.getParent() != null) {
-
- Path> parent = metadata.getParent();
- if (parent.getMetadata().getPathType() == PathType.DELEGATE) {
- parent = parent.getMetadata().getParent();
- }
- if (metadata.getPathType() == PathType.COLLECTION_ANY) {
- return visit(parent, context);
- } else if (parent.getMetadata().getPathType() != PathType.VARIABLE) {
-
- String rv = getKeyForPath(expr, metadata);
- String parentStr = visit(parent, context);
- return rv != null ? parentStr + "." + rv : parentStr;
- }
- }
- return getKeyForPath(expr, metadata);
- }
-
- protected String getKeyForPath(Path> expr, PathMetadata metadata) {
- return metadata.getElement().toString();
- }
-
- @Override
- public Object visit(SubQueryExpression> expr, Void context) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object visit(ParamExpression> expr, Void context) {
- throw new UnsupportedOperationException();
- }
-
- private LinkedList> collectConnectorArgs(String operator, Operation> operation) {
-
- LinkedList> pendingDocuments = new LinkedList<>();
- for (Expression> exp : operation.getArgs()) {
- Map document = (Map) handle(exp);
- if (document.keySet().size() == 1 && document.containsKey(operator)) {
- pendingDocuments.addAll((Collection>) document.get(operator));
- } else {
- pendingDocuments.add(document);
- }
- }
- return pendingDocuments;
-
- }
-}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java
index 422eea577..b255d2027 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java
@@ -34,6 +34,8 @@ import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.ParamExpression;
import com.querydsl.core.types.Predicate;
+import com.querydsl.mongodb.document.AbstractMongodbQuery;
+import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* {@code QuerydslAbstractMongodbQuery} provides a base class for general Querydsl query implementation.
@@ -49,8 +51,12 @@ import com.querydsl.core.types.Predicate;
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
+ * @deprecated since 3.3, use Querydsl's {@link AbstractMongodbQuery} directly. This class is deprecated for removal
+ * with the next major release.
*/
+@Deprecated
public abstract class QuerydslAbstractMongodbQuery>
+ extends AbstractMongodbQuery
implements SimpleQuery {
private static final JsonWriterSettings JSON_WRITER_SETTINGS = JsonWriterSettings.builder().outputMode(JsonMode.SHELL)
@@ -67,6 +73,8 @@ public abstract class QuerydslAbstractMongodbQuery((Q) this, new DefaultQueryMetadata(), false);
this.serializer = serializer;
}
@@ -158,22 +166,6 @@ public abstract class QuerydslAbstractMongodbQuery
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java
index 3ba84a2dd..b6935a5e8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java
@@ -37,7 +37,10 @@ import com.querydsl.mongodb.MongodbOps;
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
+ * @deprecated since 3.3, use Querydsl's {@link com.querydsl.mongodb.document.AnyEmbeddedBuilder} directly. This class
+ * is deprecated for removal with the next major release.
*/
+@Deprecated
public class QuerydslAnyEmbeddedBuilder, K> {
private final QueryMixin queryMixin;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java
deleted file mode 100644
index 36057d58c..000000000
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright 2018-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.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithProjection;
-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.lang.Nullable;
-import org.springframework.util.LinkedMultiValueMap;
-
-import com.mysema.commons.lang.CloseableIterator;
-import com.querydsl.core.Fetchable;
-import com.querydsl.core.JoinExpression;
-import com.querydsl.core.QueryMetadata;
-import com.querydsl.core.QueryModifiers;
-import com.querydsl.core.QueryResults;
-import com.querydsl.core.types.Expression;
-import com.querydsl.core.types.ExpressionUtils;
-import com.querydsl.core.types.Operation;
-import com.querydsl.core.types.OrderSpecifier;
-import com.querydsl.core.types.Path;
-import com.querydsl.core.types.Predicate;
-import com.querydsl.core.types.dsl.CollectionPathBase;
-
-/**
- * {@link Fetchable} MongoDB query with utilizing {@link MongoOperations} for command execution.
- *
- * @param result type
- * @param concrete subtype
- * @author Mark Paluch
- * @author Christoph Strobl
- * @since 2.1
- */
-abstract class QuerydslFetchableMongodbQuery>
- extends QuerydslAbstractMongodbQuery implements Fetchable {
-
- private final Class entityClass;
- private final String collection;
- private final MongoOperations mongoOperations;
- private final FindWithProjection find;
-
- QuerydslFetchableMongodbQuery(MongodbDocumentSerializer serializer, Class extends K> entityClass, String collection,
- MongoOperations mongoOperations) {
-
- super(serializer);
-
- this.entityClass = (Class) entityClass;
- this.collection = collection;
- this.mongoOperations = mongoOperations;
- find = mongoOperations.query(this.entityClass).inCollection(collection);
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.Fetchable#iterable()
- */
- @Override
- public CloseableIterator iterate() {
-
- org.springframework.data.util.CloseableIterator extends K> stream = mongoOperations.stream(createQuery(),
- entityClass, collection);
-
- return new CloseableIterator() {
-
- @Override
- public boolean hasNext() {
- return stream.hasNext();
- }
-
- @Override
- public K next() {
- return stream.next();
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Cannot remove from iterator while streaming data.");
- }
-
- @Override
- public void close() {
- stream.close();
- }
- };
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.Fetchable#fetch()
- */
- @Override
- public List fetch() {
- return find.matching(createQuery()).all();
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.Fetchable#fetchFirst()
- */
- @Override
- public K fetchFirst() {
- return find.matching(createQuery()).firstValue();
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.Fetchable#fetchOne()
- */
- @Override
- public K fetchOne() {
- return find.matching(createQuery()).oneValue();
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.Fetchable#fetchResults()
- */
- @Override
- public QueryResults fetchResults() {
-
- long total = fetchCount();
- return total > 0L ? new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total)
- : QueryResults.emptyResults();
- }
-
- /*
- * (non-Javadoc)
- * @see com.querydsl.core.Fetchable#fetchCount()
- */
- @Override
- public long fetchCount() {
- return find.matching(Query.of(createQuery()).skip(-1).limit(-1)).count();
- }
-
- /**
- * Define a join.
- *
- * @param ref reference
- * @param target join target
- * @return new instance of {@link QuerydslJoinBuilder}.
- */
- public QuerydslJoinBuilder join(Path ref, Path target) {
- return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
- }
-
- /**
- * Define a join.
- *
- * @param ref reference
- * @param target join target
- * @return new instance of {@link QuerydslJoinBuilder}.
- */
- public QuerydslJoinBuilder join(CollectionPathBase, T, ?> ref, Path target) {
- return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
- }
-
- /**
- * Define a constraint for an embedded object.
- *
- * @param collection collection must not be {@literal null}.
- * @param target target must not be {@literal null}.
- * @return new instance of {@link QuerydslAnyEmbeddedBuilder}.
- */
- public QuerydslAnyEmbeddedBuilder anyEmbedded(Path extends Collection> collection, Path target) {
- return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection);
- }
-
- protected org.springframework.data.mongodb.core.query.Query createQuery() {
-
- QueryMetadata metadata = getQueryMixin().getMetadata();
-
- return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
- metadata.getOrderBy());
- }
-
- protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate filter,
- @Nullable Expression> projection, QueryModifiers modifiers, List> orderBy) {
-
- BasicQuery basicQuery = new BasicQuery(createQuery(filter), createProjection(projection));
-
- Integer limit = modifiers.getLimitAsInteger();
- Integer offset = modifiers.getOffsetAsInteger();
-
- if (limit != null) {
- basicQuery.limit(limit);
- }
- if (offset != null) {
- basicQuery.skip(offset);
- }
- if (orderBy.size() > 0) {
- basicQuery.setSortObject(createSort(orderBy));
- }
-
- return basicQuery;
- }
-
- @Nullable
- protected Predicate createFilter(QueryMetadata metadata) {
-
- Predicate filter;
- if (!metadata.getJoins().isEmpty()) {
- filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata));
- } else {
- filter = metadata.getWhere();
- }
- return filter;
- }
-
- @SuppressWarnings("unchecked")
- @Nullable
- protected Predicate createJoinFilter(QueryMetadata metadata) {
-
- LinkedMultiValueMap, Predicate> predicates = new LinkedMultiValueMap<>();
- List joins = metadata.getJoins();
-
- for (int i = joins.size() - 1; i >= 0; i--) {
-
- JoinExpression join = joins.get(i);
- Path> source = (Path) ((Operation>) join.getTarget()).getArg(0);
- Path> target = (Path) ((Operation>) join.getTarget()).getArg(1);
- Collection extraFilters = predicates.get(target.getRoot());
- Predicate filter = ExpressionUtils.allOf(join.getCondition(), allOf(extraFilters));
-
- List extends Object> ids = getIds(target.getType(), filter);
-
- if (ids.isEmpty()) {
- return ExpressionUtils.predicate(QuerydslMongoOps.NO_MATCH, source);
- }
-
- Path> path = ExpressionUtils.path(String.class, source, "$id");
- predicates.add(source.getRoot(), ExpressionUtils.in((Path) path, ids));
- }
-
- Path> source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0);
- return allOf(predicates.get(source.getRoot()));
- }
-
- private Predicate allOf(Collection predicates) {
- return predicates != null ? ExpressionUtils.allOf(predicates) : null;
- }
-
- /**
- * Fetch the list of ids matching a given condition.
- *
- * @param targetType must not be {@literal null}.
- * @param condition must not be {@literal null}.
- * @return empty {@link List} if none found.
- */
- protected List getIds(Class> targetType, Predicate condition) {
-
- Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList());
- return mongoOperations.findDistinct(query, "_id", targetType, Object.class);
- }
-}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java
deleted file mode 100644
index 344ad0882..000000000
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018-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 com.querydsl.core.JoinType;
-import com.querydsl.core.support.QueryMixin;
-import com.querydsl.core.types.ExpressionUtils;
-import com.querydsl.core.types.Path;
-import com.querydsl.core.types.Predicate;
-
-/**
- * {@code QuerydslJoinBuilder} is a builder for join constraints.
- *
- * Original implementation source {@link com.querydsl.mongodb.JoinBuilder} by {@literal The Querydsl Team}
- * (http://www.querydsl.com/team ) licensed under the Apache License, Version
- * 2.0.
- *
- * Modified for usage with {@link QuerydslAbstractMongodbQuery}.
- *
- * @param
- * @param
- * @author tiwe
- * @author Mark Paluch
- * @author Christoph Strobl
- * @since 2.1
- */
-public class QuerydslJoinBuilder, K, T> {
-
- private final QueryMixin queryMixin;
- private final Path> ref;
- private final Path target;
-
- QuerydslJoinBuilder(QueryMixin queryMixin, Path> ref, Path target) {
-
- this.queryMixin = queryMixin;
- this.ref = ref;
- this.target = target;
- }
-
- /**
- * Add the given join conditions.
- *
- * @param conditions must not be {@literal null}.
- * @return the target {@link QueryMixin}.
- * @see QueryMixin#on(Predicate)
- */
- @SuppressWarnings("unchecked")
- public Q on(Predicate... conditions) {
-
- queryMixin.addJoin(JoinType.JOIN, ExpressionUtils.as((Path) ref, target));
- queryMixin.on(conditions);
- return queryMixin.getSelf();
- }
-}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java
deleted file mode 100644
index 0c695afd0..000000000
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2018-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 com.querydsl.core.types.Operator;
-
-/**
- * Spring Data specific {@link Operator operators} for usage with Querydsl and MongoDB.
- *
- * @author Christoph Strobl
- * @since 2.1
- */
-enum QuerydslMongoOps implements Operator {
-
- /**
- * {@link Operator} always evaluating to {@literal false}.
- */
- NO_MATCH(Boolean.class);
-
- private final Class> type;
-
- QuerydslMongoOps(Class> type) {
- this.type = type;
- }
-
- @Override
- public Class> getType() {
- return type;
- }
-}
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 4162a7948..8b30e585e 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
@@ -22,6 +22,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import org.bson.Document;
+
+import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
@@ -40,19 +43,21 @@ import com.querydsl.core.types.Operation;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
-import com.querydsl.core.types.dsl.CollectionPathBase;
+import com.querydsl.mongodb.MongodbOps;
+import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* MongoDB query with utilizing {@link ReactiveMongoOperations} for command execution.
*
+ * @implNote This class uses {@link MongoOperations} to directly convert documents into the target entity type. Also, we
+ * want entites to participate in lifecycle events and entity callbacks.
* @param result type
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.2
*/
-class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery> {
+class ReactiveSpringDataMongodbQuery extends SpringDataMongodbQuerySupport> {
- private final Class entityClass;
private final ReactiveMongoOperations mongoOperations;
private final FindWithProjection find;
@@ -60,15 +65,15 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery entityClass, @Nullable String collection) {
super(serializer);
- this.entityClass = (Class) entityClass;
this.mongoOperations = mongoOperations;
- this.find = StringUtils.hasText(collection) ? mongoOperations.query(this.entityClass).inCollection(collection)
- : mongoOperations.query(this.entityClass);
+ this.find = StringUtils.hasText(collection) ? mongoOperations.query((Class) entityClass).inCollection(collection)
+ : mongoOperations.query((Class) entityClass);
}
/**
@@ -99,48 +104,11 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery find.matching(it).count());
}
- /**
- * Define a join.
- *
- * @param ref reference
- * @param target join target
- * @return new instance of {@link QuerydslJoinBuilder}.
- */
- QuerydslJoinBuilder, K, T> join(Path ref, Path target) {
- return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
- }
-
- /**
- * Define a join.
- *
- * @param ref reference
- * @param target join target
- * @return new instance of {@link QuerydslJoinBuilder}.
- */
- QuerydslJoinBuilder, K, T> join(CollectionPathBase, T, ?> ref,
- Path target) {
-
- return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
- }
-
- /**
- * Define a constraint for an embedded object.
- *
- * @param collection collection must not be {@literal null}.
- * @param target target must not be {@literal null}.
- * @return new instance of {@link QuerydslAnyEmbeddedBuilder}.
- */
- QuerydslAnyEmbeddedBuilder, K> anyEmbedded(
- Path extends Collection> collection, Path target) {
-
- return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection);
- }
-
protected Mono createQuery() {
QueryMetadata metadata = getQueryMixin().getMetadata();
- return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
+ return createQuery(createReactiveFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
metadata.getOrderBy());
}
@@ -160,7 +128,8 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery {
- BasicQuery basicQuery = new BasicQuery(it, createProjection(projection));
+ Document fields = createProjection(projection);
+ BasicQuery basicQuery = new BasicQuery(it, fields == null ? new Document() : fields);
Integer limit = modifiers.getLimitAsInteger();
Integer offset = modifiers.getOffsetAsInteger();
@@ -179,11 +148,11 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery createFilter(QueryMetadata metadata) {
+ protected Mono createReactiveFilter(QueryMetadata metadata) {
if (!metadata.getJoins().isEmpty()) {
- return createJoinFilter(metadata).map(it -> ExpressionUtils.allOf(metadata.getWhere(), it))
+ return createReactiveJoinFilter(metadata).map(it -> ExpressionUtils.allOf(metadata.getWhere(), it))
.switchIfEmpty(Mono.justOrEmpty(metadata.getWhere()));
}
@@ -197,7 +166,7 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery createJoinFilter(QueryMetadata metadata) {
+ protected Mono createReactiveJoinFilter(QueryMetadata metadata) {
MultiValueMap, Mono> predicates = new LinkedMultiValueMap<>();
List joins = metadata.getJoins();
@@ -230,7 +199,7 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0);
return allOf(predicates.get(source.getRoot())).onErrorResume(NoMatchException.class,
- e -> Mono.just(ExpressionUtils.predicate(QuerydslMongoOps.NO_MATCH, e.source)));
+ e -> Mono.just(ExpressionUtils.predicate(MongodbOps.NO_MATCH, e.source)));
}
private Mono allOf(@Nullable Collection> predicates) {
@@ -246,8 +215,8 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery getIds(Class> targetType, Mono condition) {
- return condition.flatMapMany(it -> getIds(targetType, it))
- .switchIfEmpty(Flux.defer(() -> getIds(targetType, (Predicate) null)));
+ return condition.flatMapMany(it -> getJoinIds(targetType, it))
+ .switchIfEmpty(Flux.defer(() -> getJoinIds(targetType, (Predicate) null)));
}
/**
@@ -257,12 +226,18 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery getIds(Class> targetType, @Nullable Predicate condition) {
+ protected Flux getJoinIds(Class> targetType, @Nullable Predicate condition) {
return createQuery(Mono.justOrEmpty(condition), null, QueryModifiers.EMPTY, Collections.emptyList())
.flatMapMany(query -> mongoOperations.findDistinct(query, "_id", targetType, Object.class));
}
+ @Override
+ protected List getIds(Class> aClass, Predicate predicate) {
+ throw new UnsupportedOperationException(
+ "Use create Flux getIds(Class> targetType, Mono condition)");
+ }
+
/**
* Marker exception to indicate no matches for a query using reference Id's.
*/
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 8a153d0c2..d62aa99c5 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
@@ -15,7 +15,27 @@
*/
package org.springframework.data.mongodb.repository.support;
+import java.util.Collections;
+import java.util.List;
+
+import org.bson.Document;
+
+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.lang.Nullable;
+
+import com.mysema.commons.lang.CloseableIterator;
+import com.mysema.commons.lang.EmptyCloseableIterator;
+import com.querydsl.core.Fetchable;
+import com.querydsl.core.QueryMetadata;
+import com.querydsl.core.QueryModifiers;
+import com.querydsl.core.QueryResults;
+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}
@@ -25,7 +45,13 @@ import org.springframework.data.mongodb.core.MongoOperations;
* @author Mark Paluch
* @author Christoph Strobl
*/
-public class SpringDataMongodbQuery extends QuerydslFetchableMongodbQuery> {
+public class SpringDataMongodbQuery extends SpringDataMongodbQuerySupport>
+ implements Fetchable {
+
+ private final Class entityClass;
+ private final String collection;
+ private final MongoOperations mongoOperations;
+ private final ExecutableFindOperation.FindWithProjection find;
/**
* Creates a new {@link SpringDataMongodbQuery}.
@@ -33,7 +59,7 @@ public class SpringDataMongodbQuery extends QuerydslFetchableMongodbQuery type) {
+ public SpringDataMongodbQuery(MongoOperations operations, Class extends T> type) {
this(operations, type, operations.getCollectionName(type));
}
@@ -44,9 +70,174 @@ public class SpringDataMongodbQuery extends QuerydslFetchableMongodbQuery type,
+ public SpringDataMongodbQuery(MongoOperations operations, Class extends T> type,
String collectionName) {
-
- super(new SpringDataMongodbSerializer(operations.getConverter()), type, collectionName, operations);
+ this(new SpringDataMongodbSerializer(operations.getConverter()), operations, type, collectionName);
}
+
+ private SpringDataMongodbQuery(MongodbDocumentSerializer serializer, MongoOperations operations,
+ Class extends T> type, String collectionName) {
+
+ super(serializer);
+
+ this.entityClass = (Class) type;
+ this.collection = collectionName;
+ this.mongoOperations = operations;
+ this.find = mongoOperations.query(this.entityClass).inCollection(collection);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#iterable()
+ */
+ @Override
+ public CloseableIterator iterate() {
+
+ try {
+ org.springframework.data.util.CloseableIterator extends T> stream = mongoOperations.stream(createQuery(),
+ entityClass, collection);
+
+ return new CloseableIterator() {
+
+ @Override
+ public boolean hasNext() {
+ return stream.hasNext();
+ }
+
+ @Override
+ public T next() {
+ return stream.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Cannot remove from iterator while streaming data.");
+ }
+
+ @Override
+ public void close() {
+ stream.close();
+ }
+ };
+ } catch (RuntimeException e) {
+ return handleException(e, new EmptyCloseableIterator<>());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#fetch()
+ */
+ @Override
+ public List fetch() {
+ try {
+ return find.matching(createQuery()).all();
+ } catch (RuntimeException e) {
+ return handleException(e, Collections.emptyList());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#fetchFirst()
+ */
+ @Override
+ public T fetchFirst() {
+ try {
+ return find.matching(createQuery()).firstValue();
+ } catch (RuntimeException e) {
+ return handleException(e, null);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#fetchOne()
+ */
+ @Override
+ public T fetchOne() {
+ try {
+ return find.matching(createQuery()).oneValue();
+ } catch (RuntimeException e) {
+ return handleException(e, null);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#fetchResults()
+ */
+ @Override
+ public QueryResults fetchResults() {
+
+ long total = fetchCount();
+ return total > 0L ? new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total)
+ : QueryResults.emptyResults();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.querydsl.core.Fetchable#fetchCount()
+ */
+ @Override
+ public long fetchCount() {
+ try {
+ return find.matching(Query.of(createQuery()).skip(-1).limit(-1)).count();
+ } catch (RuntimeException e) {
+ return handleException(e, 0L);
+ }
+ }
+
+ protected org.springframework.data.mongodb.core.query.Query createQuery() {
+
+ QueryMetadata metadata = getQueryMixin().getMetadata();
+
+ return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
+ metadata.getOrderBy());
+ }
+
+ protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate filter,
+ @Nullable Expression> projection, QueryModifiers modifiers, List> orderBy) {
+
+ Document fields = createProjection(projection);
+ BasicQuery basicQuery = new BasicQuery(createQuery(filter), fields == null ? new Document() : fields);
+
+ Integer limit = modifiers.getLimitAsInteger();
+ Integer offset = modifiers.getOffsetAsInteger();
+
+ if (limit != null) {
+ basicQuery.limit(limit);
+ }
+ if (offset != null) {
+ basicQuery.skip(offset);
+ }
+ if (orderBy.size() > 0) {
+ basicQuery.setSortObject(createSort(orderBy));
+ }
+
+ return basicQuery;
+ }
+
+ /**
+ * Fetch the list of ids matching a given condition.
+ *
+ * @param targetType must not be {@literal null}.
+ * @param condition must not be {@literal null}.
+ * @return empty {@link List} if none found.
+ */
+ protected List getIds(Class> targetType, Predicate condition) {
+
+ Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList());
+ return mongoOperations.findDistinct(query, "_id", targetType, Object.class);
+ }
+
+ private static T handleException(RuntimeException e, T defaultValue) {
+
+ if (e.getClass().getName().endsWith("$NoResults")) {
+ return defaultValue;
+ }
+
+ throw e;
+ }
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java
new file mode 100644
index 000000000..406019cf4
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java
@@ -0,0 +1,147 @@
+/*
+ * 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.List;
+
+import org.bson.Document;
+import org.bson.codecs.DocumentCodec;
+import org.bson.json.JsonMode;
+import org.bson.json.JsonWriterSettings;
+
+import org.springframework.beans.DirectFieldAccessor;
+
+import com.mongodb.MongoClientSettings;
+import com.querydsl.core.support.QueryMixin;
+import com.querydsl.core.types.OrderSpecifier;
+import com.querydsl.mongodb.document.AbstractMongodbQuery;
+import com.querydsl.mongodb.document.MongodbDocumentSerializer;
+
+/**
+ * Support query type to augment Spring Data-specific {@link #toString} representations and
+ * {@link org.springframework.data.domain.Sort} creation.
+ *
+ * @author Mark Paluch
+ * @since 3.3
+ */
+abstract class SpringDataMongodbQuerySupport>
+ extends AbstractMongodbQuery {
+
+ private final QueryMixin superQueryMixin;
+
+ private static final JsonWriterSettings JSON_WRITER_SETTINGS = JsonWriterSettings.builder().outputMode(JsonMode.SHELL)
+ .build();
+
+ private final MongodbDocumentSerializer serializer;
+
+ @SuppressWarnings("unchecked")
+ SpringDataMongodbQuerySupport(MongodbDocumentSerializer serializer) {
+ super(serializer);
+ this.serializer = serializer;
+
+ DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(this);
+ this.superQueryMixin = (QueryMixin) fieldAccessor.getPropertyValue("queryMixin");
+ }
+
+ /**
+ * Returns the {@literal Mongo Shell} representation of the query.
+ * The following query
+ *
+ *
+ *
+ * where(p.lastname.eq("Matthews")).orderBy(p.firstname.asc()).offset(1).limit(5);
+ *
+ *
+ * results in
+ *
+ *
+ *
+ * find({"lastname" : "Matthews"}).sort({"firstname" : 1}).skip(1).limit(5)
+ *
+ *
+ * Note that encoding to {@link String} may fail when using data types that cannot be encoded or DBRef's without an
+ * identifier.
+ *
+ * @return never {@literal null}.
+ */
+ @Override
+ public String toString() {
+
+ Document projection = createProjection(getQueryMixin().getMetadata().getProjection());
+ Document sort = createSort(getQueryMixin().getMetadata().getOrderBy());
+ DocumentCodec codec = new DocumentCodec(MongoClientSettings.getDefaultCodecRegistry());
+
+ StringBuilder sb = new StringBuilder("find(" + asDocument().toJson(JSON_WRITER_SETTINGS, codec));
+ if (projection != null && projection.isEmpty()) {
+ sb.append(", ").append(projection.toJson(JSON_WRITER_SETTINGS, codec));
+ }
+ sb.append(")");
+ if (!sort.isEmpty()) {
+ sb.append(".sort(").append(sort.toJson(JSON_WRITER_SETTINGS, codec)).append(")");
+ }
+ if (getQueryMixin().getMetadata().getModifiers().getOffset() != null) {
+ sb.append(".skip(").append(getQueryMixin().getMetadata().getModifiers().getOffset()).append(")");
+ }
+ if (getQueryMixin().getMetadata().getModifiers().getLimit() != null) {
+ sb.append(".limit(").append(getQueryMixin().getMetadata().getModifiers().getLimit()).append(")");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Get the where definition as a Document instance
+ *
+ * @return
+ */
+ public Document asDocument() {
+ return createQuery(getQueryMixin().getMetadata().getWhere());
+ }
+
+ /**
+ * Obtain the {@literal Mongo Shell} json query representation.
+ *
+ * @return never {@literal null}.
+ */
+ public String toJson() {
+ return toJson(JSON_WRITER_SETTINGS);
+ }
+
+ /**
+ * Obtain the json query representation applying given {@link JsonWriterSettings settings}.
+ *
+ * @param settings must not be {@literal null}.
+ * @return never {@literal null}.
+ */
+ public String toJson(JsonWriterSettings settings) {
+ return asDocument().toJson(settings);
+ }
+
+ /**
+ * Compute the sort {@link Document} from the given list of {@link OrderSpecifier order specifiers}.
+ *
+ * @param orderSpecifiers can be {@literal null}.
+ * @return an empty {@link Document} if predicate is {@literal null}.
+ * @see MongodbDocumentSerializer#toSort(List)
+ */
+ protected Document createSort(List> orderSpecifiers) {
+ return serializer.toSort(orderSpecifiers);
+ }
+
+ // TODO: Remove once https://github.com/querydsl/querydsl/pull/2916 is merged
+ QueryMixin getQueryMixin() {
+ return superQueryMixin;
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java
index e18f30d96..2453e1a46 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java
@@ -22,6 +22,7 @@ import java.util.Set;
import java.util.regex.Pattern;
import org.bson.Document;
+
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
@@ -39,6 +40,7 @@ import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.PathType;
import com.querydsl.mongodb.MongodbSerializer;
+import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* Custom {@link MongodbSerializer} to take mapping information into account when building keys for constraints.
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 5584ae6e3..0067eb3bf 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
@@ -28,8 +28,6 @@ import reactor.test.StepVerifier;
import java.util.Arrays;
-import javax.annotation.Nullable;
-
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -49,6 +47,7 @@ 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.ReactiveQueryMethodEvaluationContextProvider;
+import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.ClassUtils;