Skip to content

Commit 693f5dd

Browse files
christophstroblodrotbohm
authored andcommitted
DATAMONGO-1245 - Add support for Query By Example.
An explorative approach to QBE trying find possibilities and limitations. We now support querying documents by providing a sample of the given object holding compare values. For the sake of partial matching we flatten out nested structures so we can create different queries for matching like: { _id : 1, nested : { value : "conflux" } } { _id : 1, nested.value : { "conflux" } } This is useful when you want so search using a only partially filled nested document. String matching can be configured to wrap strings with $regex which creates { firstname : { $regex : "^foo", $options: "i" } } when using StringMatchMode.STARTING along with the ignoreCaseOption. DBRefs and geo structures such as Point or GeoJsonPoint is converted to their according structure. Related tickets: DATACMNS-810. Original pull request: #341.
1 parent ece655f commit 693f5dd

23 files changed

+1730
-161
lines changed

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

+59-51
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.data.annotation.Id;
5252
import org.springframework.data.authentication.UserCredentials;
5353
import org.springframework.data.convert.EntityReader;
54+
import org.springframework.data.domain.Example;
5455
import org.springframework.data.geo.Distance;
5556
import org.springframework.data.geo.GeoResult;
5657
import org.springframework.data.geo.GeoResults;
@@ -340,8 +341,8 @@ public CloseableIterator<T> doInCollection(DBCollection collection) throws Mongo
340341
DBCursor cursor = collection.find(mappedQuery, mappedFields);
341342
QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType);
342343

343-
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType,
344-
collection.getName());
344+
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType, collection
345+
.getName());
345346

346347
return new CloseableIterableCursorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
347348
}
@@ -374,8 +375,8 @@ public CommandResult doInDB(DB db) throws MongoException, DataAccessException {
374375
*/
375376
@Deprecated
376377
public CommandResult executeCommand(final DBObject command, final int options) {
377-
return executeCommand(command,
378-
(options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary());
378+
return executeCommand(command, (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred()
379+
: ReadPreference.primary());
379380
}
380381

381382
/*
@@ -421,8 +422,7 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan
421422
* @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply
422423
* limits, skips and so on).
423424
*/
424-
protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch,
425-
CursorPreparer preparer) {
425+
protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) {
426426

427427
Assert.notNull(query);
428428

@@ -637,6 +637,17 @@ public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
637637
return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass);
638638
}
639639

640+
public <S extends T, T> List<T> findByExample(S sample) {
641+
return findByExample(new Example<S>(sample));
642+
}
643+
644+
@SuppressWarnings("unchecked")
645+
public <S extends T, T> List<T> findByExample(Example<S> sample) {
646+
647+
Assert.notNull(sample, "Sample object must not be null!");
648+
return (List<T>) find(new Query(new Criteria().alike(sample)), sample.getSampleType());
649+
}
650+
640651
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass) {
641652
return geoNear(near, entityClass, determineCollectionName(entityClass));
642653
}
@@ -672,8 +683,8 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String co
672683
List<Object> results = (List<Object>) commandResult.get("results");
673684
results = results == null ? Collections.emptyList() : results;
674685

675-
DbObjectCallback<GeoResult<T>> callback = new GeoNearResultDbObjectCallback<T>(
676-
new ReadDbObjectCallback<T>(mongoConverter, entityClass, collectionName), near.getMetric());
686+
DbObjectCallback<GeoResult<T>> callback = new GeoNearResultDbObjectCallback<T>(new ReadDbObjectCallback<T>(
687+
mongoConverter, entityClass, collectionName), near.getMetric());
677688
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
678689

679690
int index = 0;
@@ -749,9 +760,8 @@ public long count(final Query query, String collectionName) {
749760
public long count(Query query, Class<?> entityClass, String collectionName) {
750761

751762
Assert.hasText(collectionName);
752-
final DBObject dbObject = query == null ? null
753-
: queryMapper.getMappedObject(query.getQueryObject(),
754-
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
763+
final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(),
764+
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
755765

756766
return execute(collectionName, new CollectionCallback<Long>() {
757767
public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException {
@@ -1030,8 +1040,8 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat
10301040
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName,
10311041
entityClass, dbDoc, null);
10321042
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
1033-
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc)
1034-
: collection.insert(dbDoc, writeConcernToUse);
1043+
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) : collection.insert(dbDoc,
1044+
writeConcernToUse);
10351045
handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT);
10361046
return dbDoc.get(ID_FIELD);
10371047
}
@@ -1052,8 +1062,8 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA
10521062
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null,
10531063
null, null);
10541064
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
1055-
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList)
1056-
: collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse);
1065+
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) : collection.insert(
1066+
dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse);
10571067
handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST);
10581068
return null;
10591069
}
@@ -1083,8 +1093,8 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat
10831093
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass,
10841094
dbDoc, null);
10851095
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
1086-
WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc)
1087-
: collection.save(dbDoc, writeConcernToUse);
1096+
WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) : collection.save(dbDoc,
1097+
writeConcernToUse);
10881098
handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE);
10891099
return dbDoc.get(ID_FIELD);
10901100
}
@@ -1137,10 +1147,10 @@ public WriteResult doInCollection(DBCollection collection) throws MongoException
11371147

