From 8cb92de1ee01d259792f6dd36f9aae89ad6b284d Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Thu, 8 Aug 2013 09:48:42 +0200 Subject: [PATCH] DATAMONGO-445 - Allow to skip unnecessary elements in NearQuery. Added support for skipping elements for NearQuery in MongoTemplate. As mongodb currently (2.4.4) doesn't support he skipping of elements in geoNear-Queries we skip the unnecessary elements ourselves. We use the limit & skip information from the given query or an explicitly passed Pageable. Original pull request: #64. --- .../data/mongodb/core/MongoTemplate.java | 20 ++++- .../data/mongodb/core/geo/GeoResults.java | 2 +- .../data/mongodb/core/query/NearQuery.java | 37 +++++++- .../repository/query/AbstractMongoQuery.java | 6 ++ .../core/query/NearQueryUnitTests.java | 48 ++++++++++ ...tractPersonRepositoryIntegrationTests.java | 90 +++++++++++++++++++ 6 files changed, 200 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index d288cd00d..26c961609 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -566,8 +566,26 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { mongoConverter, entityClass), near.getMetric()); List> result = new ArrayList>(results.size()); + int index = 0; + int elementsToSkip = near.getSkip() != null ? near.getSkip() : 0; + for (Object element : results) { - result.add(callback.doWith((DBObject) element)); + + /* + * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries + * we skip the elements ourselves to avoid at least the document 2 object mapping overhead. + * + * @see https://jira.mongodb.org/browse/SERVER-3925 + */ + if (index >= elementsToSkip) { + result.add(callback.doWith((DBObject) element)); + } + index++; + } + + if (elementsToSkip > 0) { + // as we skipped some elements we have to calculate the averageDistance ourselves: + return new GeoResults(result, near.getMetric()); } DBObject stats = (DBObject) commandResult.get("stats"); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoResults.java index 3827ceb00..a08e547c0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoResults.java @@ -131,7 +131,7 @@ public class GeoResults implements Iterable> { private static Distance calculateAverageDistance(List> results, Metric metric) { if (results.isEmpty()) { - return new Distance(0, null); + return new Distance(0, metric); } double averageDistance = 0; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java index 87a8b7932..c000d4e32 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java @@ -15,6 +15,7 @@ */ package org.springframework.data.mongodb.core.query; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.geo.CustomMetric; import org.springframework.data.mongodb.core.geo.Distance; import org.springframework.data.mongodb.core.geo.Metric; @@ -29,6 +30,7 @@ import com.mongodb.DBObject; * Builder class to build near-queries. * * @author Oliver Gierke + * @author Thomas Darimont */ public class NearQuery { @@ -38,6 +40,7 @@ public class NearQuery { private Metric metric; private boolean spherical; private Integer num; + private Integer skip; /** * Creates a new {@link NearQuery}. @@ -116,7 +119,7 @@ public class NearQuery { } /** - * Configures the number of results to return. + * Configures the maximum number of results to return. * * @param num * @return @@ -126,6 +129,29 @@ public class NearQuery { return this; } + /** + * Configures the number of results to skip. + * + * @param skip + * @return + */ + public NearQuery skip(int skip) { + this.skip = skip; + return this; + } + + /** + * Configures the {@link Pageable} to use. + * + * @param pageable + * @return + */ + public NearQuery with(Pageable pageable) { + this.num = pageable.getOffset() + pageable.getPageSize(); + this.skip = pageable.getOffset(); + return this; + } + /** * Sets the max distance results shall have from the configured origin. If a {@link Metric} was set before the given * value will be interpreted as being a value in that metric. E.g. @@ -290,9 +316,18 @@ public class NearQuery { */ public NearQuery query(Query query) { this.query = query; + this.skip = query.getSkip(); + this.num = query.getLimit(); return this; } + /** + * @return the number of elements to skip. + */ + public Integer getSkip() { + return skip; + } + /** * Returns the {@link DBObject} built by the {@link NearQuery}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index 9f90fa3d4..88e4dc3cb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -38,6 +38,7 @@ import org.springframework.util.Assert; * Base class for {@link RepositoryQuery} implementations for Mongo. * * @author Oliver Gierke + * @author Thomas Darimont */ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -287,6 +288,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { nearQuery.maxDistance(maxDistance).in(maxDistance.getMetric()); } + Pageable pageable = accessor.getPageable(); + if (pageable != null) { + nearQuery.with(pageable); + } + MongoEntityMetadata metadata = method.getEntityInformation(); return (GeoResults) operations.geoNear(nearQuery, metadata.getJavaType(), metadata.getCollectionName()); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java index c4650e6ce..b97bf90d7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java @@ -19,14 +19,18 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.geo.Distance; import org.springframework.data.mongodb.core.geo.Metric; import org.springframework.data.mongodb.core.geo.Metrics; +import org.springframework.data.mongodb.core.geo.Point; /** * Unit tests for {@link NearQuery}. * * @author Oliver Gierke + * @author Thomas Darimont */ public class NearQueryUnitTests { @@ -75,4 +79,48 @@ public class NearQueryUnitTests { query = query.maxDistance(new Distance(200, Metrics.KILOMETERS)); assertThat(query.getMetric(), is((Metric) Metrics.MILES)); } + + /** + * @see DATAMONGO-445 + */ + @Test + public void shouldTakeSkipAndLimitSettingsFromGivenPageable() { + + Pageable pageable = new PageRequest(3, 5); + NearQuery query = NearQuery.near(new Point(1, 1)).with(pageable); + + assertThat(query.getSkip(), is(pageable.getPageNumber() * pageable.getPageSize())); + assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize())); + } + + /** + * @see DATAMONGO-445 + */ + @Test + public void shouldTakeSkipAndLimitSettingsFromGivenQuery() { + + int limit = 10; + int skip = 5; + NearQuery query = NearQuery.near(new Point(1, 1)).query( + Query.query(Criteria.where("foo").is("bar")).limit(limit).skip(skip)); + + assertThat(query.getSkip(), is(skip)); + assertThat((Integer) query.toDBObject().get("num"), is(limit)); + } + + /** + * @see DATAMONGO-445 + */ + @Test + public void shouldTakeSkipAndLimitSettingsFromPageableEvenIfItWasSpecifiedOnQuery() { + + int limit = 10; + int skip = 5; + Pageable pageable = new PageRequest(3, 5); + NearQuery query = NearQuery.near(new Point(1, 1)) + .query(Query.query(Criteria.where("foo").is("bar")).limit(limit).skip(skip)).with(pageable); + + assertThat(query.getSkip(), is(pageable.getPageNumber() * pageable.getPageSize())); + assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize())); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 937de0227..b5b445f91 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -590,4 +590,94 @@ public abstract class AbstractPersonRepositoryIntegrationTests { assertThat(result, hasSize(2)); assertThat(result, hasItems(dave, oliver)); } + + /** + * @see DATAMONGO-445 + */ + @Test + public void executesGeoPageQueryForWithPageRequestForPageInBetween() { + + Point farAway = new Point(-73.9, 40.7); + Point here = new Point(-73.99, 40.73); + + dave.setLocation(farAway); + oliver.setLocation(here); + carter.setLocation(here); + boyd.setLocation(here); + leroi.setLocation(here); + + repository.save(Arrays.asList(dave, oliver, carter, boyd, leroi)); + + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, + Metrics.KILOMETERS), new PageRequest(1, 2)); + + assertThat(results.getContent().isEmpty(), is(false)); + assertThat(results.getNumberOfElements(), is(2)); + assertThat(results.isFirstPage(), is(false)); + assertThat(results.isLastPage(), is(false)); + assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); + assertThat(results.getAverageDistance().getNormalizedValue(), is(0.0)); + } + + /** + * @see DATAMONGO-445 + */ + @Test + public void executesGeoPageQueryForWithPageRequestForPageAtTheEnd() { + + Point point = new Point(-73.99171, 40.738868); + + dave.setLocation(point); + oliver.setLocation(point); + carter.setLocation(point); + + repository.save(Arrays.asList(dave, oliver, carter)); + + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, + Metrics.KILOMETERS), new PageRequest(1, 2)); + assertThat(results.getContent().isEmpty(), is(false)); + assertThat(results.getNumberOfElements(), is(1)); + assertThat(results.isFirstPage(), is(false)); + assertThat(results.isLastPage(), is(true)); + assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); + } + + /** + * @see DATAMONGO-445 + */ + @Test + public void executesGeoPageQueryForWithPageRequestForJustOneElement() { + + Point point = new Point(-73.99171, 40.738868); + dave.setLocation(point); + repository.save(dave); + + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, + Metrics.KILOMETERS), new PageRequest(0, 2)); + + assertThat(results.getContent().isEmpty(), is(false)); + assertThat(results.getNumberOfElements(), is(1)); + assertThat(results.isFirstPage(), is(true)); + assertThat(results.isLastPage(), is(true)); + assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); + } + + /** + * @see DATAMONGO-445 + */ + @Test + public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { + + dave.setLocation(new Point(-73.99171, 40.738868)); + repository.save(dave); + + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, + Metrics.KILOMETERS), new PageRequest(1, 2)); + + assertThat(results.getContent().isEmpty(), is(true)); + assertThat(results.getNumberOfElements(), is(0)); + assertThat(results.isFirstPage(), is(false)); + assertThat(results.isLastPage(), is(true)); + assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); + } }