DATAMONGO-1454 - Add support for exists projection in repository query methods.
We now support exists projections for query methods in query methods for derived and string queries.
public PersonRepository extends Repository<Person, String> {
boolean existsByFirstname(String firstname);
@ExistsQuery(value = "{ 'lastname' : ?0 }")
boolean someExistQuery(String lastname);
@Query(value = "{ 'lastname' : ?0 }", exists = true)
boolean anotherExistQuery(String lastname);
}
Original pull request: #381.
This commit is contained in:
committed by
Oliver Gierke
parent
8a5da0e737
commit
5e60867750
@@ -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 "";
|
||||
}
|
||||
@@ -62,6 +62,14 @@ public @interface Query {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,9 @@ import org.springframework.data.convert.EntityInstantiators;
|
||||
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 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
|
||||
@@ -123,8 +126,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
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 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -164,7 +164,6 @@ interface MongoQueryExecution {
|
||||
final class SingleEntityExecution implements MongoQueryExecution {
|
||||
|
||||
private final MongoOperations operations;
|
||||
private final boolean countProjection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -172,7 +171,50 @@ interface 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.mongodb.util.JSONParseException;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
|
||||
@@ -143,6 +144,15 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
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()
|
||||
|
||||
@@ -43,16 +43,18 @@ import com.mongodb.util.JSON;
|
||||
* @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<ParameterBinding> queryParameterBindings;
|
||||
private final List<ParameterBinding> fieldSpecParameterBindings;
|
||||
@@ -96,14 +98,26 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -136,6 +150,15 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
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()
|
||||
@@ -145,12 +168,30 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
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
|
||||
*/
|
||||
static enum ParameterBindingParser {
|
||||
enum ParameterBindingParser {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -257,7 +298,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
|
||||
} else if (value instanceof Pattern) {
|
||||
|
||||
String string = ((Pattern) value).toString().trim();
|
||||
String string = value.toString().trim();
|
||||
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
|
||||
|
||||
while (valueMatcher.find()) {
|
||||
|
||||
@@ -588,6 +588,23 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
assertThat(repository.someCountQuery("Matthews"), is(2L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1454
|
||||
*/
|
||||
@Test
|
||||
public void executesDerivedExistsProjectionToBoolean() {
|
||||
assertThat(repository.existsByFirstname("Oliver August"), is(true));
|
||||
assertThat(repository.existsByFirstname("Hans Peter"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1454
|
||||
*/
|
||||
@Test
|
||||
public void executesAnnotatedExistProjection() {
|
||||
assertThat(repository.someExistQuery("Matthews"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-701
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.springframework.data.repository.query.Param;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Fırat KÜÇÜK
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface PersonRepository extends MongoRepository<Person, String>, QueryDslPredicateExecutor<Person> {
|
||||
|
||||
@@ -251,6 +252,17 @@ public interface PersonRepository extends MongoRepository<Person, String>, 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
|
||||
*/
|
||||
|
||||
@@ -313,6 +313,7 @@ public class AbstractMongoQueryUnitTests {
|
||||
private static class MongoQueryFake extends AbstractMongoQuery {
|
||||
|
||||
private boolean isCountQuery;
|
||||
private boolean isExistsQuery;
|
||||
private boolean isDeleteQuery;
|
||||
|
||||
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
|
||||
@@ -329,6 +330,11 @@ public class AbstractMongoQueryUnitTests {
|
||||
return isCountQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return isDeleteQuery;
|
||||
|
||||
@@ -59,6 +59,7 @@ import com.mongodb.DBRef;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StringBasedMongoQueryUnitTests {
|
||||
@@ -146,7 +147,7 @@ public class StringBasedMongoQueryUnitTests {
|
||||
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()));
|
||||
@@ -176,8 +177,11 @@ public class StringBasedMongoQueryUnitTests {
|
||||
@Test
|
||||
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
|
||||
|
||||
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {
|
||||
new Document("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1) });
|
||||
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter,
|
||||
StubParameterAccessor.getAccessor(converter, //
|
||||
new Document("firstname", "first").append("lastname", "last"), //
|
||||
Collections.singletonMap("lastname", 1)));
|
||||
|
||||
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", Document.class,
|
||||
Map.class);
|
||||
|
||||
@@ -194,7 +198,7 @@ public class StringBasedMongoQueryUnitTests {
|
||||
@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);
|
||||
@@ -209,7 +213,7 @@ public class StringBasedMongoQueryUnitTests {
|
||||
@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);
|
||||
|
||||
@@ -361,6 +365,17 @@ public class StringBasedMongoQueryUnitTests {
|
||||
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
@@ -419,5 +434,8 @@ public class StringBasedMongoQueryUnitTests {
|
||||
|
||||
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}")
|
||||
List<Person> findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2);
|
||||
|
||||
@Query(value = "{ 'lastname' : ?0 }", exists = true)
|
||||
boolean existsByLastname(String lastname);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user