Skip to content

Commit 6fa3a86

Browse files
committed
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 PersonReposotiry 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); }
1 parent 6ff1f2c commit 6fa3a86

File tree

11 files changed

+229
-18
lines changed

11 files changed

+229
-18
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.repository;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
import org.springframework.core.annotation.AliasFor;
25+
26+
/**
27+
* Annotation to declare finder exists queries directly on repository methods. Both attributes allow using a placeholder
28+
* notation of {@code ?0}, {@code ?1} and so on.
29+
*
30+
* @author Mark Paluch
31+
* @since 1.10
32+
*/
33+
@Retention(RetentionPolicy.RUNTIME)
34+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
35+
@Documented
36+
@Query(exists = true)
37+
public @interface ExistsQuery {
38+
39+
/**
40+
* Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the
41+
* method name then. Alias for {@link Query#value}.
42+
*
43+
* @return
44+
*/
45+
@AliasFor(annotation = Query.class)
46+
String value() default "";
47+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@
6262
*/
6363
boolean count() default false;
6464

65+
/**
66+
* Returns whether the query defined should be executed as exists projection.
67+
*
68+
* @since 1.10
69+
* @return
70+
*/
71+
boolean exists() default false;
72+
6573
/**
6674
* Returns whether the query should delete matching documents.
6775
*

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import org.springframework.data.mongodb.core.MongoOperations;
2121
import org.springframework.data.mongodb.core.query.Query;
2222
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CollectionExecution;
23+
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CountExecution;
2324
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
25+
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ExistsExecution;
2426
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution;
2527
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution;
2628
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagingGeoNearExecution;
@@ -40,6 +42,7 @@
4042
* @author Oliver Gierke
4143
* @author Thomas Darimont
4244
* @author Christoph Strobl
45+
* @author Mark Paluch
4346
*/
4447
public abstract class AbstractMongoQuery implements RepositoryQuery {
4548

@@ -123,8 +126,12 @@ private MongoQueryExecution getExecutionToWrap(Query query, MongoParameterAccess
123126
return new CollectionExecution(operations, accessor.getPageable());
124127
} else if (method.isPageQuery()) {
125128
return new PagedExecution(operations, accessor.getPageable());
129+
} else if (isCountQuery()) {
130+
return new CountExecution(operations);
131+
} else if (isExistsQuery()) {
132+
return new ExistsExecution(operations);
126133
} else {
127-
return new SingleEntityExecution(operations, isCountQuery());
134+
return new SingleEntityExecution(operations);
128135
}
129136
}
130137

@@ -164,6 +171,14 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) {
164171
*/
165172
protected abstract boolean isCountQuery();
166173

174+
/**
175+
* Returns whether the query should get an exists projection applied.
176+
*
177+
* @return
178+
* @since 1.10
179+
*/
180+
protected abstract boolean isExistsQuery();
181+
167182
/**
168183
* Return weather the query should delete matching documents.
169184
*

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,57 @@ public long get() {
163163
static final class SingleEntityExecution implements MongoQueryExecution {
164164

165165
private final MongoOperations operations;
166-
private final boolean countProjection;
167166

168167
/*
169168
* (non-Javadoc)
170169
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
171170
*/
172171
@Override
173172
public Object execute(Query query, Class<?> type, String collection) {
174-
return countProjection ? operations.count(query, type, collection) : operations.findOne(query, type, collection);
173+
return operations.findOne(query, type, collection);
174+
}
175+
}
176+
177+
/**
178+
* {@link MongoQueryExecution} to perform a count projection.
179+
*
180+
* @author Oliver Gierke
181+
* @author Mark Paluch
182+
* @since 1.10
183+
*/
184+
@RequiredArgsConstructor
185+
static final class CountExecution implements MongoQueryExecution {
186+
187+
private final MongoOperations operations;
188+
189+
/*
190+
* (non-Javadoc)
191+
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
192+
*/
193+
@Override
194+
public Object execute(Query query, Class<?> type, String collection) {
195+
return operations.count(query, type, collection);
196+
}
197+
}
198+
199+
/**
200+
* {@link MongoQueryExecution} to perform an exists projection.
201+
*
202+
* @author Mark Paluch
203+
* @since 1.10
204+
*/
205+
@RequiredArgsConstructor
206+
static final class ExistsExecution implements MongoQueryExecution {
207+
208+
private final MongoOperations operations;
209+
210+
/*
211+
* (non-Javadoc)
212+
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
213+
*/
214+
@Override
215+
public Object execute(Query query, Class<?> type, String collection) {
216+
return operations.exists(query, type, collection);
175217
}
176218
}
177219

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.data.projection.ProjectionFactory;
3434
import org.springframework.data.repository.core.RepositoryMetadata;
3535
import org.springframework.data.repository.query.QueryMethod;
36+
import org.springframework.data.repository.util.QueryExecutionConverters;
3637
import org.springframework.data.util.ClassTypeInformation;
3738
import org.springframework.data.util.TypeInformation;
3839
import org.springframework.util.Assert;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* @author Oliver Gierke
4141
* @author Christoph Strobl
4242
* @author Thomas Darimont
43+
* @author Mark Paluch
4344
*/
4445
public class PartTreeMongoQuery extends AbstractMongoQuery {
4546

@@ -141,6 +142,15 @@ protected boolean isCountQuery() {
141142
return tree.isCountProjection();
142143
}
143144

145+
/*
146+
* (non-Javadoc)
147+
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery()
148+
*/
149+
@Override
150+
protected boolean isExistsQuery() {
151+
return tree.isExistsProjection();
152+
}
153+
144154
/*
145155
* (non-Javadoc)
146156
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()

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

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2015 the original author or authors.
2+
* Copyright 2011-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,16 +42,18 @@
4242
* @author Oliver Gierke
4343
* @author Christoph Strobl
4444
* @author Thomas Darimont
45+
* @author Mark Paluch
4546
*/
4647
public class StringBasedMongoQuery extends AbstractMongoQuery {
4748

48-
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
49+
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!";
4950
private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class);
5051
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
5152

5253
private final String query;
5354
private final String fieldSpec;
5455
private final boolean isCountQuery;
56+
private final boolean isExistsQuery;
5557
private final boolean isDeleteQuery;
5658
private final List<ParameterBinding> queryParameterBindings;
5759
private final List<ParameterBinding> fieldSpecParameterBindings;
@@ -95,14 +97,27 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
9597
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
9698
method.getFieldSpecification(), this.fieldSpecParameterBindings);
9799

