Skip to content

Commit 5a78d99

Browse files
mp911deodrotbohm
authored andcommitted
DATAMONGO-1245 - Initial documentation for Query by Example.
Adopt changes from query by example API refactoring. Related tickets: DATACMNS-810. Original pull request: spring-projects#341.
1 parent 693f5dd commit 5a78d99

25 files changed

+587
-348
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

+62-52
Large diffs are not rendered by default.

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java

+62-44
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015 the original author or authors.
2+
* Copyright 2015-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.
@@ -17,22 +17,30 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.HashMap;
2022
import java.util.Iterator;
2123
import java.util.List;
2224
import java.util.Map;
25+
import java.util.Map.Entry;
26+
import java.util.Set;
2327
import java.util.Stack;
2428
import java.util.regex.Pattern;
2529

30+
import org.bson.BasicBSONObject;
2631
import org.springframework.data.domain.Example;
27-
import org.springframework.data.domain.Example.NullHandler;
28-
import org.springframework.data.domain.Example.StringMatcher;
29-
import org.springframework.data.domain.PropertySpecifier;
32+
import org.springframework.data.domain.ExampleSpec;
33+
import org.springframework.data.domain.ExampleSpec.NullHandler;
34+
import org.springframework.data.domain.ExampleSpec.PropertyValueTransformer;
35+
import org.springframework.data.domain.ExampleSpec.StringMatcher;
3036
import org.springframework.data.mapping.PropertyHandler;
3137
import org.springframework.data.mapping.context.MappingContext;
3238
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3339
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3440
import org.springframework.data.mongodb.core.query.MongoRegexCreator;
3541
import org.springframework.data.mongodb.core.query.SerializationUtils;
42+
import org.springframework.data.repository.core.support.ExampleSpecAccessor;
43+
import org.springframework.data.repository.query.parser.Part.Type;
3644
import org.springframework.util.ObjectUtils;
3745
import org.springframework.util.StringUtils;
3846

@@ -41,57 +49,71 @@
4149

4250
/**
4351
* @author Christoph Strobl
52+
* @author Mark Paluch
4453
* @since 1.8
4554
*/
4655
public class MongoExampleMapper {
4756

4857
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
4958
private final MongoConverter converter;
59+
private final Map<StringMatcher, Type> stringMatcherPartMapping = new HashMap<StringMatcher, Type>();
5060

5161
public MongoExampleMapper(MongoConverter converter) {
5262

5363
this.converter = converter;
5464
this.mappingContext = converter.getMappingContext();
65+
66+
stringMatcherPartMapping.put(StringMatcher.EXACT, Type.SIMPLE_PROPERTY);
67+
stringMatcherPartMapping.put(StringMatcher.CONTAINING, Type.CONTAINING);
68+
stringMatcherPartMapping.put(StringMatcher.STARTING, Type.STARTING_WITH);
69+
stringMatcherPartMapping.put(StringMatcher.ENDING, Type.ENDING_WITH);
70+
stringMatcherPartMapping.put(StringMatcher.REGEX, Type.REGEX);
5571
}
5672

5773
/**
5874
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from
5975
* {@link Example#getProbe()}.
60-
*
76+
*
6177
* @param example
6278
* @return
6379
* @since 1.8
6480
*/
6581
public DBObject getMappedExample(Example<?> example) {
66-
return getMappedExample(example, mappingContext.getPersistentEntity(example.getSampleType()));
82+
return getMappedExample(example, mappingContext.getPersistentEntity(example.getProbeType()));
6783
}
6884

6985
/**
7086
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from
7187
* {@link Example#getProbe()}.
72-
*
88+
*
7389
* @param example
7490
* @param entity
7591
* @return
7692
* @since 1.8
7793
*/
7894
public DBObject getMappedExample(Example<?> example, MongoPersistentEntity<?> entity) {
7995

80-
DBObject reference = (DBObject) converter.convertToMongoType(example.getSampleObject());
96+
DBObject reference = (DBObject) converter.convertToMongoType(example.getProbe());
8197

82-
if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getSampleObject()).getIdentifier() == null) {
98+
if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getProbe()).getIdentifier() == null) {
8399
reference.removeField(entity.getIdProperty().getFieldName());
84100
}
85101