11381148
increaseVersionForUpdateIfNecessary(entity, update);
11391149

1140-
DBObject queryObj = query == null ? new BasicDBObject()
1141-
: queryMapper.getMappedObject(query.getQueryObject(), entity);
1142-
DBObject updateObj = update == null ? new BasicDBObject()
1143-
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
1150+
DBObject queryObj = query == null ? new BasicDBObject() : queryMapper.getMappedObject(query.getQueryObject(),
1151+
entity);
1152+
DBObject updateObj = update == null ? new BasicDBObject() : updateMapper.getMappedObject(
1153+
update.getUpdateObject(), entity);
11441154

11451155
if (LOGGER.isDebugEnabled()) {
11461156
LOGGER.debug("Calling update using query: {} and update: {} in collection: {}",
@@ -1281,9 +1291,9 @@ private void assertUpdateableIdIfNotSet(Object entity) {
12811291
Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty);
12821292

12831293
if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) {
1284-
throw new InvalidDataAccessApiUsageException(
1285-
String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(),
1286-
entity.getClass().getName()));
1294+
throw new InvalidDataAccessApiUsageException(String.format(
1295+
"Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), entity.getClass()
1296+
.getName()));
12871297
}
12881298
}
12891299

@@ -1322,12 +1332,12 @@ public WriteResult doInCollection(DBCollection collection) throws MongoException
13221332
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
13231333

13241334
if (LOGGER.isDebugEnabled()) {
1325-
LOGGER.debug("Remove using query: {} in collection: {}.",
1326-
new Object[] { serializeToJsonSafely(dboq), collection.getName() });
1335+
LOGGER.debug("Remove using query: {} in collection: {}.", new Object[] { serializeToJsonSafely(dboq),
1336+
collection.getName() });
13271337
}
13281338

1329-
WriteResult wr = writeConcernToUse == null ? collection.remove(dboq)
1330-
: collection.remove(dboq, writeConcernToUse);
1339+
WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) : collection.remove(dboq,
1340+
writeConcernToUse);
13311341

13321342
handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE);
13331343

@@ -1343,8 +1353,8 @@ public <T> List<T> findAll(Class<T> entityClass) {
13431353
}
13441354

13451355
public <T> List<T> findAll(Class<T> entityClass, String collectionName) {
1346-
return executeFindMultiInternal(new FindCallback(null), null,
1347-
new ReadDbObjectCallback<T>(mongoConverter, entityClass, collectionName), collectionName);
1356+
return executeFindMultiInternal(new FindCallback(null), null, new ReadDbObjectCallback<T>(mongoConverter,
1357+
entityClass, collectionName), collectionName);
13481358
}
13491359

13501360
public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,
@@ -1360,8 +1370,8 @@ public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapF
13601370

13611371
public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
13621372
String reduceFunction, Class<T> entityClass) {
1363-
return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(),
1364-
entityClass);
1373+
return mapReduce(query, inputCollectionName, mapFunction, reduceFunction,
1374+
new MapReduceOptions().outputTypeInline(), entityClass);
13651375
}
13661376

13671377
public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
@@ -1372,9 +1382,8 @@ public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName
13721382
DBCollection inputCollection = getCollection(inputCollectionName);
13731383

13741384
MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc,
1375-
mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(),
1376-
query == null || query.getQueryObject() == null ? null
1377-
: queryMapper.getMappedObject(query.getQueryObject(), null));
1385+
mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), query == null
1386+
|| query.getQueryObject() == null ? null : queryMapper.getMappedObject(query.getQueryObject(), null));
13781387

13791388
copyMapReduceOptionsToCommand(query, mapReduceOptions, command);
13801389

@@ -1710,8 +1719,8 @@ protected <T> T doFindOne(String collectionName, DBObject query, DBObject fields
17101719
mappedFields, entityClass, collectionName);
17111720
}
17121721

