Skip to content

Commit 715231e

Browse files
Detect ReadPreference for annotated aggregations and inherited find methods.
This commit reduces the API surface of the ReadPreference annotation leaving fine grained control such as Tags to the Template API. Next to supporting read preference for annotated queries we now also make sure to apply read preferences for annotated aggregation methods as well as predefined finder methods running queries. See: #2971 Original Pull Request: #4503
1 parent 16b97a2 commit 715231e

23 files changed

+415
-166
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Aggregation.java

+18
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
4545
@Documented
4646
@QueryAnnotation
47+
@ReadPreference
4748
public @interface Aggregation {
4849

4950
/**
@@ -127,4 +128,21 @@
127128
*/
128129
@AliasFor(annotation = Collation.class, attribute = "value")
129130
String collation() default "";
131+
132+
/**
133+
* The mode of the read preference to use. <br />
134+
* {@code @Aggregation(pipeline = { ... }, readPreference = "secondary")} can be used as shortcut for:
135+
*
136+
* <pre class="code">
137+
* &#64;@Aggregation(pipeline = { ... })
138+
* &#64;ReadPreference("secondary")
139+
* List&lt;PersonAggregate&gt; groupByLastnameAnd(String property);
140+
* </pre>
141+
*
142+
* @return the index name.
143+
* @since 4.2
144+
* @see ReadPreference#value()
145+
*/
146+
@AliasFor(annotation = ReadPreference.class, attribute = "value")
147+
String readPreference() default "";
130148
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@
157157
* <pre class="code">
158158
* &#64;Query(...)
159159
* &#64;ReadPreference("secondary")
160-
* List&lt;User&gt; findAllByLastname(String collation);
160+
* List&lt;User&gt; findAllByLastname(String lastname);
161161
* </pre>
162162
*
163163
* @return the index name.

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreference.java

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2023 the original author or authors.
2+
* Copyright 2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
* Annotation to declare read preference for repository and query.
2626
*
2727
* @author Jorge Rodríguez
28+
* @author Christoph Strobl
2829
* @since 4.2
2930
*/
3031
@Retention(RetentionPolicy.RUNTIME)
@@ -38,16 +39,5 @@
3839
*/
3940
String value() default "";
4041

41-
/**
42-
* Set read preference tags
43-
* @return read preference tags
44-
*/
45-
ReadPreferenceTag[] tags() default {};
46-
47-
/**
48-
* Set read preference maxStalenessSeconds
49-
* @return read preference maxStalenessSeconds
50-
*/
51-
long maxStalenessSeconds() default -1;
5242
}
5343

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ReadPreferenceTag.java

-37
This file was deleted.

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ private Query applyAnnotatedReadPreferenceIfPresent(Query query) {
159159
if (!method.hasAnnotatedReadPreference()) {
160160
return query;
161161
}
162-
163-
return query.withReadPreference(method.getAnnotatedReadPreference());
162+
163+
return query.withReadPreference(com.mongodb.ReadPreference.valueOf(method.getAnnotatedReadPreference()));
164164
}
165165

166166
private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, FindWithQuery<?> operation) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ protected Publisher<Object> doExecute(ReactiveMongoQueryMethod method, ResultPro
164164
query = applyHintIfPresent(query);
165165
query = applyAnnotatedReadPreferenceIfPresent(query);
166166

167-
168167
FindWithQuery<?> find = typeToRead == null //
169168
? findOperationWithProjection //
170169
: findOperationWithProjection.as(typeToRead);
@@ -303,7 +302,7 @@ private Query applyAnnotatedReadPreferenceIfPresent(Query query) {
303302
return query;
304303
}
305304

306-
return query.withReadPreference(method.getAnnotatedReadPreference());
305+
return query.withReadPreference(com.mongodb.ReadPreference.valueOf(method.getAnnotatedReadPreference()));
307306
}
308307

309308
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java

+18
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.springframework.util.ClassUtils;
3838
import org.springframework.util.ObjectUtils;
3939

