diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
index 18f8b269b..a3928e40a 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
@@ -33,6 +33,7 @@ import org.springframework.data.mongodb.core.annotation.Collation;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Jorge Rodríguez
*/
@Collation
@Retention(RetentionPolicy.RUNTIME)
@@ -40,6 +41,7 @@ import org.springframework.data.mongodb.core.annotation.Collation;
@Documented
@QueryAnnotation
@Hint
+@ReadPreference
public @interface Query {
/**
@@ -147,4 +149,21 @@ public @interface Query {
*/
@AliasFor(annotation = Hint.class, attribute = "indexName")
String hint() default "";
+
+ /**
+ * The mode of the read preference to use.
+ * {@code @Query(value = "...", readPreference = "secondary")} can be used as shortcut for:
+ *
+ *
+ * @Query(...)
+ * @ReadPreference("secondary")
+ * List<User> findAllByLastname(String collation);
+ *
+ *
+ * @return the index name.
+ * @since 4.2
+ * @see ReadPreference#value()
+ */
+ @AliasFor(annotation = ReadPreference.class, attribute = "value")
+ String readPreference() default "";
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreference.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreference.java
new file mode 100644
index 000000000..2ba4e32fd
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreference.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011-2023 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to declare read preference for repository and query.
+ *
+ * @author Jorge Rodríguez
+ * @since 4.2
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Documented
+public @interface ReadPreference {
+
+ /**
+ * Configure read preference mode
+ * @return read preference mode
+ */
+ String value() default "";
+
+ /**
+ * Set read preference tags
+ * @return read preference tags
+ */
+ ReadPreferenceTag[] tags() default {};
+
+ /**
+ * Set read preference maxStalenessSeconds
+ * @return read preference maxStalenessSeconds
+ */
+ long maxStalenessSeconds() default -1;
+}
+
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreferenceTag.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreferenceTag.java
new file mode 100644
index 000000000..5fae877ee
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreferenceTag.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011-2023 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;
+
+/**
+ * Annotation used by {@link ReadPreference} for define {@link com.mongodb.Tag}
+ *
+ * @author Jorge Rodríguez
+ * @since 4.2
+ */
+public @interface ReadPreferenceTag {
+
+ /**
+ * Set the name of tag
+ * @return name of tag
+ */
+ String name();
+
+ /**
+ * Set the value of tag
+ * @return value of tag
+ */
+ String value();
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
index e8d69504d..0913a9fb4 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
@@ -62,6 +62,7 @@ import com.mongodb.client.MongoDatabase;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Jorge Rodríguez
*/
public abstract class AbstractMongoQuery implements RepositoryQuery {
@@ -137,6 +138,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
query = applyAnnotatedDefaultSortIfPresent(query);
query = applyAnnotatedCollationIfPresent(query, accessor);
query = applyHintIfPresent(query);
+ query = applyAnnotatedReadPreferenceIfPresent(query);
FindWithQuery> find = typeToRead == null //
? executableFind //
@@ -145,6 +147,22 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
return getExecution(accessor, find).execute(query);
}
+ /**
+ * If present apply the {@link com.mongodb.ReadPreference} from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
+ *
+ * @param query must not be {@literal null}.
+ * @return never {@literal null}.
+ * @since 4.2
+ */
+ private Query applyAnnotatedReadPreferenceIfPresent(Query query) {
+
+ if (!method.hasAnnotatedReadPreference()) {
+ return query;
+ }
+
+ return query.withReadPreference(method.getAnnotatedReadPreference());
+ }
+
private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, FindWithQuery> operation) {
if (isDeleteQuery()) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
index fb430a151..51301697e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
@@ -66,6 +66,7 @@ import com.mongodb.MongoClientSettings;
*
* @author Mark Paluch
* @author Christoph Strobl
+ * @author Jorge Rodríguez
* @since 2.0
*/
public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
@@ -161,6 +162,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
query = applyAnnotatedDefaultSortIfPresent(query);
query = applyAnnotatedCollationIfPresent(query, accessor);
query = applyHintIfPresent(query);
+ query = applyAnnotatedReadPreferenceIfPresent(query);
+
FindWithQuery> find = typeToRead == null //
? findOperationWithProjection //
@@ -229,6 +232,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
return method.getTailableAnnotation() != null;
}
+
Query applyQueryMetaAttributesWhenPresent(Query query) {
if (method.hasQueryMetaAttributes()) {
@@ -286,6 +290,22 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
return query.withHint(method.getAnnotatedHint());
}
+ /**
+ * If present apply the {@link com.mongodb.ReadPreference} from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
+ *
+ * @param query must not be {@literal null}.
+ * @return never {@literal null}.
+ * @since 4.2
+ */
+ private Query applyAnnotatedReadPreferenceIfPresent(Query query) {
+
+ if (!method.hasAnnotatedReadPreference()) {
+ return query;
+ }
+
+ return query.withReadPreference(method.getAnnotatedReadPreference());
+ }
+
/**
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
index 2f8ac7639..f4f30a5a9 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
@@ -22,7 +22,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import com.mongodb.Tag;
+import com.mongodb.TagSet;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.geo.GeoPage;
import org.springframework.data.geo.GeoResult;
@@ -36,6 +40,7 @@ import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.Hint;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.Query;
+import org.springframework.data.mongodb.repository.ReadPreference;
import org.springframework.data.mongodb.repository.Tailable;
import org.springframework.data.mongodb.repository.Update;
import org.springframework.data.projection.ProjectionFactory;
@@ -57,6 +62,7 @@ import org.springframework.util.StringUtils;
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
+ * @author Jorge Rodríguez
*/
public class MongoQueryMethod extends QueryMethod {
@@ -314,6 +320,58 @@ public class MongoQueryMethod extends QueryMethod {
"Expected to find @Query annotation but did not; Make sure to check hasAnnotatedSort() before."));
}
+
+ /**
+ * Check if the query method is decorated with an non empty {@link Query#collation()}.
+ *
+ * @return true if method annotated with {@link Query} or {@link Aggregation} having a non-empty collation attribute.
+ * @since 4.2
+ */
+ public boolean hasAnnotatedReadPreference() {
+ return doFindReadPreferenceAnnotation().map(ReadPreference::value).filter(StringUtils::hasText).isPresent();
+ }
+
+ /**
+ * Get the {@link com.mongodb.ReadPreference} extracted from the {@link ReadPreference} annotation.
+ *
+ * @return the {@link ReadPreference()}.
+ * @throws IllegalStateException if method not annotated with {@link Query}. Make sure to check
+ * {@link #hasAnnotatedQuery()} first.
+ * @since 4.2
+ */
+ public com.mongodb.ReadPreference getAnnotatedReadPreference() {
+
+ return doFindReadPreferenceAnnotation().map(annotationReadPreference -> {
+
+ com.mongodb.ReadPreference readPreference = com.mongodb.ReadPreference.valueOf(annotationReadPreference.value());
+
+ if (annotationReadPreference.tags().length > 0) {
+ List