Skip to content

Commit 5e60867

Browse files
mp911deodrotbohm
authored andcommitted
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.
1 parent 8a5da0e commit 5e60867

File tree

10 files changed

+233
-17
lines changed

10 files changed

+233
-17
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
@@ -164,15 +164,57 @@ public long get() {
164164
final class SingleEntityExecution implements MongoQueryExecution {
165165

166166
private final MongoOperations operations;
167-
private final boolean countProjection;
168167

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

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
@@ -42,6 +42,7 @@
4242
* @author Oliver Gierke
4343
* @author Christoph Strobl
4444
* @author Thomas Darimont
45+
* @author Mark Paluch
4546
*/
4647
public class PartTreeMongoQuery extends AbstractMongoQuery {
4748

@@ -143,6 +144,15 @@ protected boolean isCountQuery() {
143144
return tree.isCountProjection();
144145
}
145146

147+
/*
148+
* (non-Javadoc)
149+
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery()
150+
*/
151+
@Override
152+
protected boolean isExistsQuery() {
153+
return tree.isExistsProjection();
154+
}
155+
146156
/*
147157
* (non-Javadoc)
148158
* @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: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,18 @@
4343
* @author Oliver Gierke
4444
* @author Christoph Strobl
4545
* @author Thomas Darimont
46+
* @author Mark Paluch
4647
*/
4748
public class StringBasedMongoQuery extends AbstractMongoQuery {
4849

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

5354
private final String query;
5455
private final String fieldSpec;
5556
private final boolean isCountQuery;
57+
private final boolean isExistsQuery;
5658
private final boolean isDeleteQuery;
5759
private final List<ParameterBinding> queryParameterBindings;
5860
private final List<ParameterBinding> fieldSpecParameterBindings;
@@ -96,14 +98,26 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
9698
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
9799
method.getFieldSpecification(), this.fieldSpecParameterBindings);
98100

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

102-
if (isCountQuery && isDeleteQuery) {
103-
throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
104-
}
103+
if (method.hasAnnotatedQuery()) {
105104

106-
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
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+
}
107121
}
108122

109123
/*
@@ -136,6 +150,15 @@ protected boolean isCountQuery() {
136150
return isCountQuery;
137151
}
138152

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+
139162
/*
140163
* (non-Javadoc)
141164
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
@@ -145,12 +168,30 @@ protected boolean isDeleteQuery() {
145168
return this.isDeleteQuery;
146169
}
147170

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+
179+
for (boolean value : values) {
180+
181+
if (value) {
182+
count++;
183+
}
184+
}
185+
186+
return count;
187+
}
188+
148189
/**
149190
* A parser that extracts the parameter bindings from a given query string.
150191
*
151192
* @author Thomas Darimont
152193
*/
153-
static enum ParameterBindingParser {
194+
enum ParameterBindingParser {
154195

155196
INSTANCE;
156197

@@ -257,7 +298,7 @@ private static void collectParameterReferencesIntoBindings(List<ParameterBinding
257298

258299
} else if (value instanceof Pattern) {
259300

260-
String string = ((Pattern) value).toString().trim();
301+
String string = value.toString().trim();
261302
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
262303

263304
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
@@ -588,6 +588,23 @@ public void executesAnnotatedCountProjection() {
588588
assertThat(repository.someCountQuery("Matthews"), is(2L));
589589
}
590590

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

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
@@ -313,6 +313,7 @@ private MongoQueryFake createQueryForMethod(String methodName, Class<?>... param
313313
private static class MongoQueryFake extends AbstractMongoQuery {
314314

315315
private boolean isCountQuery;
316+
private boolean isExistsQuery;
316317
private boolean isDeleteQuery;
317318

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

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

0 commit comments

Comments
 (0)