Browse Source

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
pull/4373/head
Christoph Strobl 3 years ago committed by Mark Paluch
parent
commit
7b44f78133
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 48
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Hint.java
  2. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
  3. 16
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
  4. 16
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
  5. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java
  6. 22
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
  7. 1
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java
  8. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java
  9. 28
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
  10. 52
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java
  11. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregationUnitTests.java
  12. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java
  13. 19
      src/main/asciidoc/reference/mongo-repositories.adoc

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

@ -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 "";
}

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

@ -39,6 +39,7 @@ 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 {
/** /**
@ -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">
* &#64;Query(...)
* &#64;Hint("lastname-idx")
* List&lt;User&gt; findAllByLastname(String collation);
* </pre>
*
* @return the index name.
* @since 4.1
* @see Hint#indexName()
*/
@AliasFor(annotation = Hint.class, attribute = "indexName")
String hint() default "";
} }

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

@ -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

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

@ -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

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

@ -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.
* *

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

@ -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() //

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

@ -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();
} }

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

@ -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();
} }

28
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java

@ -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

52
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java

@ -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);
} }
} }

18
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregationUnitTests.java

@ -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 {

18
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java

@ -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> {

19
src/main/asciidoc/reference/mongo-repositories.adoc

@ -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

Loading…
Cancel
Save