1713-
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
1714-
new ReadDbObjectCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
1722+
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), new ReadDbObjectCallback<T>(
1723+
this.mongoConverter, entityClass, collectionName), collectionName);
17151724
}
17161725

17171726
/**
@@ -1725,8 +1734,8 @@ protected <T> T doFindOne(String collectionName, DBObject query, DBObject fields
17251734
* @return the List of converted objects.
17261735
*/
17271736
protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> entityClass) {
1728-
return doFind(collectionName, query, fields, entityClass, null,
1729-
new ReadDbObjectCallback<T>(this.mongoConverter, entityClass, collectionName));
1737+
return doFind(collectionName, query, fields, entityClass, null, new ReadDbObjectCallback<T>(this.mongoConverter,
1738+
entityClass, collectionName));
17301739
}
17311740

17321741
/**
@@ -1744,8 +1753,8 @@ protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fie
17441753
*/
17451754
protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> entityClass,
17461755
CursorPreparer preparer) {
1747-
return doFind(collectionName, query, fields, entityClass, preparer,
1748-
new ReadDbObjectCallback<T>(mongoConverter, entityClass, collectionName));
1756+
return doFind(collectionName, query, fields, entityClass, preparer, new ReadDbObjectCallback<T>(mongoConverter,
1757+
entityClass, collectionName));
17491758
}
17501759

17511760
protected <S, T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<S> entityClass,
@@ -1898,8 +1907,8 @@ private <T> T executeFindOneInternal(CollectionCallback<DBObject> collectionCall
18981907
DbObjectCallback<T> objectCallback, String collectionName) {
18991908

19001909
try {
1901-
T result = objectCallback
1902-
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)));
1910+
T result = objectCallback.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(),
1911+
collectionName)));
19031912
return result;
19041913
} catch (RuntimeException e) {
19051914
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
@@ -1924,8 +1933,8 @@ private <T> T executeFindOneInternal(CollectionCallback<DBObject> collectionCall
19241933
* @param collectionName the collection to be queried
19251934
* @return
19261935
*/
1927-
private <T> List<T> executeFindMultiInternal(CollectionCallback<DBCursor> collectionCallback, CursorPreparer preparer,
1928-
DbObjectCallback<T> objectCallback, String collectionName) {
1936+
private <T> List<T> executeFindMultiInternal(CollectionCallback<DBCursor> collectionCallback,
1937+
CursorPreparer preparer, DbObjectCallback<T> objectCallback, String collectionName) {
19291938

19301939
try {
19311940

@@ -2015,8 +2024,8 @@ String determineCollectionName(Class<?> entityClass) {
20152024

20162025
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
20172026
if (entity == null) {
2018-
throw new InvalidDataAccessApiUsageException(
2019-
"No Persistent Entity information found for the class " + entityClass.getName());
2027+
throw new InvalidDataAccessApiUsageException("No Persistent Entity information found for the class "
2028+
+ entityClass.getName());
20202029
}
20212030
return entity.getCollection();
20222031
}
@@ -2080,8 +2089,8 @@ private void handleCommandError(CommandResult result, DBObject source) {
20802089
String error = result.getErrorMessage();
20812090
error = error == null ? "NO MESSAGE" : error;
20822091

2083-
throw new InvalidDataAccessApiUsageException(
2084-
"Command execution failed: Error [" + error + "], Command = " + source, ex);
2092+
throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = "
2093+
+ source, ex);
20852094
}
20862095
}
20872096

@@ -2277,8 +2286,7 @@ public T doWith(DBObject object) {
22772286

22782287
class UnwrapAndReadDbObjectCallback<T> extends ReadDbObjectCallback<T> {
22792288

2280-
public UnwrapAndReadDbObjectCallback(EntityReader<? super T, DBObject> reader, Class<T> type,
2281-
String collectionName) {
2289+
public UnwrapAndReadDbObjectCallback(EntityReader<? super T, DBObject> reader, Class<T> type, String collectionName) {
22822290
super(reader, type, collectionName);
22832291
}
22842292

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

+4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ public Point convert(DBObject source) {
117117

118118
Assert.isTrue(source.keySet().size() == 2, "Source must contain 2 elements");
119119

120+
if (source.containsField("type")) {
121+
return DbObjectToGeoJsonPointConverter.INSTANCE.convert(source);
122+
}
123+
120124
return new Point((Double) source.get("x"), (Double) source.get("y"));
121125
}
122126
}

0 commit comments

Comments
 (0)