diff --git a/pom.xml b/pom.xml
index ea80a3cb74..b7736caef0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1454-SNAPSHOT
pom
Spring Data MongoDB
@@ -28,7 +28,7 @@
multi
spring-data-mongodb
- 1.13.0.BUILD-SNAPSHOT
+ 1.13.0.DATACMNS-875-SNAPSHOT
2.14.3
2.13.0
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index ae0a5d6c8f..62a47837bd 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1454-SNAPSHOT
../pom.xml
@@ -48,7 +48,7 @@
org.springframework.data
spring-data-mongodb
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1454-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 2d02722262..97b7fc1b72 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1454-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml
index ee5e3336db..995f1a0a04 100644
--- a/spring-data-mongodb-log4j/pom.xml
+++ b/spring-data-mongodb-log4j/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1454-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 8072d3f665..1bc4410a1b 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1454-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java
new file mode 100644
index 0000000000..de937374d9
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 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
+ *
+ * http://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 finder exists queries directly on repository methods. Both attributes allow using a placeholder
+ * notation of {@code ?0}, {@code ?1} and so on.
+ *
+ * @author Mark Paluch
+ * @since 1.10
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Documented
+@Query(exists = true)
+public @interface ExistsQuery {
+
+ /**
+ * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the
+ * method name then. Alias for {@link Query#value}.
+ *
+ * @return
+ */
+ @AliasFor(annotation = Query.class)
+ String value() default "";
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
index 97fb25ff16..a0ad7a905d 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
@@ -62,6 +62,14 @@
*/
boolean count() default false;
+ /**
+ * Returns whether the query defined should be executed as exists projection.
+ *
+ * @since 1.10
+ * @return
+ */
+ boolean exists() default false;
+
/**
* Returns whether the query should delete matching documents.
*
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 d6b65be255..d59efb0450 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
@@ -20,7 +20,9 @@
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CollectionExecution;
+import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CountExecution;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
+import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ExistsExecution;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagingGeoNearExecution;
@@ -40,6 +42,7 @@
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
+ * @author Mark Paluch
*/
public abstract class AbstractMongoQuery implements RepositoryQuery {
@@ -123,8 +126,12 @@ private MongoQueryExecution getExecutionToWrap(Query query, MongoParameterAccess
return new CollectionExecution(operations, accessor.getPageable());
} else if (method.isPageQuery()) {
return new PagedExecution(operations, accessor.getPageable());
+ } else if (isCountQuery()) {
+ return new CountExecution(operations);
+ } else if (isExistsQuery()) {
+ return new ExistsExecution(operations);
} else {
- return new SingleEntityExecution(operations, isCountQuery());
+ return new SingleEntityExecution(operations);
}
}
@@ -164,6 +171,14 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) {
*/
protected abstract boolean isCountQuery();
+ /**
+ * Returns whether the query should get an exists projection applied.
+ *
+ * @return
+ * @since 1.10
+ */
+ protected abstract boolean isExistsQuery();
+
/**
* Return weather the query should delete matching documents.
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java
index 76e98e8c7f..86c8ca1d39 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java
@@ -163,7 +163,6 @@ public long get() {
static final class SingleEntityExecution implements MongoQueryExecution {
private final MongoOperations operations;
- private final boolean countProjection;
/*
* (non-Javadoc)
@@ -171,7 +170,50 @@ static final class SingleEntityExecution implements MongoQueryExecution {
*/
@Override
public Object execute(Query query, Class> type, String collection) {
- return countProjection ? operations.count(query, type, collection) : operations.findOne(query, type, collection);
+ return operations.findOne(query, type, collection);
+ }
+ }
+
+ /**
+ * {@link MongoQueryExecution} to perform a count projection.
+ *
+ * @author Oliver Gierke
+ * @author Mark Paluch
+ * @since 1.10
+ */
+ @RequiredArgsConstructor
+ static final class CountExecution implements MongoQueryExecution {
+
+ private final MongoOperations operations;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
+ */
+ @Override
+ public Object execute(Query query, Class> type, String collection) {
+ return operations.count(query, type, collection);
+ }
+ }
+
+ /**
+ * {@link MongoQueryExecution} to perform an exists projection.
+ *
+ * @author Mark Paluch
+ * @since 1.10
+ */
+ @RequiredArgsConstructor
+ static final class ExistsExecution implements MongoQueryExecution {
+
+ private final MongoOperations operations;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
+ */
+ @Override
+ public Object execute(Query query, Class> type, String collection) {
+ return operations.exists(query, type, collection);
}
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java
index 4c1da7bd9b..f97b64afdb 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java
@@ -40,6 +40,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
+ * @author Mark Paluch
*/
public class PartTreeMongoQuery extends AbstractMongoQuery {
@@ -141,6 +142,15 @@ protected boolean isCountQuery() {
return tree.isCountProjection();
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery()
+ */
+ @Override
+ protected boolean isExistsQuery() {
+ return tree.isExistsProjection();
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
index 33d2e15e7d..c79f872ad1 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2015 the original author or authors.
+ * Copyright 2011-2016 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.
@@ -42,16 +42,18 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
+ * @author Mark Paluch
*/
public class StringBasedMongoQuery extends AbstractMongoQuery {
- private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
+ private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!";
private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class);
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
private final String query;
private final String fieldSpec;
private final boolean isCountQuery;
+ private final boolean isExistsQuery;
private final boolean isDeleteQuery;
private final List queryParameterBindings;
private final List fieldSpecParameterBindings;
@@ -95,14 +97,27 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
method.getFieldSpecification(), this.fieldSpecParameterBindings);
- this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
- this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
+ this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
- if (isCountQuery && isDeleteQuery) {
- throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
- }
- this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
+ if (method.hasAnnotatedQuery()) {
+
+ org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
+
+ this.isCountQuery = queryAnnotation.count();
+ this.isExistsQuery = queryAnnotation.exists();
+ this.isDeleteQuery = queryAnnotation.delete();
+
+ if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) {
+ throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method));
+ }
+
+ } else {
+
+ this.isCountQuery = false;
+ this.isExistsQuery = false;
+ this.isDeleteQuery = false;
+ }
}
/*
@@ -135,6 +150,15 @@ protected boolean isCountQuery() {
return isCountQuery;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery()
+ */
+ @Override
+ protected boolean isExistsQuery() {
+ return isExistsQuery;
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
@@ -144,12 +168,30 @@ protected boolean isDeleteQuery() {
return this.isDeleteQuery;
}
+ private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) {
+ return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
+ }
+
+ private static int countBooleanValues(boolean... values) {
+
+ int count = 0;
+
+ for (boolean value : values) {
+
+ if (value) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
/**
* A parser that extracts the parameter bindings from a given query string.
*
* @author Thomas Darimont
*/
- private static enum ParameterBindingParser {
+ private enum ParameterBindingParser {
INSTANCE;
@@ -256,7 +298,7 @@ private static void collectParameterReferencesIntoBindings(List, QueryDslPredicateExecutor {
@@ -251,6 +252,17 @@ public interface PersonRepository extends MongoRepository, Query
@Query(value = "{ 'lastname' : ?0 }", count = true)
long someCountQuery(String lastname);
+ /**
+ * @see DATAMONGO-1454
+ */
+ boolean existsByFirstname(String firstname);
+
+ /**
+ * @see DATAMONGO-1454
+ */
+ @ExistsQuery(value = "{ 'lastname' : ?0 }")
+ boolean someExistQuery(String lastname);
+
/**
* @see DATAMONGO-770
*/
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
index 7406e63341..83812b560f 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
@@ -312,6 +312,7 @@ private MongoQueryFake createQueryForMethod(String methodName, Class>... param
private static class MongoQueryFake extends AbstractMongoQuery {
private boolean isCountQuery;
+ private boolean isExistsQuery;
private boolean isDeleteQuery;
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
@@ -328,6 +329,11 @@ protected boolean isCountQuery() {
return isCountQuery;
}
+ @Override
+ protected boolean isExistsQuery() {
+ return false;
+ }
+
@Override
protected boolean isDeleteQuery() {
return isDeleteQuery;
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
index 41e0c6ef8c..f3b4fe1672 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
@@ -61,6 +61,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
+ * @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class StringBasedMongoQueryUnitTests {
@@ -148,7 +149,7 @@ public void bindsNullParametersCorrectly() throws Exception {
public void bindsDbrefCorrectly() throws Exception {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByHavingSizeFansNotZero");
- ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {});
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(), is(new BasicQuery("{ fans : { $not : { $size : 0 } } }").getQueryObject()));
@@ -178,8 +179,7 @@ public void preventsDeleteAndCountFlagAtTheSameTime() throws Exception {
@Test
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
- ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {
- new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1) });
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1));
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", DBObject.class,
Map.class);
@@ -196,7 +196,7 @@ public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception
@Test
public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception {
- ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { "fun" });
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "fun");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByTitleBeginsWithExplicitQuoting", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
@@ -210,7 +210,7 @@ public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQu
@Test
public void shouldParseQueryWithParametersInExpression() throws Exception {
- ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { 1, 2, 3, 4 });
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 1, 2, 3, 4);
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithParametersInExpression", int.class,
int.class, int.class, int.class);
@@ -362,6 +362,17 @@ public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception {
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
+ /**
+ * @see DATAMONGO-1454
+ */
+ @Test
+ public void shouldSupportExistsProjection() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class);
+
+ assertThat(mongoQuery.isExistsQuery(), is(true));
+ }
+
private StringBasedMongoQuery createQueryForMethod(String name, Class>... parameters) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameters);
@@ -420,5 +431,8 @@ private interface SampleRepository extends Repository {
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}")
List findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2);
+
+ @Query(value = "{ 'lastname' : ?0 }", exists = true)
+ boolean existsByLastname(String lastname);
}
}