Add ReadPreference annotation.
The annotation enables a declarative style of defining the ReadPreference to use for individual or all repository queries. Closes: #2971 Original Pull Request: #4503
This commit is contained in:
committed by
Christoph Strobl
parent
0474f65044
commit
16b97a26cc
@@ -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. <br />
|
||||
* {@code @Query(value = "...", readPreference = "secondary")} can be used as shortcut for:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Query(...)
|
||||
* @ReadPreference("secondary")
|
||||
* List<User> findAllByLastname(String collation);
|
||||
* </pre>
|
||||
*
|
||||
* @return the index name.
|
||||
* @since 4.2
|
||||
* @see ReadPreference#value()
|
||||
*/
|
||||
@AliasFor(annotation = ReadPreference.class, attribute = "value")
|
||||
String readPreference() default "";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Tag> tags = Arrays.stream(annotationReadPreference.tags())
|
||||
.map(tag -> new Tag(tag.name(), tag.value()))
|
||||
.collect(Collectors.toList());
|
||||
readPreference = readPreference.withTagSet(new TagSet(tags));
|
||||
}
|
||||
|
||||
if (annotationReadPreference.maxStalenessSeconds() > 0) {
|
||||
readPreference = readPreference.withMaxStalenessMS(annotationReadPreference.maxStalenessSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
return readPreference;
|
||||
}).orElseThrow(() -> new IllegalStateException(
|
||||
"Expected to find @ReadPreference annotation but did not; Make sure to check hasAnnotatedReadPreference() before."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link com.mongodb.ReadPreference} from query. First check if the method is annotated. If not, check if the class is annotated.
|
||||
* So if the method and the class are annotated with @ReadPreference, the method annotation takes precedence.
|
||||
* @return the {@link com.mongodb.ReadPreference}
|
||||
* @since 4.2
|
||||
*/
|
||||
private Optional<ReadPreference> doFindReadPreferenceAnnotation() {
|
||||
return doFindAnnotation(ReadPreference.class).or(() -> doFindAnnotationInClass(ReadPreference.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the query method is decorated with an non empty {@link Query#collation()} or or
|
||||
* {@link Aggregation#collation()}.
|
||||
@@ -400,11 +458,21 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
|
||||
|
||||
|
||||
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
|
||||
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <A extends Annotation> Optional<A> doFindAnnotationInClass(Class<A> annotationType) {
|
||||
|
||||
Optional<Annotation> mergedAnnotation = Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), annotationType));
|
||||
annotationCache.put(annotationType, mergedAnnotation);
|
||||
|
||||
return (Optional<A>) mergedAnnotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModifyingQuery() {
|
||||
return isModifying.get();
|
||||
|
||||
@@ -23,7 +23,11 @@ import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.mongodb.Tag;
|
||||
import com.mongodb.TagSet;
|
||||
import com.mongodb.TaggableReadPreference;
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
import org.bson.types.ObjectId;
|
||||
@@ -65,6 +69,8 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.repository.Hint;
|
||||
import org.springframework.data.mongodb.repository.Meta;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.mongodb.repository.ReadPreference;
|
||||
import org.springframework.data.mongodb.repository.ReadPreferenceTag;
|
||||
import org.springframework.data.mongodb.repository.Update;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
@@ -84,6 +90,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Jorge Rodríguez
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@@ -518,6 +525,33 @@ class AbstractMongoQueryUnitTests {
|
||||
assertThat(captor.getValue().getSortObject()).isEqualTo(new Document("fn", 1));
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void findShouldApplyReadPreference() {
|
||||
|
||||
createQueryForMethod("findWithReadPreferenceByFirstname", String.class).execute(new Object[] { "Jasna" });
|
||||
|
||||
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||
verify(withQueryMock).matching(captor.capture());
|
||||
assertThat(captor.getValue().getReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
assertThat(((TaggableReadPreference)captor.getValue().getReadPreference()).getTagSetList())
|
||||
.containsExactly(new TagSet(List.of(new Tag("local", "east"), new Tag("pre", "west"))));
|
||||
assertThat(((TaggableReadPreference)captor.getValue().getReadPreference()).getMaxStaleness(TimeUnit.SECONDS)).isEqualTo(99);
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void findShouldApplyReadPreferenceAtRepository() {
|
||||
|
||||
createQueryForMethod("findWithLimit", String.class, Limit.class).execute(new Object[] { "dalinar", Limit.of(42) });
|
||||
|
||||
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||
verify(withQueryMock).matching(captor.capture());
|
||||
assertThat(captor.getValue().getReadPreference().getName()).isEqualTo("primaryPreferred");
|
||||
assertThat(((TaggableReadPreference)captor.getValue().getReadPreference()).getTagSetList())
|
||||
.containsExactly(new TagSet(List.of(new Tag("primary-local", "east"), new Tag("primary-pre", "west"))));
|
||||
assertThat(((TaggableReadPreference)captor.getValue().getReadPreference()).getMaxStaleness(TimeUnit.SECONDS)).isEqualTo(20);
|
||||
}
|
||||
|
||||
|
||||
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
||||
return createQueryForMethod(Repo.class, methodName, paramTypes);
|
||||
}
|
||||
@@ -588,6 +622,11 @@ class AbstractMongoQueryUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
@ReadPreference(
|
||||
value = "primaryPreferred",
|
||||
tags = { @ReadPreferenceTag(name = "primary-local", value = "east"), @ReadPreferenceTag(name = "primary-pre", value = "west") },
|
||||
maxStalenessSeconds = 20
|
||||
)
|
||||
private interface Repo extends MongoRepository<Person, Long> {
|
||||
|
||||
List<Person> deleteByLastname(String lastname);
|
||||
@@ -643,6 +682,13 @@ class AbstractMongoQueryUnitTests {
|
||||
List<Person> findWithLimit(String firstname, Limit limit);
|
||||
|
||||
List<Person> findWithSortAndLimit(String firstname, Sort sort, Limit limit);
|
||||
|
||||
@ReadPreference(
|
||||
value = "secondaryPreferred",
|
||||
tags = { @ReadPreferenceTag(name = "local", value = "east"), @ReadPreferenceTag(name = "pre", value = "west") },
|
||||
maxStalenessSeconds = 99
|
||||
)
|
||||
List<Person> findWithReadPreferenceByFirstname(String firstname);
|
||||
}
|
||||
|
||||
// DATAMONGO-1872
|
||||
|
||||
@@ -19,6 +19,9 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.Tag;
|
||||
import com.mongodb.TagSet;
|
||||
import com.mongodb.TaggableReadPreference;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
import org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingUpdate;
|
||||
@@ -26,6 +29,8 @@ import org.springframework.data.mongodb.core.ReactiveUpdateOperation.ReactiveUpd
|
||||
import org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithQuery;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.repository.Hint;
|
||||
import org.springframework.data.mongodb.repository.ReadPreference;
|
||||
import org.springframework.data.mongodb.repository.ReadPreferenceTag;
|
||||
import org.springframework.data.mongodb.repository.Update;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -33,6 +38,7 @@ import reactor.core.publisher.Mono;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -68,7 +74,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @currentRead Way of Kings - Brandon Sanderson
|
||||
* @author Jorge Rodríguez
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@@ -263,6 +269,19 @@ class AbstractReactiveMongoQueryUnitTests {
|
||||
assertThat(captor.getValue().getHint()).isEqualTo("idx-ln");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void findShouldApplyReadPreference() {
|
||||
|
||||
createQueryForMethod("findWithReadPreferenceByFirstname", String.class).executeBlocking(new Object[] { "Jasna" });
|
||||
|
||||
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||
verify(withQueryMock).matching(captor.capture());
|
||||
assertThat(captor.getValue().getReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
assertThat(((TaggableReadPreference)captor.getValue().getReadPreference()).getTagSetList())
|
||||
.containsExactly(new TagSet(List.of(new Tag("local", "east"), new Tag("pre", "west"))));
|
||||
assertThat(((TaggableReadPreference)captor.getValue().getReadPreference()).getMaxStaleness(TimeUnit.SECONDS)).isEqualTo(99);
|
||||
}
|
||||
|
||||
private ReactiveMongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
||||
return createQueryForMethod(Repo.class, methodName, paramTypes);
|
||||
}
|
||||
@@ -367,5 +386,12 @@ class AbstractReactiveMongoQueryUnitTests {
|
||||
|
||||
@Hint("idx-fn")
|
||||
void findWithHintByFirstname(String firstname);
|
||||
|
||||
@ReadPreference(
|
||||
value = "secondaryPreferred",
|
||||
tags = { @ReadPreferenceTag(name = "local", value = "east"), @ReadPreferenceTag(name = "pre", value = "west") },
|
||||
maxStalenessSeconds = 99
|
||||
)
|
||||
Flux<Person> findWithReadPreferenceByFirstname(String firstname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.springframework.data.mongodb.repository.Contact;
|
||||
import org.springframework.data.mongodb.repository.Meta;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
import org.springframework.data.mongodb.repository.ReadPreference;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
@@ -52,6 +53,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Jorge Rodríguez
|
||||
*/
|
||||
public class MongoQueryMethodUnitTests {
|
||||
|
||||
@@ -311,6 +313,43 @@ public class MongoQueryMethodUnitTests {
|
||||
assertThat(method.getAnnotatedCollation()).isEqualTo("de_AT");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void readsReadPreferenceAtQueryAnnotation() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithReadPreferenceFromAtReadPreferenceByFirstname", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void readsReadPreferenceFromAtQueryAnnotation() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithReadPreferenceFromAtQueryByFirstname", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void annotatedReadPreferenceClashSelectsAtReadPreferenceAnnotationValue() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithMultipleReadPreferencesFromAtQueryAndAtReadPreferenceByFirstname", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void readsReadPreferenceAtRepositoryAnnotation() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(PersonRepository.class, "deleteByUserName", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("primaryPreferred");
|
||||
}
|
||||
|
||||
private MongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception {
|
||||
|
||||
Method method = repository.getMethod(name, parameters);
|
||||
@@ -318,6 +357,7 @@ public class MongoQueryMethodUnitTests {
|
||||
return new MongoQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context);
|
||||
}
|
||||
|
||||
@ReadPreference(value = "primaryPreferred")
|
||||
interface PersonRepository extends Repository<User, Long> {
|
||||
|
||||
// Misses Pageable
|
||||
@@ -381,6 +421,16 @@ public class MongoQueryMethodUnitTests {
|
||||
@Collation("de_AT")
|
||||
@Query(collation = "en_US")
|
||||
List<User> findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname(String firstname);
|
||||
|
||||
@ReadPreference("secondaryPreferred")
|
||||
List<User> findWithReadPreferenceFromAtReadPreferenceByFirstname(String firstname);
|
||||
|
||||
@Query(readPreference = "secondaryPreferred")
|
||||
List<User> findWithReadPreferenceFromAtQueryByFirstname(String firstname);
|
||||
|
||||
@ReadPreference("secondaryPreferred")
|
||||
@Query(readPreference = "primaryPreferred")
|
||||
List<User> findWithMultipleReadPreferencesFromAtQueryAndAtReadPreferenceByFirstname(String firstname);
|
||||
}
|
||||
|
||||
interface SampleRepository extends Repository<Contact, Long> {
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
import org.springframework.data.mongodb.repository.ReadPreference;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -53,6 +54,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Jorge Rodríguez
|
||||
*/
|
||||
public class ReactiveMongoQueryMethodUnitTests {
|
||||
|
||||
@@ -226,6 +228,43 @@ public class ReactiveMongoQueryMethodUnitTests {
|
||||
assertThat(method.getAnnotatedCollation()).isEqualTo("de_AT");
|
||||
}
|
||||
|
||||
|
||||
@Test // GH-2971
|
||||
void readsReadPreferenceAtQueryAnnotation() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "findWithReadPreferenceFromAtReadPreferenceByFirstname", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void readsReadPreferenceFromAtQueryAnnotation() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "findWithReadPreferenceFromAtQueryByFirstname", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void annotatedReadPreferenceClashSelectsAtReadPreferenceAnnotationValue() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "findWithMultipleReadPreferencesFromAtQueryAndAtReadPreferenceByFirstname", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("secondaryPreferred");
|
||||
}
|
||||
|
||||
@Test // GH-2971
|
||||
void readsReadPreferenceAtRepositoryAnnotation() throws Exception {
|
||||
|
||||
MongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "deleteByUserName", String.class);
|
||||
|
||||
assertThat(method.hasAnnotatedReadPreference()).isTrue();
|
||||
assertThat(method.getAnnotatedReadPreference().getName()).isEqualTo("primaryPreferred");
|
||||
}
|
||||
|
||||
private ReactiveMongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters)
|
||||
throws Exception {
|
||||
|
||||
@@ -234,6 +273,7 @@ public class ReactiveMongoQueryMethodUnitTests {
|
||||
return new ReactiveMongoQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context);
|
||||
}
|
||||
|
||||
@ReadPreference(value = "primaryPreferred")
|
||||
interface PersonRepository extends Repository<User, Long> {
|
||||
|
||||
Mono<Person> findMonoByLastname(String lastname, Pageable pageRequest);
|
||||
@@ -277,6 +317,16 @@ public class ReactiveMongoQueryMethodUnitTests {
|
||||
@Collation("de_AT")
|
||||
@Query(collation = "en_US")
|
||||
List<User> findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname(String firstname);
|
||||
|
||||
@ReadPreference("secondaryPreferred")
|
||||
Flux<User> findWithReadPreferenceFromAtReadPreferenceByFirstname(String firstname);
|
||||
|
||||
@Query(readPreference = "secondaryPreferred")
|
||||
Flux<User> findWithReadPreferenceFromAtQueryByFirstname(String firstname);
|
||||
|
||||
@ReadPreference("secondaryPreferred")
|
||||
@Query(readPreference = "primaryPreferred")
|
||||
Flux<User> findWithMultipleReadPreferencesFromAtQueryAndAtReadPreferenceByFirstname(String firstname);
|
||||
}
|
||||
|
||||
interface SampleRepository extends Repository<Contact, Long> {
|
||||
|
||||
@@ -824,3 +824,33 @@ interface GameRepository extends Repository<Game, String> {
|
||||
<2> Instead of `@Query(collation=...)`.
|
||||
<3> Favors `@Collation` over meta usage.
|
||||
====
|
||||
|
||||
== Read Preferences
|
||||
|
||||
The `@ReadPreference` annotation allows you to configure MongoDB's ReadPreferences
|
||||
|
||||
.Example of read preferences
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@ReadPreference(
|
||||
value = "primaryPreferred",
|
||||
tags = {@ReadPreferenceTag(name = "local", value = "east"), @ReadPreferenceTag(name = "pre", value = "west")},
|
||||
maxStalenessSeconds = 150
|
||||
) <1>
|
||||
public interface PersonRepository extends CrudRepository<Person, String> {
|
||||
|
||||
@ReadPreference(value = "secondaryPreferred") <2>
|
||||
List<Person> findWithReadPreferenceAnnotationByLastname(String lastname);
|
||||
|
||||
@Query(readPreference = "nearest") <3>
|
||||
List<Person> findWithReadPreferenceAtTagByFirstname(String firstname);
|
||||
|
||||
List<Person> findWithReadPreferenceAtTagByFirstname(String firstname); <4>
|
||||
----
|
||||
<1> Configure read preference for all repository operations that do not have a query-level definition. Therefore, in this case the read preference mode will be `primaryPreferred`
|
||||
<2> Use the read preference mode defined in annotation `ReadPreference`, in this case secondaryPreferred
|
||||
<3> The `@Query` annotation defines the `read preference mode` alias which is equivalent to adding the `@ReadPreference` annotation.
|
||||
<4> This query will use the read preference mode defined in the repository.
|
||||
====
|
||||
|
||||
Reference in New Issue
Block a user