86-
applyPropertySpecs("", reference, example);
102+
ExampleSpecAccessor exampleSpecAccessor = new ExampleSpecAccessor(example.getExampleSpec());
103+
104+
applyPropertySpecs("", reference, example.getProbeType(), exampleSpecAccessor);
87105

88-
return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, example.getNullHandler()) ? reference : new BasicDBObject(
89-
SerializationUtils.flatMap(reference));
106+
if (exampleSpecAccessor.isTyped()) {
107+
this.converter.getTypeMapper().writeTypeRestrictions(reference, (Set) Collections.singleton(example.getResultType()));
108+
}
109+
110+
return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, exampleSpecAccessor.getNullHandler()) ? reference
111+
: new BasicDBObject(SerializationUtils.flattenMap(reference));
90112
}
91113

92-
private String getMappedPropertyPath(String path, Example<?> example) {
114+
private String getMappedPropertyPath(String path, Class<?> probeType) {
93115

94-
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(example.getSampleType());
116+
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(probeType);
95117

96118
Iterator<String> parts = Arrays.asList(path.split("\\.")).iterator();
97119

@@ -136,7 +158,8 @@ public void doWithPersistentProperty(MongoPersistentProperty property) {
136158

137159
}
138160

139-
private void applyPropertySpecs(String path, DBObject source, Example<?> example) {
161+
private void applyPropertySpecs(String path, DBObject source, Class<?> probeType,
162+
ExampleSpecAccessor exampleSpecAccessor) {
140163

141164
if (!(source instanceof BasicDBObject)) {
142165
return;
@@ -147,47 +170,37 @@ private void applyPropertySpecs(String path, DBObject source, Example<?> example
147170
while (iter.hasNext()) {
148171

149172
Map.Entry<String, Object> entry = iter.next();
150-
151-
if (entry.getKey().equals("_id") && entry.getValue() == null) {
173+
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey();
174+
String mappedPropertyPath = getMappedPropertyPath(propertyPath, probeType);
175+
176+
if(isEmptyIdProperty(entry)) {
152177
iter.remove();
153178
continue;
154179
}
155-
156-
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey();
157-
158-
String mappedPropertyPath = getMappedPropertyPath(propertyPath, example);
159-
if (example.isIgnoredPath(propertyPath) || example.isIgnoredPath(mappedPropertyPath)) {
180+
181+
if (exampleSpecAccessor.isIgnoredPath(propertyPath) || exampleSpecAccessor.isIgnoredPath(mappedPropertyPath)) {
160182
iter.remove();
161183
continue;
162184
}
163185

164-
PropertySpecifier specifier = null;
165-
StringMatcher stringMatcher = example.getDefaultStringMatcher();
186+
StringMatcher stringMatcher = exampleSpecAccessor.getDefaultStringMatcher();
166187
Object value = entry.getValue();
167-
boolean ignoreCase = example.isIngnoreCaseEnabled();
188+
boolean ignoreCase = exampleSpecAccessor.isIgnoreCaseEnabled();
168189

169-
if (example.hasPropertySpecifiers()) {
190+
if (exampleSpecAccessor.hasPropertySpecifiers()) {
170191

171-
mappedPropertyPath = example.hasPropertySpecifier(propertyPath) ? propertyPath : getMappedPropertyPath(
172-
propertyPath, example);
192+
mappedPropertyPath = exampleSpecAccessor.hasPropertySpecifier(propertyPath) ? propertyPath
193+
: getMappedPropertyPath(propertyPath, probeType);
173194

174-
specifier = example.getPropertySpecifier(mappedPropertyPath);
175-
176-
if (specifier != null) {
177-
if (specifier.hasStringMatcher()) {
178-
stringMatcher = specifier.getStringMatcher();
179-
}
180-
if (specifier.getIgnoreCase() != null) {
181-
ignoreCase = specifier.getIgnoreCase();
182-
}
183-
184-
}
195+
stringMatcher = exampleSpecAccessor.getStringMatcherForPath(mappedPropertyPath);
196+
ignoreCase = exampleSpecAccessor.isIgnoreCaseForPath(mappedPropertyPath);
185197
}
186198

187199
// TODO: should a PropertySpecifier outrule the later on string matching?
188-
if (specifier != null) {
200+
if (exampleSpecAccessor.hasPropertySpecifier(mappedPropertyPath)) {
189201

190-
value = specifier.transformValue(value);
202+
PropertyValueTransformer valueTransformer = exampleSpecAccessor.getValueTransformerForPath(mappedPropertyPath);
203+
value = valueTransformer.convert(value);
191204
if (value == null) {
192205
iter.remove();
193206
continue;
@@ -199,11 +212,15 @@ private void applyPropertySpecs(String path, DBObject source, Example<?> example
199212
if (entry.getValue() instanceof String) {
200213
applyStringMatcher(entry, stringMatcher, ignoreCase);
201214
} else if (entry.getValue() instanceof BasicDBObject) {
202-
applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), example);
215+
applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), probeType, exampleSpecAccessor);
203216
}
204217
}
205218
}
206219

220+
private boolean isEmptyIdProperty(Entry<String, Object> entry) {
221+
return entry.getKey().equals("_id") && entry.getValue() == null;
222+
}
223+
207224
private void applyStringMatcher(Map.Entry<String, Object> entry, StringMatcher stringMatcher, boolean ignoreCase) {
208225

209226
BasicDBObject dbo = new BasicDBObject();
@@ -216,8 +233,8 @@ private void applyStringMatcher(Map.Entry<String, Object> entry, StringMatcher s
216233
}
217234
} else {
218235

219-
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(),
220-
stringMatcher.getPartType());
236+
Type type = stringMatcherPartMapping.get(stringMatcher);
237+
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(), type);
221238
dbo.put("$regex", expression);
222239
entry.setValue(dbo);
223240
}
@@ -226,4 +243,5 @@ private void applyStringMatcher(Map.Entry<String, Object> entry, StringMatcher s
226243
dbo.put("$options", "i");
227244
}
228245
}
246+
229247
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ protected DBObject getMappedKeyword(Field property, Keyword keyword) {
261261
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
262262
Object value = keyword.getValue();
263263

264-
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) : getMappedValue(
265-
property.with(keyword.getKey()), value);
264+
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property)
265+
: getMappedValue(property.with(keyword.getKey()), value);
266266

267267
return new BasicDBObject(keyword.key, convertedValue);
268268
}
@@ -484,8 +484,8 @@ public Object convertId(Object id) {
484484
}
485485

486486
try {
487-
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService
488-
.convert(id, ObjectId.class) : delegateConvertToMongoType(id, null);
487+
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService.convert(id, ObjectId.class)
488+
: delegateConvertToMongoType(id, null);
489489
} catch (ConversionException o_O) {
490490
return delegateConvertToMongoType(id, null);
491491
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static Criteria where(String key) {
9898
* @since 1.8
9999
*/
100100
public static Criteria byExample(Object example) {
101-
return byExample(new Example<Object>(example));
101+
return byExample(Example.of(example));
102102
}
103103

104104
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015 the original author or authors.
2+
* Copyright 2015-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.
@@ -22,6 +22,7 @@
2222

2323
/**
2424
* @author Christoph Strobl
25+
* @author Mark Paluch
2526
* @since 1.8
2627
*/
2728
public enum MongoRegexCreator {
@@ -67,6 +68,10 @@ public String toRegularExpression(String source, Type type) {
6768

6869
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Type type) {
6970

71+
if (ObjectUtils.nullSafeEquals(Type.REGEX, type)) {
72+
return source;
73+
}
74+
7075
if (!ObjectUtils.nullSafeEquals(Type.LIKE, type)) {
7176
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source;
7277
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-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.
@@ -63,7 +63,7 @@ private SerializationUtils() {
6363
* @return {@link Collections#emptyMap()} when source is {@literal null}
6464
* @since 1.8
6565
*/
66-
public static Map<String, Object> flatMap(DBObject source) {
66+
public static Map<String, Object> flattenMap(DBObject source) {
6767

6868
if (source == null) {
6969
return Collections.emptyMap();

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

+13-31
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,22 @@
1919
import java.util.List;
2020

2121
import org.springframework.data.domain.Example;
22-
import org.springframework.data.domain.Page;
23-
import org.springframework.data.domain.Pageable;
2422
import org.springframework.data.domain.Sort;
2523
import org.springframework.data.repository.NoRepositoryBean;
2624
import org.springframework.data.repository.PagingAndSortingRepository;
25+
import org.springframework.data.repository.query.QueryByExampleExecutor;
2726

2827
/**
2928
* Mongo specific {@link org.springframework.data.repository.Repository} interface.
30-
*
29+
*
3130
* @author Oliver Gierke
3231
* @author Christoph Strobl
3332
* @author Thomas Darimont
33+
* @author Mark Paluch
3434
*/
3535
@NoRepositoryBean
36-
public interface MongoRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
36+
public interface MongoRepository<T, ID extends Serializable>
37+
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
3738

3839
/*
3940
* (non-Javadoc)
@@ -57,7 +58,7 @@ public interface MongoRepository<T, ID extends Serializable> extends PagingAndSo
5758
* Inserts the given a given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use
5859
* the returned instance for further operations as the save operation might have changed the entity instance
5960
* completely. Prefer using {@link #save(Object)} instead to avoid the usage of store-specific API.
60-
*
61+
*
6162
* @param entity must not be {@literal null}.
6263
* @return the saved entity
6364
* @since 1.7
@@ -68,40 +69,21 @@ public interface MongoRepository<T, ID extends Serializable> extends PagingAndSo
6869
* Inserts the given entities. Assumes the given entities to have not been persisted yet and thus will optimize the
6970
* insert over a call to {@link #save(Iterable)}. Prefer using {@link #save(Iterable)} to avoid the usage of store
7071
* specific API.
71-
*
72+
*
7273
* @param entities must not be {@literal null}.
7374
* @return the saved entities
7475
* @since 1.7
7576
*/
7677
<S extends T> List<S> insert(Iterable<S> entities);
7778

78-
/**
79-
* Returns all instances of the type specified by the given {@link Example}.
80-
*
81-
* @param example must not be {@literal null}.
82-
* @return
83-
* @since 1.8
79+
/* (non-Javadoc)
80+
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
8481
*/
85-
<S extends T> List<T> findAllByExample(Example<S> example);
82+
<S extends T> List<S> findAll(Example<S> example);
8683

87-
/**
88-
* Returns all instances of the type specified by the given {@link Example}.
89-
*
90-
* @param example must not be {@literal null}.
91-
* @param sort can be {@literal null}.
92-
* @return all entities sorted by the given options
93-
* @since 1.8
84+
/* (non-Javadoc)
85+
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
9486
*/
95-
<S extends T> List<T> findAllByExample(Example<S> example, Sort sort);
87+
<S extends T> List<S> findAll(Example<S> example, Sort sort);
9688

97-
/**
98-
* Returns a {@link Page} of entities meeting the paging restriction specified by the given {@link Example} limited to
99-
* criteria provided in the {@code Pageable} object.
100-
*
101-
* @param example must not be {@literal null}.
102-
* @param pageable can be {@literal null}.
103-
* @return a {@link Page} of entities
104-
* @since 1.8
105-
*/
106-
<S extends T> Page<T> findAllByExample(Example<S> example, Pageable pageable);
10789
}

0 commit comments

Comments
 (0)