DATAMONGO-2264 - Polishing.

Store result to getPersistentEntity() in local variable to reduce invocation count. Use numbered postfix instead of object instantiation to generate a unique distance field name.

Deprecate geoNear methods with hint to aggregations. Fix distance field usage in ReactiveMongoTemplate.

Tweak docs.

Original pull request: #744.
This commit is contained in:
Mark Paluch
2019-05-06 11:34:31 +02:00
parent c3769df07f
commit c15ab4c946
9 changed files with 90 additions and 29 deletions

View File

@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Optional;
import org.bson.Document;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mapping.IdentifierAccessor;
@@ -44,7 +45,6 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/**
* Common operations performed on an entity in the context of it's mapping metadata.
@@ -173,14 +173,15 @@ class EntityOperations {
*/
public String nearQueryDistanceFieldName(Class<?> domainType) {
if (!context.hasPersistentEntityFor(domainType)
|| context.getPersistentEntity(domainType).getPersistentProperty("dis") == null) {
MongoPersistentEntity<?> persistentEntity = context.getPersistentEntity(domainType);
if (persistentEntity == null || persistentEntity.getPersistentProperty("dis") == null) {
return "dis";
}
String distanceFieldName = "calculated-distance";
while (context.getPersistentEntity(domainType).getPersistentProperty(distanceFieldName) != null) {
distanceFieldName += "-" + ObjectUtils.getIdentityHexString(new Object());
int counter = 0;
while (persistentEntity.getPersistentProperty(distanceFieldName) != null) {
distanceFieldName += "-" + (counter++);
}
return distanceFieldName;

View File

@@ -22,6 +22,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import org.bson.Document;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
@@ -641,24 +642,52 @@ public interface MongoOperations extends FluentMongoOperations {
* information to determine the collection the query is ran against. Note, that MongoDB limits the number of results
* by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a particular number of
* results.
* <p>
* MongoDB 4.2 has removed the {@code geoNear} command. This method uses since version 2.2 aggregations and the
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
* aggregations directly:
* </p>
*
* <pre class="code">
* TypedAggregation&lt;T&gt; geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
* AggregationResults&lt;Document&gt; results = aggregate(geoNear, Document.class);
* </pre>
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @return
* @deprecated since 2.2. The {@code eval} command has been removed in MongoDB Server 4.2.0. Use Aggregations with
* {@link Aggregation#geoNear(NearQuery, String)} instead.
*/
@Deprecated
<T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass);
/**
* Returns {@link GeoResults} for all entities matching the given {@link NearQuery}. Note, that MongoDB limits the
* number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a
* particular number of results.
* <p>
* MongoDB 4.2 has removed the {@code geoNear} command. This method uses since version 2.2 aggregations and the
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
* aggregations directly:
* </p>
*
* <pre class="code">
* TypedAggregation&lt;T&gt; geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
* AggregationResults&lt;Document&gt; results = aggregate(geoNear, Document.class);
* </pre>
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @param collectionName the collection to trigger the query against. If no collection name is given the entity class
* will be inspected. Must not be {@literal null} nor empty.
* @return
* @deprecated since 2.2. The {@code eval} command has been removed in MongoDB Server 4.2.0. Use Aggregations with
* {@link Aggregation#geoNear(NearQuery, String)} instead.
*/
@Deprecated
<T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String collectionName);
/**

View File

@@ -35,6 +35,7 @@ import org.bson.codecs.Codec;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -3302,9 +3303,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
double distance = Double.NaN;
if (object.containsKey(distanceField)) {
distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class)
.doubleValue();
distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class);
}
T doWith = delegate.doWith(object);

View File

@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import org.bson.Document;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
@@ -612,24 +613,52 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* entity mapping information to determine the collection the query is ran against. Note, that MongoDB limits the
* number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a
* particular number of results.
* <p>
* MongoDB 4.2 has removed the {@code geoNear} command. This method uses since version 2.2 aggregations and the
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
* aggregations directly:
* </p>
*
* <pre class="code">
* TypedAggregation&lt;T&gt; geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
* Flux&lt;Document&gt; results = aggregate(geoNear, Document.class);
* </pre>
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @return the converted {@link GeoResult}s.
* @deprecated since 2.2. The {@code eval} command has been removed in MongoDB Server 4.2.0. Use Aggregations with
* {@link Aggregation#geoNear(NearQuery, String)} instead.
*/
@Deprecated
<T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass);
/**
* Returns {@link Flux} of {@link GeoResult} for all entities matching the given {@link NearQuery}. Note, that MongoDB
* limits the number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect
* a particular number of results.
* <p>
* MongoDB 4.2 has removed the {@code geoNear} command. This method uses since version 2.2 aggregations and the
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
* aggregations directly:
* </p>
*
* <pre class="code">
* TypedAggregation&lt;T&gt; geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
* Flux&lt;Document&gt; results = aggregate(geoNear, Document.class);
* </pre>
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @param collectionName the collection to trigger the query against. If no collection name is given the entity class
* will be inspected.
* @return the converted {@link GeoResult}s.
* @deprecated since 2.2. The {@code eval} command has been removed in MongoDB Server 4.2.0. Use Aggregations with
* {@link Aggregation#geoNear(NearQuery, String)} instead.
*/
@Deprecated
<T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass, String collectionName);
/**
@@ -933,8 +962,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's Type
* Conversion"</a> for more details.
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
* Type Conversion"</a> for more details.
* <p/>
* <p/>
* Insert is used to initially store the object into the database. To update an existing object use the save method.
@@ -993,8 +1022,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's Type
* Conversion"</a> for more details.
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
* Type Conversion"</a> for more details.
* <p/>
* <p/>
* Insert is used to initially store the object into the database. To update an existing object use the save method.
@@ -1041,8 +1070,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's Type
* Conversion"</a> for more details.
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
* @return the saved object.
@@ -1078,8 +1107,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's Type
* Conversion"</a> for more details.
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
* @return the saved object.

View File

@@ -1055,7 +1055,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
GeoNearResultDocumentCallback<T> callback = new GeoNearResultDocumentCallback<>(distanceField,
new ProjectingReadCallback<>(mongoConverter, entityClass, returnType, collection), near.getMetric());
Aggregation $geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
Aggregation $geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, distanceField))
.withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
return aggregate($geoNear, collection, Document.class) //
@@ -3040,9 +3040,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
double distance = Double.NaN;
if (object.containsKey(distanceField)) {
distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class)
.doubleValue();
distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class);
}
T doWith = delegate.doWith(object);

View File

@@ -136,7 +136,6 @@ public class ReactiveMongoTemplateUnitTests {
this.mappingContext = new MongoMappingContext();
this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
this.template = new ReactiveMongoTemplate(factory, converter);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1444

View File

@@ -321,12 +321,15 @@ public class ReactiveMongoRepositoryTests {
}
@Test // DATAMONGO-1444
public void findsPeoplePageableGeoresultByLocationWithinBox() {
public void findsPeoplePageableGeoresultByLocationWithinBox() throws InterruptedException {
Point point = new Point(-73.99171, 40.738868);
dave.setLocation(point);
StepVerifier.create(repository.save(dave)).expectNextCount(1).verifyComplete();
// Allow for index creation
Thread.sleep(500);
StepVerifier.create(repository.findByLocationNear(new Point(-73.99, 40.73), //
new Distance(2000, Metrics.KILOMETERS), //
PageRequest.of(0, 10))) //
@@ -338,12 +341,15 @@ public class ReactiveMongoRepositoryTests {
}
@Test // DATAMONGO-1444
public void findsPeopleByLocationWithinBox() {
public void findsPeopleByLocationWithinBox() throws InterruptedException {
Point point = new Point(-73.99171, 40.738868);
dave.setLocation(point);
StepVerifier.create(repository.save(dave)).expectNextCount(1).verifyComplete();
// Allow for index creation
Thread.sleep(500);
StepVerifier.create(repository.findPersonByLocationNear(new Point(-73.99, 40.73), //
new Distance(2000, Metrics.KILOMETERS))) //
.expectNext(dave) //

View File

@@ -3,7 +3,7 @@
[[new-features.2-2-0]]
== What's New in Spring Data MongoDB 2.2
* Compatible with MongoDB 4.2.
* Compatibility with MongoDB 4.2 deprecating `eval`, `group` and `geoNear` Template API methods.
* <<mongo.query.kotlin-support,Type-safe Queries for Kotlin>>
* <<mongodb.reactive.repositories.queries.type-safe,Querydsl support for reactive repositories>> via `ReactiveQuerydslPredicateExecutor`.
* <<reactive.gridfs,Reactive GridFS support>>.

View File

@@ -1343,16 +1343,16 @@ Spring Data MongoDB 2.2 `MongoOperations#geoNear` uses the `$geoNear` https://do
instead of the `geoNear` command to run a `NearQuery`.
The calculated distance (the `dis` when using a geoNear command) previously returned within a wrapper type now is embedded
into the resulting document. If the given domain type already contains a property with equal name, the calculated distance
is named `calculated-distance` or if that name is taken as well, postfixed with a random hash.
into the resulting document.
If the given domain type already contains a property with that name, the calculated distance
is named `calculated-distance` with a potentially random postfix.
Target types may contain a property named after the returned distance to (additionally) read it back directly into the domain
type as shown below.
Target types may contain a property named after the returned distance to (additionally) read it back directly into the domain type as shown below.
[source,java]
----
GeoResults<VenueWithDisField> = template.query(Venue.class) <1>
.as(VenueWithDisField.class) <2>
.as(VenueWithDisField.class) <2>
.near(NearQuery.near(new GeoJsonPoint(-73.99, 40.73), KILOMETERS))
.all();
----