Add Hint annotation.
This commit introduces the new `@Hint` annotation that allows to override MongoDB's default index selection for repository query, update and aggregate operations.
```
@Hint("lastname-idx")
List<Person> findByLastname(String lastname);
@Query(value = "{ 'firstname' : ?0 }", hint="firstname-idx")
List<Person> findByFirstname(String firstname);
```
Closes: #3230
Original pull request: #4339
This commit is contained in:
committed by
Mark Paluch
parent
af2076d4a5
commit
7b44f78133
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to declare index hints for repository query, update and aggregate operations. The index is specified by
|
||||||
|
* its name.
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
|
@Documented
|
||||||
|
public @interface Hint {
|
||||||
|
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the index to use. In case of an {@literal aggregation} the index is evaluated against the initial
|
||||||
|
* collection or view. Specify the index either by the index name.
|
||||||
|
*
|
||||||
|
* @return the index name.
|
||||||
|
*/
|
||||||
|
@AliasFor("value")
|
||||||
|
String indexName() default "";
|
||||||
|
}
|
||||||
@@ -39,13 +39,14 @@ import org.springframework.data.mongodb.core.annotation.Collation;
|
|||||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
@Documented
|
@Documented
|
||||||
@QueryAnnotation
|
@QueryAnnotation
|
||||||
|
@Hint
|
||||||
public @interface Query {
|
public @interface Query {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the
|
* Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the
|
||||||
* method name then.
|
* method name then.
|
||||||
*
|
*
|
||||||
* @return empty {@link String} by default.
|
* @return empty {@link String} by default.
|
||||||
*/
|
*/
|
||||||
String value() default "";
|
String value() default "";
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ public @interface Query {
|
|||||||
* Defines the fields that should be returned for the given query. Note that only these fields will make it into the
|
* Defines the fields that should be returned for the given query. Note that only these fields will make it into the
|
||||||
* domain object returned.
|
* domain object returned.
|
||||||
*
|
*
|
||||||
* @return empty {@link String} by default.
|
* @return empty {@link String} by default.
|
||||||
*/
|
*/
|
||||||
String fields() default "";
|
String fields() default "";
|
||||||
|
|
||||||
@@ -129,4 +130,21 @@ public @interface Query {
|
|||||||
*/
|
*/
|
||||||
@AliasFor(annotation = Collation.class, attribute = "value")
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the index to use. <br />
|
||||||
|
* {@code @Query(value = "...", hint = "lastname-idx")} can be used as shortcut for:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Query(...)
|
||||||
|
* @Hint("lastname-idx")
|
||||||
|
* List<User> findAllByLastname(String collation);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return the index name.
|
||||||
|
* @since 4.1
|
||||||
|
* @see Hint#indexName()
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = Hint.class, attribute = "indexName")
|
||||||
|
String hint() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
|||||||
applyQueryMetaAttributesWhenPresent(query);
|
applyQueryMetaAttributesWhenPresent(query);
|
||||||
query = applyAnnotatedDefaultSortIfPresent(query);
|
query = applyAnnotatedDefaultSortIfPresent(query);
|
||||||
query = applyAnnotatedCollationIfPresent(query, accessor);
|
query = applyAnnotatedCollationIfPresent(query, accessor);
|
||||||
|
query = applyHintIfPresent(query);
|
||||||
|
|
||||||
FindWithQuery<?> find = typeToRead == null //
|
FindWithQuery<?> find = typeToRead == null //
|
||||||
? executableFind //
|
? executableFind //
|
||||||
@@ -225,6 +226,21 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
|||||||
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
|
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present apply the hint from the {@link org.springframework.data.mongodb.repository.Hint} annotation.
|
||||||
|
*
|
||||||
|
* @param query must not be {@literal null}.
|
||||||
|
* @return never {@literal null}.
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
Query applyHintIfPresent(Query query) {
|
||||||
|
|
||||||
|
if(!method.hasAnnotatedHint()) {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
return query.withHint(method.getAnnotatedHint());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
|
* 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
|
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
applyQueryMetaAttributesWhenPresent(query);
|
applyQueryMetaAttributesWhenPresent(query);
|
||||||
query = applyAnnotatedDefaultSortIfPresent(query);
|
query = applyAnnotatedDefaultSortIfPresent(query);
|
||||||
query = applyAnnotatedCollationIfPresent(query, accessor);
|
query = applyAnnotatedCollationIfPresent(query, accessor);
|
||||||
|
query = applyHintIfPresent(query);
|
||||||
|
|
||||||
FindWithQuery<?> find = typeToRead == null //
|
FindWithQuery<?> find = typeToRead == null //
|
||||||
? findOperationWithProjection //
|
? findOperationWithProjection //
|
||||||
@@ -269,6 +270,21 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
|
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present apply the hint from the {@link org.springframework.data.mongodb.repository.Hint} annotation.
|
||||||
|
*
|
||||||
|
* @param query must not be {@literal null}.
|
||||||
|
* @return never {@literal null}.
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
Query applyHintIfPresent(Query query) {
|
||||||
|
|
||||||
|
if(!method.hasAnnotatedHint()) {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
return query.withHint(method.getAnnotatedHint());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
|
* 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
|
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
|
||||||
|
|||||||
@@ -102,6 +102,21 @@ abstract class AggregationUtils {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present apply the hint from the {@link org.springframework.data.mongodb.repository.Hint} annotation.
|
||||||
|
*
|
||||||
|
* @param builder must not be {@literal null}.
|
||||||
|
* @return never {@literal null}.
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
static AggregationOptions.Builder applyHint(AggregationOptions.Builder builder, MongoQueryMethod queryMethod) {
|
||||||
|
|
||||||
|
if(!queryMethod.hasAnnotatedHint()) {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
return builder.hint(queryMethod.getAnnotatedHint());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append {@code $sort} aggregation stage if {@link ConvertingParameterAccessor#getSort()} is present.
|
* Append {@code $sort} aggregation stage if {@link ConvertingParameterAccessor#getSort()} is present.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
|||||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||||
import org.springframework.data.mongodb.repository.Aggregation;
|
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.Meta;
|
||||||
import org.springframework.data.mongodb.repository.Query;
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
import org.springframework.data.mongodb.repository.Tailable;
|
import org.springframework.data.mongodb.repository.Tailable;
|
||||||
@@ -362,6 +363,27 @@ public class MongoQueryMethod extends QueryMethod {
|
|||||||
"Expected to find @Aggregation annotation but did not; Make sure to check hasAnnotatedAggregation() before."));
|
"Expected to find @Aggregation annotation but did not; Make sure to check hasAnnotatedAggregation() before."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@literal true} if the {@link Hint} annotation is present and the index name is not empty.
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
public boolean hasAnnotatedHint() {
|
||||||
|
return StringUtils.hasText(getAnnotatedHint());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the aggregation pipeline declared via a {@link Hint} annotation.
|
||||||
|
*
|
||||||
|
* @return the index name (might be empty) or {@literal null} if not present.
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getAnnotatedHint() {
|
||||||
|
|
||||||
|
Optional<Hint> hint = doFindAnnotation(Hint.class);
|
||||||
|
return hint.map(Hint::indexName).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<String[]> findAnnotatedAggregation() {
|
private Optional<String[]> findAnnotatedAggregation() {
|
||||||
|
|
||||||
return lookupAggregationAnnotation() //
|
return lookupAggregationAnnotation() //
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
|
|||||||
AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, method.getParameters(),
|
AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, method.getParameters(),
|
||||||
expressionParser, evaluationContextProvider);
|
expressionParser, evaluationContextProvider);
|
||||||
AggregationUtils.applyMeta(builder, method);
|
AggregationUtils.applyMeta(builder, method);
|
||||||
|
AggregationUtils.applyHint(builder, method);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.MongoOperations;
|
|||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||||
|
import org.springframework.data.mongodb.core.aggregation.AggregationOptions.Builder;
|
||||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||||
@@ -178,6 +179,7 @@ public class StringBasedAggregation extends AbstractMongoQuery {
|
|||||||
AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, method.getParameters(),
|
AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, method.getParameters(),
|
||||||
expressionParser, evaluationContextProvider);
|
expressionParser, evaluationContextProvider);
|
||||||
AggregationUtils.applyMeta(builder, method);
|
AggregationUtils.applyMeta(builder, method);
|
||||||
|
AggregationUtils.applyHint(builder, method);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import org.springframework.data.mongodb.core.query.BasicQuery;
|
|||||||
import org.springframework.data.mongodb.core.query.Collation;
|
import org.springframework.data.mongodb.core.query.Collation;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
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.Meta;
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
import org.springframework.data.mongodb.repository.Update;
|
import org.springframework.data.mongodb.repository.Update;
|
||||||
@@ -469,6 +470,29 @@ class AbstractMongoQueryUnitTests {
|
|||||||
assertThat(update.getValue().getUpdateObject()).isEqualTo(Document.parse("{ '$inc' : { 'visits' : 100 } }"));
|
assertThat(update.getValue().getUpdateObject()).isEqualTo(Document.parse("{ '$inc' : { 'visits' : 100 } }"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3230
|
||||||
|
void findShouldApplyHint() {
|
||||||
|
|
||||||
|
createQueryForMethod("findWithHintByFirstname", String.class).execute(new Object[] { "Jasna" });
|
||||||
|
|
||||||
|
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||||
|
verify(withQueryMock).matching(captor.capture());
|
||||||
|
assertThat(captor.getValue().getHint()).isEqualTo("idx-fn");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3230
|
||||||
|
void updateShouldApplyHint() {
|
||||||
|
|
||||||
|
when(terminatingUpdate.all()).thenReturn(updateResultMock);
|
||||||
|
|
||||||
|
createQueryForMethod("findAndIncreaseVisitsByLastname", String.class, int.class) //
|
||||||
|
.execute(new Object[] { "dalinar", 100 });
|
||||||
|
|
||||||
|
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||||
|
verify(executableUpdate).matching(captor.capture());
|
||||||
|
assertThat(captor.getValue().getHint()).isEqualTo("idx-ln");
|
||||||
|
}
|
||||||
|
|
||||||
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
||||||
return createQueryForMethod(Repo.class, methodName, paramTypes);
|
return createQueryForMethod(Repo.class, methodName, paramTypes);
|
||||||
}
|
}
|
||||||
@@ -584,8 +608,12 @@ class AbstractMongoQueryUnitTests {
|
|||||||
@org.springframework.data.mongodb.repository.Query(collation = "{ 'locale' : 'en_US' }")
|
@org.springframework.data.mongodb.repository.Query(collation = "{ 'locale' : 'en_US' }")
|
||||||
List<Person> findWithWithCollationParameterAndAnnotationByFirstName(String firstname, Collation collation);
|
List<Person> findWithWithCollationParameterAndAnnotationByFirstName(String firstname, Collation collation);
|
||||||
|
|
||||||
|
@Hint("idx-ln")
|
||||||
@Update("{ '$inc' : { 'visits' : ?1 } }")
|
@Update("{ '$inc' : { 'visits' : ?1 } }")
|
||||||
void findAndIncreaseVisitsByLastname(String lastname, int value);
|
void findAndIncreaseVisitsByLastname(String lastname, int value);
|
||||||
|
|
||||||
|
@Hint("idx-fn")
|
||||||
|
void findWithHintByFirstname(String firstname);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATAMONGO-1872
|
// DATAMONGO-1872
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ package org.springframework.data.mongodb.repository.query;
|
|||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import com.mongodb.MongoClientSettings;
|
||||||
|
import com.mongodb.client.result.UpdateResult;
|
||||||
|
import org.bson.codecs.configuration.CodecRegistry;
|
||||||
|
import org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingUpdate;
|
||||||
|
import org.springframework.data.mongodb.core.ReactiveUpdateOperation.ReactiveUpdate;
|
||||||
|
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.Update;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@@ -71,6 +80,9 @@ class AbstractReactiveMongoQueryUnitTests {
|
|||||||
|
|
||||||
@Mock ReactiveFind<?> executableFind;
|
@Mock ReactiveFind<?> executableFind;
|
||||||
@Mock FindWithQuery<?> withQueryMock;
|
@Mock FindWithQuery<?> withQueryMock;
|
||||||
|
@Mock ReactiveUpdate executableUpdate;
|
||||||
|
@Mock UpdateWithQuery updateWithQuery;
|
||||||
|
@Mock TerminatingUpdate terminatingUpdate;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
@@ -91,6 +103,11 @@ class AbstractReactiveMongoQueryUnitTests {
|
|||||||
doReturn(Flux.empty()).when(withQueryMock).all();
|
doReturn(Flux.empty()).when(withQueryMock).all();
|
||||||
doReturn(Mono.empty()).when(withQueryMock).first();
|
doReturn(Mono.empty()).when(withQueryMock).first();
|
||||||
doReturn(Mono.empty()).when(withQueryMock).one();
|
doReturn(Mono.empty()).when(withQueryMock).one();
|
||||||
|
|
||||||
|
doReturn(executableUpdate).when(mongoOperationsMock).update(any());
|
||||||
|
doReturn(executableUpdate).when(executableUpdate).inCollection(anyString());
|
||||||
|
doReturn(updateWithQuery).when(executableUpdate).matching(any(Query.class));
|
||||||
|
doReturn(terminatingUpdate).when(updateWithQuery).apply(any(UpdateDefinition.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAMONGO-1854
|
@Test // DATAMONGO-1854
|
||||||
@@ -223,6 +240,29 @@ class AbstractReactiveMongoQueryUnitTests {
|
|||||||
.contains(Collation.of("en_US").toDocument());
|
.contains(Collation.of("en_US").toDocument());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3230
|
||||||
|
void findShouldApplyHint() {
|
||||||
|
|
||||||
|
createQueryForMethod("findWithHintByFirstname", String.class).executeBlocking(new Object[] { "Jasna" });
|
||||||
|
|
||||||
|
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||||
|
verify(withQueryMock).matching(captor.capture());
|
||||||
|
assertThat(captor.getValue().getHint()).isEqualTo("idx-fn");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3230
|
||||||
|
void updateShouldApplyHint() {
|
||||||
|
|
||||||
|
when(terminatingUpdate.all()).thenReturn(Mono.just(mock(UpdateResult.class)));
|
||||||
|
|
||||||
|
createQueryForMethod("findAndIncreaseVisitsByLastname", String.class, int.class) //
|
||||||
|
.executeBlocking(new Object[] { "dalinar", 100 });
|
||||||
|
|
||||||
|
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||||
|
verify(executableUpdate).matching(captor.capture());
|
||||||
|
assertThat(captor.getValue().getHint()).isEqualTo("idx-ln");
|
||||||
|
}
|
||||||
|
|
||||||
private ReactiveMongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
private ReactiveMongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
||||||
return createQueryForMethod(Repo.class, methodName, paramTypes);
|
return createQueryForMethod(Repo.class, methodName, paramTypes);
|
||||||
}
|
}
|
||||||
@@ -291,6 +331,11 @@ class AbstractReactiveMongoQueryUnitTests {
|
|||||||
isLimitingQuery = limitingQuery;
|
isLimitingQuery = limitingQuery;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<CodecRegistry> getCodecRegistry() {
|
||||||
|
return Mono.just(MongoClientSettings.getDefaultCodecRegistry());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface Repo extends ReactiveMongoRepository<Person, Long> {
|
private interface Repo extends ReactiveMongoRepository<Person, Long> {
|
||||||
@@ -315,5 +360,12 @@ class AbstractReactiveMongoQueryUnitTests {
|
|||||||
|
|
||||||
@org.springframework.data.mongodb.repository.Query(collation = "{ 'locale' : 'en_US' }")
|
@org.springframework.data.mongodb.repository.Query(collation = "{ 'locale' : 'en_US' }")
|
||||||
List<Person> findWithWithCollationParameterAndAnnotationByFirstName(String firstname, Collation collation);
|
List<Person> findWithWithCollationParameterAndAnnotationByFirstName(String firstname, Collation collation);
|
||||||
|
|
||||||
|
@Hint("idx-ln")
|
||||||
|
@Update("{ '$inc' : { 'visits' : ?1 } }")
|
||||||
|
void findAndIncreaseVisitsByLastname(String lastname, int value);
|
||||||
|
|
||||||
|
@Hint("idx-fn")
|
||||||
|
void findWithHintByFirstname(String firstname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.*;
|
|||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.springframework.data.mongodb.repository.Hint;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@@ -173,6 +174,13 @@ public class ReactiveStringBasedAggregationUnitTests {
|
|||||||
verify(operations).execute(any());
|
verify(operations).execute(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3230
|
||||||
|
void aggregatePicksUpHintFromAnnotation() {
|
||||||
|
|
||||||
|
AggregationInvocation invocation = executeAggregation("withHint");
|
||||||
|
assertThat(hintOf(invocation)).isEqualTo("idx");
|
||||||
|
}
|
||||||
|
|
||||||
private AggregationInvocation executeAggregation(String name, Object... args) {
|
private AggregationInvocation executeAggregation(String name, Object... args) {
|
||||||
|
|
||||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(size -> new Class<?>[size]);
|
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(size -> new Class<?>[size]);
|
||||||
@@ -216,6 +224,12 @@ public class ReactiveStringBasedAggregationUnitTests {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Object hintOf(AggregationInvocation invocation) {
|
||||||
|
return invocation.aggregation.getOptions() != null ? invocation.aggregation.getOptions().getHintObject().orElse(null)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
private Class<?> targetTypeOf(AggregationInvocation invocation) {
|
private Class<?> targetTypeOf(AggregationInvocation invocation) {
|
||||||
return invocation.getTargetType();
|
return invocation.getTargetType();
|
||||||
}
|
}
|
||||||
@@ -243,6 +257,10 @@ public class ReactiveStringBasedAggregationUnitTests {
|
|||||||
|
|
||||||
@Aggregation(pipeline = RAW_GROUP_BY_LASTNAME_STRING, collation = "de_AT")
|
@Aggregation(pipeline = RAW_GROUP_BY_LASTNAME_STRING, collation = "de_AT")
|
||||||
Mono<PersonAggregate> aggregateWithCollation(Collation collation);
|
Mono<PersonAggregate> aggregateWithCollation(Collation collation);
|
||||||
|
|
||||||
|
@Hint("idx")
|
||||||
|
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
|
||||||
|
String withHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class PersonAggregate {
|
static class PersonAggregate {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import org.springframework.data.mongodb.core.convert.QueryMapper;
|
|||||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||||
import org.springframework.data.mongodb.core.query.Collation;
|
import org.springframework.data.mongodb.core.query.Collation;
|
||||||
import org.springframework.data.mongodb.repository.Aggregation;
|
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.Meta;
|
||||||
import org.springframework.data.mongodb.repository.Person;
|
import org.springframework.data.mongodb.repository.Person;
|
||||||
import org.springframework.data.projection.ProjectionFactory;
|
import org.springframework.data.projection.ProjectionFactory;
|
||||||
@@ -260,6 +261,13 @@ public class StringBasedAggregationUnitTests {
|
|||||||
.withMessageContaining("Page");
|
.withMessageContaining("Page");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3230
|
||||||
|
void aggregatePicksUpHintFromAnnotation() {
|
||||||
|
|
||||||
|
AggregationInvocation invocation = executeAggregation("withHint");
|
||||||
|
assertThat(hintOf(invocation)).isEqualTo("idx");
|
||||||
|
}
|
||||||
|
|
||||||
private AggregationInvocation executeAggregation(String name, Object... args) {
|
private AggregationInvocation executeAggregation(String name, Object... args) {
|
||||||
|
|
||||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
||||||
@@ -302,6 +310,12 @@ public class StringBasedAggregationUnitTests {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Object hintOf(AggregationInvocation invocation) {
|
||||||
|
return invocation.aggregation.getOptions() != null ? invocation.aggregation.getOptions().getHintObject().orElse(null)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
private Class<?> targetTypeOf(AggregationInvocation invocation) {
|
private Class<?> targetTypeOf(AggregationInvocation invocation) {
|
||||||
return invocation.getTargetType();
|
return invocation.getTargetType();
|
||||||
}
|
}
|
||||||
@@ -350,6 +364,10 @@ public class StringBasedAggregationUnitTests {
|
|||||||
|
|
||||||
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
|
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
|
||||||
String simpleReturnType();
|
String simpleReturnType();
|
||||||
|
|
||||||
|
@Hint("idx")
|
||||||
|
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
|
||||||
|
String withHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface UnsupportedRepository extends Repository<Person, Long> {
|
private interface UnsupportedRepository extends Repository<Person, Long> {
|
||||||
|
|||||||
@@ -297,6 +297,25 @@ lower / upper bounds (`$gt` / `$gte` & `$lt` / `$lte`) according to `Range`
|
|||||||
|
|
||||||
NOTE: If the property criterion compares a document, the order of the fields and exact equality in the document matters.
|
NOTE: If the property criterion compares a document, the order of the fields and exact equality in the document matters.
|
||||||
|
|
||||||
|
[[mongodb.repositories.queries.hint]]
|
||||||
|
=== Repository Index Hints
|
||||||
|
|
||||||
|
The `@Hint` annotation allows to override MongoDB's default index selection and forces the database to use the specified index instead.
|
||||||
|
|
||||||
|
.Example of index hints
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Hint("lastname-idx") <1>
|
||||||
|
List<Person> findByLastname(String lastname);
|
||||||
|
|
||||||
|
@Query(value = "{ 'firstname' : ?0 }", hint="firstname-idx") <2>
|
||||||
|
List<Person> findByFirstname(String firstname);
|
||||||
|
----
|
||||||
|
<1> Use the index with name `lastname-idx`.
|
||||||
|
<2> The `@Query` annotation defines the `hint` alias which is equivalent to explicitly adding the `@Hint` annotation.
|
||||||
|
====
|
||||||
|
|
||||||
[[mongodb.repositories.queries.update]]
|
[[mongodb.repositories.queries.update]]
|
||||||
=== Repository Update Methods
|
=== Repository Update Methods
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user