40+
import com.mongodb.ReadPreference;
41+
4042
/**
4143
* Internal utility class to help avoid duplicate code required in both the reactive and the sync {@link Aggregation}
4244
* support offered by repositories.
@@ -117,6 +119,22 @@ static AggregationOptions.Builder applyHint(AggregationOptions.Builder builder,
117119
return builder.hint(queryMethod.getAnnotatedHint());
118120
}
119121

122+
/**
123+
* If present apply the preference from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
124+
*
125+
* @param builder must not be {@literal null}.
126+
* @return never {@literal null}.
127+
* @since 4.2
128+
*/
129+
static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder builder, MongoQueryMethod queryMethod) {
130+
131+
if (!queryMethod.hasAnnotatedReadPreference()) {
132+
return builder;
133+
}
134+
135+
return builder.readPreference(ReadPreference.valueOf(queryMethod.getAnnotatedReadPreference()));
136+
}
137+
120138
/**
121139
* Append {@code $sort} aggregation stage if {@link ConvertingParameterAccessor#getSort()} is present.
122140
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java

+6-28
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Optional;
25-
import java.util.concurrent.TimeUnit;
26-
import java.util.stream.Collectors;
2725

28-
import com.mongodb.Tag;
29-
import com.mongodb.TagSet;
3026
import org.springframework.core.annotation.AnnotatedElementUtils;
3127
import org.springframework.data.geo.GeoPage;
3228
import org.springframework.data.geo.GeoResult;
@@ -322,7 +318,7 @@ public String getAnnotatedSort() {
322318

323319

324320
/**
325-
* Check if the query method is decorated with an non empty {@link Query#collation()}.
321+
* Check if the query method is decorated with an non empty {@link ReadPreference}.
326322
*
327323
* @return true if method annotated with {@link Query} or {@link Aggregation} having a non-empty collation attribute.
328324
* @since 4.2
@@ -334,44 +330,27 @@ public boolean hasAnnotatedReadPreference() {
334330
/**
335331
* Get the {@link com.mongodb.ReadPreference} extracted from the {@link ReadPreference} annotation.
336332
*
337-
* @return the {@link ReadPreference()}.
333+
* @return the name of the {@link ReadPreference()}.
338334
* @throws IllegalStateException if method not annotated with {@link Query}. Make sure to check
339335
* {@link #hasAnnotatedQuery()} first.
340336
* @since 4.2
341337
*/
342-
public com.mongodb.ReadPreference getAnnotatedReadPreference() {
338+
public String getAnnotatedReadPreference() {
343339

344-
return doFindReadPreferenceAnnotation().map(annotationReadPreference -> {
345-
346-
com.mongodb.ReadPreference readPreference = com.mongodb.ReadPreference.valueOf(annotationReadPreference.value());
347-
348-
if (annotationReadPreference.tags().length > 0) {
349-
List<Tag> tags = Arrays.stream(annotationReadPreference.tags())
350-
.map(tag -> new Tag(tag.name(), tag.value()))
351-
.collect(Collectors.toList());
352-
readPreference = readPreference.withTagSet(new TagSet(tags));
353-
}
354-
355-
if (annotationReadPreference.maxStalenessSeconds() > 0) {
356-
readPreference = readPreference.withMaxStalenessMS(annotationReadPreference.maxStalenessSeconds(), TimeUnit.SECONDS);
357-
}
358-
359-
return readPreference;
360-
}).orElseThrow(() -> new IllegalStateException(
340+
return doFindReadPreferenceAnnotation().map(ReadPreference::value).orElseThrow(() -> new IllegalStateException(
361341
"Expected to find @ReadPreference annotation but did not; Make sure to check hasAnnotatedReadPreference() before."));
362342
}
363343

364344
/**
365-
* Get {@link com.mongodb.ReadPreference} from query. First check if the method is annotated. If not, check if the class is annotated.
345+
* Get {@link com.mongodb.ReadPreference#getName() name} from query. First check if the method is annotated. If not, check if the class is annotated.
366346
* So if the method and the class are annotated with @ReadPreference, the method annotation takes precedence.
367-
* @return the {@link com.mongodb.ReadPreference}
347+
* @return the {@link ReadPreference}
368348
* @since 4.2
369349
*/
370350
private Optional<ReadPreference> doFindReadPreferenceAnnotation() {
371351
return doFindAnnotation(ReadPreference.class).or(() -> doFindAnnotationInClass(ReadPreference.class));
372352
}
373353

374-
375354
/**
376355
* Check if the query method is decorated with an non empty {@link Query#collation()} or or
377356
* {@link Aggregation#collation()}.
@@ -458,7 +437,6 @@ Optional<Update> lookupUpdateAnnotation() {
458437

459438
@SuppressWarnings("unchecked")
460439
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
461-
462440

463441
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
464442
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ private AggregationOptions computeOptions(MongoQueryMethod method, ConvertingPar
132132
expressionParser, evaluationContextProvider);
133133
AggregationUtils.applyMeta(builder, method);
134134
AggregationUtils.applyHint(builder, method);
135+
AggregationUtils.applyReadPreference(builder, method);
135136

136137
TypeInformation<?> returnType = method.getReturnType();
137138
if (returnType.getComponentType() != null) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ private AggregationOptions computeOptions(MongoQueryMethod method, ConvertingPar
181181
expressionParser, evaluationContextProvider);
182182
AggregationUtils.applyMeta(builder, method);
183183
AggregationUtils.applyHint(builder, method);
184+
AggregationUtils.applyReadPreference(builder, method);
184185

185186
if (ReflectionUtils.isVoid(method.getReturnType().getType()) && pipeline.isOutOrMerge()) {
186187
builder.skipOutput();

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ protected Object getTargetRepository(RepositoryInformation information) {
127127

128128
MongoEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType(),
129129
information);
130-
return getTargetRepositoryViaReflection(information, entityInformation, operations);
130+
return getTargetRepositoryViaReflection(information, information, entityInformation, operations);
131131
}
132132

133133
@Override

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ protected Object getTargetRepository(RepositoryInformation information) {
114114

115115
MongoEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType(),
116116
information);
117-
return getTargetRepositoryViaReflection(information, entityInformation, operations);
117+
return getTargetRepositoryViaReflection(information, information, entityInformation, operations);
118118
}
119119

120120
@Override

0 commit comments

Comments
 (0)