98-
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
99-
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
100+
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
100101

101-
if (isCountQuery && isDeleteQuery) {
102-
throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
103-
}
104102

105-
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
103+
if (method.hasAnnotatedQuery()) {
104+
105+
org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
106+
107+
this.isCountQuery = queryAnnotation.count();
108+
this.isExistsQuery = queryAnnotation.exists();
109+
this.isDeleteQuery = queryAnnotation.delete();
110+
111+
if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) {
112+
throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method));
113+
}
114+
115+
} else {
116+
117+
this.isCountQuery = false;
118+
this.isExistsQuery = false;
119+
this.isDeleteQuery = false;
120+
}
106121
}
107122

108123
/*
@@ -135,6 +150,15 @@ protected boolean isCountQuery() {
135150
return isCountQuery;
136151
}
137152

153+
/*
154+
* (non-Javadoc)
155+
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery()
156+
*/
157+
@Override
158+
protected boolean isExistsQuery() {
159+
return isExistsQuery;
160+
}
161+
138162
/*
139163
* (non-Javadoc)
140164
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
@@ -144,12 +168,27 @@ protected boolean isDeleteQuery() {
144168
return this.isDeleteQuery;
145169
}
146170

171+
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) {
172+
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
173+
}
174+
175+
private static int countBooleanValues(boolean... values) {
176+
177+
int count = 0;
178+
for (boolean value : values) {
179+
if (value) {
180+
count++;
181+
}
182+
}
183+
return count;
184+
}
185+
147186
/**
148187
* A parser that extracts the parameter bindings from a given query string.
149188
*
150189
* @author Thomas Darimont
151190
*/
152-
private static enum ParameterBindingParser {
191+
private enum ParameterBindingParser {
153192

154193
INSTANCE;
155194

@@ -256,7 +295,7 @@ private static void collectParameterReferencesIntoBindings(List<ParameterBinding
256295

257296
} else if (value instanceof Pattern) {
258297

259-
String string = ((Pattern) value).toString().trim();
298+
String string = value.toString().trim();
260299
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
261300

262301
while (valueMatcher.find()) {

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,23 @@ public void executesAnnotatedCountProjection() {
589589
assertThat(repository.someCountQuery("Matthews"), is(2L));
590590
}
591591

592+
/**
593+
* @see DATAMONGO-1454
594+
*/
595+
@Test
596+
public void executesDerivedExistsProjectionToBoolean() {
597+
assertThat(repository.existsByFirstname("Oliver August"), is(true));
598+
assertThat(repository.existsByFirstname("Hans Peter"), is(false));
599+
}
600+
601+
/**
602+
* @see DATAMONGO-1454
603+
*/
604+
@Test
605+
public void executesAnnotatedExistProjection() {
606+
assertThat(repository.someExistQuery("Matthews"), is(true));
607+
}
608+
592609
/**
593610
* @see DATAMONGO-701
594611
*/

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @author Thomas Darimont
4444
* @author Christoph Strobl
4545
* @author Fırat KÜÇÜK
46+
* @author Mark Paluch
4647
*/
4748
public interface PersonRepository extends MongoRepository<Person, String>, QueryDslPredicateExecutor<Person> {
4849

@@ -251,6 +252,17 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
251252
@Query(value = "{ 'lastname' : ?0 }", count = true)
252253
long someCountQuery(String lastname);
253254

255+
/**
256+
* @see DATAMONGO-1454
257+
*/
258+
boolean existsByFirstname(String firstname);
259+
260+
/**
261+
* @see DATAMONGO-1454
262+
*/
263+
@ExistsQuery(value = "{ 'lastname' : ?0 }")
264+
boolean someExistQuery(String lastname);
265+
254266
/**
255267
* @see DATAMONGO-770
256268
*/

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ private MongoQueryFake createQueryForMethod(String methodName, Class<?>... param
312312
private static class MongoQueryFake extends AbstractMongoQuery {
313313

314314
private boolean isCountQuery;
315+
private boolean isExistsQuery;
315316
private boolean isDeleteQuery;
316317

317318
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
@@ -328,6 +329,11 @@ protected boolean isCountQuery() {
328329
return isCountQuery;
329330
}
330331

332+
@Override
333+
protected boolean isExistsQuery() {
334+
return false;
335+
}
336+
331337
@Override
332338
protected boolean isDeleteQuery() {
333339
return isDeleteQuery;

0 commit comments

Comments
 (0)