From 5a78d99af0fc4dcab135f82f9867516db7576155 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 23 Feb 2016 17:15:50 +0100 Subject: [PATCH] DATAMONGO-1245 - Initial documentation for Query by Example. Adopt changes from query by example API refactoring. Related tickets: DATACMNS-810. Original pull request: #341. --- .../data/mongodb/core/MongoTemplate.java | 114 +++++++------- .../core/convert/MongoExampleMapper.java | 110 ++++++++------ .../mongodb/core/convert/QueryMapper.java | 8 +- .../data/mongodb/core/query/Criteria.java | 2 +- .../mongodb/core/query/MongoRegexCreator.java | 7 +- .../core/query/SerializationUtils.java | 4 +- .../mongodb/repository/MongoRepository.java | 44 ++---- .../query/ConvertingParameterAccessor.java | 6 - .../query/MongoParameterAccessor.java | 9 -- .../repository/query/MongoParameters.java | 24 +-- .../MongoParametersParameterAccessor.java | 13 +- .../repository/query/MongoQueryCreator.java | 23 +-- .../support/SimpleMongoRepository.java | 67 +++++++-- .../core/SerializationUtilsUnitTests.java | 38 +++-- .../convert/MongoExampleMapperUnitTests.java | 142 ++++++++++++------ .../core/temp/QueryByExampleTests.java | 29 +++- ...tractPersonRepositoryIntegrationTests.java | 65 ++++---- .../data/mongodb/repository/Contact.java | 3 +- .../ContactRepositoryIntegrationTests.java | 54 ++++++- .../mongodb/repository/PersonRepository.java | 7 +- .../query/StubParameterAccessor.java | 10 -- .../support/SimpleMongoRepositoryTests.java | 83 ++++++++-- src/main/asciidoc/index.adoc | 2 +- src/main/asciidoc/reference/mongodb.adoc | 4 + .../asciidoc/reference/query-by-example.adoc | 71 +++++++++ 25 files changed, 589 insertions(+), 350 deletions(-) create mode 100644 src/main/asciidoc/reference/query-by-example.adoc diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 10772f632..ec56a16f0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2015 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. @@ -51,7 +51,6 @@ import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.annotation.Id; import org.springframework.data.authentication.UserCredentials; import org.springframework.data.convert.EntityReader; -import org.springframework.data.domain.Example; import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; @@ -139,7 +138,6 @@ import com.mongodb.util.JSONParseException; * @author Chuong Ngo * @author Christoph Strobl * @author Doménique Tilleuil - * @author Mark Paluch */ @SuppressWarnings("deprecation") public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -341,8 +339,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { DBCursor cursor = collection.find(mappedQuery, mappedFields); QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType); - ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, collection - .getName()); + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, + collection.getName()); return new CloseableIterableCursorAdapter(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback); } @@ -375,8 +373,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { */ @Deprecated public CommandResult executeCommand(final DBObject command, final int options) { - return executeCommand(command, (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() - : ReadPreference.primary()); + return executeCommand(command, + (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary()); } /* @@ -422,7 +420,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply * limits, skips and so on). */ - protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) { + protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, + CursorPreparer preparer) { Assert.notNull(query); @@ -637,15 +636,23 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findByExample(java.lang.Object) + */ public List findByExample(S sample) { - return findByExample(new Example(sample)); + return findByExample(Example.of(sample)); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findByExample(org.springframework.data.domain.Example) + */ @SuppressWarnings("unchecked") public List findByExample(Example sample) { Assert.notNull(sample, "Sample object must not be null!"); - return (List) find(new Query(new Criteria().alike(sample)), sample.getSampleType()); + return (List) find(new Query(new Criteria().alike(sample)), sample.getResultType()); } public GeoResults geoNear(NearQuery near, Class entityClass) { @@ -683,8 +690,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { List results = (List) commandResult.get("results"); results = results == null ? Collections.emptyList() : results; - DbObjectCallback> callback = new GeoNearResultDbObjectCallback(new ReadDbObjectCallback( - mongoConverter, entityClass, collectionName), near.getMetric()); + DbObjectCallback> callback = new GeoNearResultDbObjectCallback( + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), near.getMetric()); List> result = new ArrayList>(results.size()); int index = 0; @@ -760,8 +767,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { public long count(Query query, Class entityClass, String collectionName) { Assert.hasText(collectionName); - final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(), - entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); + final DBObject dbObject = query == null ? null + : queryMapper.getMappedObject(query.getQueryObject(), + entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); return execute(collectionName, new CollectionCallback() { public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -1040,8 +1048,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass, dbDoc, null); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) : collection.insert(dbDoc, - writeConcernToUse); + WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) + : collection.insert(dbDoc, writeConcernToUse); handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT); return dbDoc.get(ID_FIELD); } @@ -1062,8 +1070,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, null, null); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) : collection.insert( - dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); + WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) + : collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST); return null; } @@ -1093,8 +1101,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, dbDoc, null); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) : collection.save(dbDoc, - writeConcernToUse); + WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) + : collection.save(dbDoc, writeConcernToUse); handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE); return dbDoc.get(ID_FIELD); } @@ -1147,10 +1155,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { increaseVersionForUpdateIfNecessary(entity, update); - DBObject queryObj = query == null ? new BasicDBObject() : queryMapper.getMappedObject(query.getQueryObject(), - entity); - DBObject updateObj = update == null ? new BasicDBObject() : updateMapper.getMappedObject( - update.getUpdateObject(), entity); + DBObject queryObj = query == null ? new BasicDBObject() + : queryMapper.getMappedObject(query.getQueryObject(), entity); + DBObject updateObj = update == null ? new BasicDBObject() + : updateMapper.getMappedObject(update.getUpdateObject(), entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", @@ -1291,9 +1299,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty); if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) { - throw new InvalidDataAccessApiUsageException(String.format( - "Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), entity.getClass() - .getName())); + throw new InvalidDataAccessApiUsageException( + String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), + entity.getClass().getName())); } } @@ -1332,12 +1340,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Remove using query: {} in collection: {}.", new Object[] { serializeToJsonSafely(dboq), - collection.getName() }); + LOGGER.debug("Remove using query: {} in collection: {}.", + new Object[] { serializeToJsonSafely(dboq), collection.getName() }); } - WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) : collection.remove(dboq, - writeConcernToUse); + WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) + : collection.remove(dboq, writeConcernToUse); handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE); @@ -1353,8 +1361,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { } public List findAll(Class entityClass, String collectionName) { - return executeFindMultiInternal(new FindCallback(null), null, new ReadDbObjectCallback(mongoConverter, - entityClass, collectionName), collectionName); + return executeFindMultiInternal(new FindCallback(null), null, + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), collectionName); } public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, @@ -1370,8 +1378,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, Class entityClass) { - return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, - new MapReduceOptions().outputTypeInline(), entityClass); + return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), + entityClass); } public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, @@ -1382,8 +1390,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { DBCollection inputCollection = getCollection(inputCollectionName); MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc, - mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), query == null - || query.getQueryObject() == null ? null : queryMapper.getMappedObject(query.getQueryObject(), null)); + mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), + query == null || query.getQueryObject() == null ? null + : queryMapper.getMappedObject(query.getQueryObject(), null)); copyMapReduceOptionsToCommand(query, mapReduceOptions, command); @@ -1719,8 +1728,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { mappedFields, entityClass, collectionName); } - return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), new ReadDbObjectCallback( - this.mongoConverter, entityClass, collectionName), collectionName); + return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), + new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName), collectionName); } /** @@ -1734,8 +1743,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * @return the List of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { - return doFind(collectionName, query, fields, entityClass, null, new ReadDbObjectCallback(this.mongoConverter, - entityClass, collectionName)); + return doFind(collectionName, query, fields, entityClass, null, + new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName)); } /** @@ -1753,8 +1762,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, CursorPreparer preparer) { - return doFind(collectionName, query, fields, entityClass, preparer, new ReadDbObjectCallback(mongoConverter, - entityClass, collectionName)); + return doFind(collectionName, query, fields, entityClass, preparer, + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); } protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, @@ -1907,8 +1916,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { DbObjectCallback objectCallback, String collectionName) { try { - T result = objectCallback.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), - collectionName))); + T result = objectCallback + .doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName))); return result; } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); @@ -1933,8 +1942,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * @param collectionName the collection to be queried * @return */ - private List executeFindMultiInternal(CollectionCallback collectionCallback, - CursorPreparer preparer, DbObjectCallback objectCallback, String collectionName) { + private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, + DbObjectCallback objectCallback, String collectionName) { try { @@ -2024,8 +2033,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); if (entity == null) { - throw new InvalidDataAccessApiUsageException("No Persistent Entity information found for the class " - + entityClass.getName()); + throw new InvalidDataAccessApiUsageException( + "No Persistent Entity information found for the class " + entityClass.getName()); } return entity.getCollection(); } @@ -2089,8 +2098,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { String error = result.getErrorMessage(); error = error == null ? "NO MESSAGE" : error; - throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = " - + source, ex); + throw new InvalidDataAccessApiUsageException( + "Command execution failed: Error [" + error + "], Command = " + source, ex); } } @@ -2286,7 +2295,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { class UnwrapAndReadDbObjectCallback extends ReadDbObjectCallback { - public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { + public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, + String collectionName) { super(reader, type, collectionName); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java index 3332e4f56..7463921fe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -17,22 +17,30 @@ package org.springframework.data.mongodb.core.convert; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.Stack; import java.util.regex.Pattern; +import org.bson.BasicBSONObject; import org.springframework.data.domain.Example; -import org.springframework.data.domain.Example.NullHandler; -import org.springframework.data.domain.Example.StringMatcher; -import org.springframework.data.domain.PropertySpecifier; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.data.domain.ExampleSpec.NullHandler; +import org.springframework.data.domain.ExampleSpec.PropertyValueTransformer; +import org.springframework.data.domain.ExampleSpec.StringMatcher; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.query.MongoRegexCreator; import org.springframework.data.mongodb.core.query.SerializationUtils; +import org.springframework.data.repository.core.support.ExampleSpecAccessor; +import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -41,35 +49,43 @@ import com.mongodb.DBObject; /** * @author Christoph Strobl + * @author Mark Paluch * @since 1.8 */ public class MongoExampleMapper { private final MappingContext, MongoPersistentProperty> mappingContext; private final MongoConverter converter; + private final Map stringMatcherPartMapping = new HashMap(); public MongoExampleMapper(MongoConverter converter) { this.converter = converter; this.mappingContext = converter.getMappingContext(); + + stringMatcherPartMapping.put(StringMatcher.EXACT, Type.SIMPLE_PROPERTY); + stringMatcherPartMapping.put(StringMatcher.CONTAINING, Type.CONTAINING); + stringMatcherPartMapping.put(StringMatcher.STARTING, Type.STARTING_WITH); + stringMatcherPartMapping.put(StringMatcher.ENDING, Type.ENDING_WITH); + stringMatcherPartMapping.put(StringMatcher.REGEX, Type.REGEX); } /** * Returns the given {@link Example} as {@link DBObject} holding matching values extracted from * {@link Example#getProbe()}. - * + * * @param example * @return * @since 1.8 */ public DBObject getMappedExample(Example example) { - return getMappedExample(example, mappingContext.getPersistentEntity(example.getSampleType())); + return getMappedExample(example, mappingContext.getPersistentEntity(example.getProbeType())); } /** * Returns the given {@link Example} as {@link DBObject} holding matching values extracted from * {@link Example#getProbe()}. - * + * * @param example * @param entity * @return @@ -77,21 +93,27 @@ public class MongoExampleMapper { */ public DBObject getMappedExample(Example example, MongoPersistentEntity entity) { - DBObject reference = (DBObject) converter.convertToMongoType(example.getSampleObject()); + DBObject reference = (DBObject) converter.convertToMongoType(example.getProbe()); - if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getSampleObject()).getIdentifier() == null) { + if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getProbe()).getIdentifier() == null) { reference.removeField(entity.getIdProperty().getFieldName()); } - applyPropertySpecs("", reference, example); + ExampleSpecAccessor exampleSpecAccessor = new ExampleSpecAccessor(example.getExampleSpec()); - return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, example.getNullHandler()) ? reference : new BasicDBObject( - SerializationUtils.flatMap(reference)); + applyPropertySpecs("", reference, example.getProbeType(), exampleSpecAccessor); + + if (exampleSpecAccessor.isTyped()) { + this.converter.getTypeMapper().writeTypeRestrictions(reference, (Set) Collections.singleton(example.getResultType())); + } + + return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, exampleSpecAccessor.getNullHandler()) ? reference + : new BasicDBObject(SerializationUtils.flattenMap(reference)); } - private String getMappedPropertyPath(String path, Example example) { + private String getMappedPropertyPath(String path, Class probeType) { - MongoPersistentEntity entity = mappingContext.getPersistentEntity(example.getSampleType()); + MongoPersistentEntity entity = mappingContext.getPersistentEntity(probeType); Iterator parts = Arrays.asList(path.split("\\.")).iterator(); @@ -136,7 +158,8 @@ public class MongoExampleMapper { } - private void applyPropertySpecs(String path, DBObject source, Example example) { + private void applyPropertySpecs(String path, DBObject source, Class probeType, + ExampleSpecAccessor exampleSpecAccessor) { if (!(source instanceof BasicDBObject)) { return; @@ -147,47 +170,37 @@ public class MongoExampleMapper { while (iter.hasNext()) { Map.Entry entry = iter.next(); - - if (entry.getKey().equals("_id") && entry.getValue() == null) { - iter.remove(); - continue; - } - String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey(); - - String mappedPropertyPath = getMappedPropertyPath(propertyPath, example); - if (example.isIgnoredPath(propertyPath) || example.isIgnoredPath(mappedPropertyPath)) { + String mappedPropertyPath = getMappedPropertyPath(propertyPath, probeType); + + if(isEmptyIdProperty(entry)) { + iter.remove(); + continue; + } + + if (exampleSpecAccessor.isIgnoredPath(propertyPath) || exampleSpecAccessor.isIgnoredPath(mappedPropertyPath)) { iter.remove(); continue; } - PropertySpecifier specifier = null; - StringMatcher stringMatcher = example.getDefaultStringMatcher(); + StringMatcher stringMatcher = exampleSpecAccessor.getDefaultStringMatcher(); Object value = entry.getValue(); - boolean ignoreCase = example.isIngnoreCaseEnabled(); + boolean ignoreCase = exampleSpecAccessor.isIgnoreCaseEnabled(); - if (example.hasPropertySpecifiers()) { + if (exampleSpecAccessor.hasPropertySpecifiers()) { - mappedPropertyPath = example.hasPropertySpecifier(propertyPath) ? propertyPath : getMappedPropertyPath( - propertyPath, example); + mappedPropertyPath = exampleSpecAccessor.hasPropertySpecifier(propertyPath) ? propertyPath + : getMappedPropertyPath(propertyPath, probeType); - specifier = example.getPropertySpecifier(mappedPropertyPath); - - if (specifier != null) { - if (specifier.hasStringMatcher()) { - stringMatcher = specifier.getStringMatcher(); - } - if (specifier.getIgnoreCase() != null) { - ignoreCase = specifier.getIgnoreCase(); - } - - } + stringMatcher = exampleSpecAccessor.getStringMatcherForPath(mappedPropertyPath); + ignoreCase = exampleSpecAccessor.isIgnoreCaseForPath(mappedPropertyPath); } // TODO: should a PropertySpecifier outrule the later on string matching? - if (specifier != null) { + if (exampleSpecAccessor.hasPropertySpecifier(mappedPropertyPath)) { - value = specifier.transformValue(value); + PropertyValueTransformer valueTransformer = exampleSpecAccessor.getValueTransformerForPath(mappedPropertyPath); + value = valueTransformer.convert(value); if (value == null) { iter.remove(); continue; @@ -199,11 +212,15 @@ public class MongoExampleMapper { if (entry.getValue() instanceof String) { applyStringMatcher(entry, stringMatcher, ignoreCase); } else if (entry.getValue() instanceof BasicDBObject) { - applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), example); + applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), probeType, exampleSpecAccessor); } } } + private boolean isEmptyIdProperty(Entry entry) { + return entry.getKey().equals("_id") && entry.getValue() == null; + } + private void applyStringMatcher(Map.Entry entry, StringMatcher stringMatcher, boolean ignoreCase) { BasicDBObject dbo = new BasicDBObject(); @@ -216,8 +233,8 @@ public class MongoExampleMapper { } } else { - String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(), - stringMatcher.getPartType()); + Type type = stringMatcherPartMapping.get(stringMatcher); + String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(), type); dbo.put("$regex", expression); entry.setValue(dbo); } @@ -226,4 +243,5 @@ public class MongoExampleMapper { dbo.put("$options", "i"); } } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index 34177102b..e6b9587c2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -261,8 +261,8 @@ public class QueryMapper { boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists(); Object value = keyword.getValue(); - Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) : getMappedValue( - property.with(keyword.getKey()), value); + Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) + : getMappedValue(property.with(keyword.getKey()), value); return new BasicDBObject(keyword.key, convertedValue); } @@ -484,8 +484,8 @@ public class QueryMapper { } try { - return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService - .convert(id, ObjectId.class) : delegateConvertToMongoType(id, null); + return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService.convert(id, ObjectId.class) + : delegateConvertToMongoType(id, null); } catch (ConversionException o_O) { return delegateConvertToMongoType(id, null); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index 1bbf306a8..0836cdae9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -98,7 +98,7 @@ public class Criteria implements CriteriaDefinition { * @since 1.8 */ public static Criteria byExample(Object example) { - return byExample(new Example(example)); + return byExample(Example.of(example)); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java index 4f7b4f042..a1d85dff9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -22,6 +22,7 @@ import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl + * @author Mark Paluch * @since 1.8 */ public enum MongoRegexCreator { @@ -67,6 +68,10 @@ public enum MongoRegexCreator { private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Type type) { + if (ObjectUtils.nullSafeEquals(Type.REGEX, type)) { + return source; + } + if (!ObjectUtils.nullSafeEquals(Type.LIKE, type)) { return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java index efc904b03..4064b239b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-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. @@ -63,7 +63,7 @@ public abstract class SerializationUtils { * @return {@link Collections#emptyMap()} when source is {@literal null} * @since 1.8 */ - public static Map flatMap(DBObject source) { + public static Map flattenMap(DBObject source) { if (source == null) { return Collections.emptyMap(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java index 35ec5f376..59b013d37 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java @@ -19,21 +19,22 @@ import java.io.Serializable; import java.util.List; import org.springframework.data.domain.Example; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; /** * Mongo specific {@link org.springframework.data.repository.Repository} interface. - * + * * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ @NoRepositoryBean -public interface MongoRepository extends PagingAndSortingRepository { +public interface MongoRepository + extends PagingAndSortingRepository, QueryByExampleExecutor { /* * (non-Javadoc) @@ -57,7 +58,7 @@ public interface MongoRepository extends PagingAndSo * Inserts the given a given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use * the returned instance for further operations as the save operation might have changed the entity instance * completely. Prefer using {@link #save(Object)} instead to avoid the usage of store-specific API. - * + * * @param entity must not be {@literal null}. * @return the saved entity * @since 1.7 @@ -68,40 +69,21 @@ public interface MongoRepository extends PagingAndSo * Inserts the given entities. Assumes the given entities to have not been persisted yet and thus will optimize the * insert over a call to {@link #save(Iterable)}. Prefer using {@link #save(Iterable)} to avoid the usage of store * specific API. - * + * * @param entities must not be {@literal null}. * @return the saved entities * @since 1.7 */ List insert(Iterable entities); - /** - * Returns all instances of the type specified by the given {@link Example}. - * - * @param example must not be {@literal null}. - * @return - * @since 1.8 + /* (non-Javadoc) + * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example) */ - List findAllByExample(Example example); + List findAll(Example example); - /** - * Returns all instances of the type specified by the given {@link Example}. - * - * @param example must not be {@literal null}. - * @param sort can be {@literal null}. - * @return all entities sorted by the given options - * @since 1.8 + /* (non-Javadoc) + * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort) */ - List findAllByExample(Example example, Sort sort); + List findAll(Example example, Sort sort); - /** - * Returns a {@link Page} of entities meeting the paging restriction specified by the given {@link Example} limited to - * criteria provided in the {@code Pageable} object. - * - * @param example must not be {@literal null}. - * @param pageable can be {@literal null}. - * @return a {@link Page} of entities - * @since 1.8 - */ - Page findAllByExample(Example example, Pageable pageable); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index d6e8dfbd6..15d0cd25e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.springframework.data.domain.Example; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Range; import org.springframework.data.domain.Sort; @@ -133,11 +132,6 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { return delegate.getFullText(); } - @Override - public Example getSampleObject() { - return delegate.getSampleObject(); - } - /** * Converts the given value with the underlying {@link MongoWriter}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java index a64793e64..6c67778e3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java @@ -15,7 +15,6 @@ */ package org.springframework.data.mongodb.repository.query; -import org.springframework.data.domain.Example; import org.springframework.data.domain.Range; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; @@ -61,12 +60,4 @@ public interface MongoParameterAccessor extends ParameterAccessor { * @since 1.8 */ Object[] getValues(); - - /** - * Get the sample for query by example - * - * @return - * @since 1.8 - */ - Example getSampleObject(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java index 317301145..1e36047b9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.List; import org.springframework.core.MethodParameter; -import org.springframework.data.domain.Example; import org.springframework.data.domain.Range; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; @@ -43,7 +42,6 @@ public class MongoParameters extends Parameters private final int rangeIndex; private final int maxDistanceIndex; private final Integer fullTextIndex; - private final int sampleObjectIndex; private Integer nearIndex; @@ -71,12 +69,10 @@ public class MongoParameters extends Parameters } else if (this.nearIndex == null) { this.nearIndex = -1; } - - this.sampleObjectIndex = parameterTypes.indexOf(Example.class); } private MongoParameters(List parameters, int maxDistanceIndex, Integer nearIndex, - Integer fullTextIndex, int rangeIndex, int sampleObjectIndex) { + Integer fullTextIndex, int rangeIndex) { super(parameters); @@ -84,7 +80,6 @@ public class MongoParameters extends Parameters this.fullTextIndex = fullTextIndex; this.maxDistanceIndex = maxDistanceIndex; this.rangeIndex = rangeIndex; - this.sampleObjectIndex = sampleObjectIndex; } private final int getNearIndex(List> parameterTypes) { @@ -187,22 +182,13 @@ public class MongoParameters extends Parameters return rangeIndex; } - /** - * @return - * @since 1.8 - */ - public int getSampleObjectParameterIndex() { - return sampleObjectIndex; - } - /* * (non-Javadoc) * @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List) */ @Override protected MongoParameters createFrom(List parameters) { - return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex, - this.sampleObjectIndex); + return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex); } private int getTypeIndex(List> parameterTypes, Class type, Class componentType) { @@ -254,7 +240,7 @@ public class MongoParameters extends Parameters @Override public boolean isSpecialParameter() { return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter() - || TextCriteria.class.isAssignableFrom(getType()) || isExample(); + || TextCriteria.class.isAssignableFrom(getType()); } private boolean isNearParameter() { @@ -274,10 +260,6 @@ public class MongoParameters extends Parameters return parameter.getParameterAnnotation(Near.class) != null; } - private boolean isExample() { - return Example.class.isAssignableFrom(getType()); - } - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java index 9cbe003e3..7f8ac79b4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java @@ -18,7 +18,6 @@ package org.springframework.data.mongodb.repository.query; import java.util.Arrays; import java.util.List; -import org.springframework.data.domain.Example; import org.springframework.data.domain.Range; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; @@ -126,15 +125,9 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso return ((TextCriteria) fullText); } - throw new IllegalArgumentException(String.format( - "Expected full text parameter to be one of String, Term or TextCriteria but found %s.", - ClassUtils.getShortName(fullText.getClass()))); - } - - public Example getSampleObject() { - - int index = method.getParameters().getSampleObjectParameterIndex(); - return index >= 0 ? (Example) getValue(index) : null; + throw new IllegalArgumentException( + String.format("Expected full text parameter to be one of String, Term or TextCriteria but found %s.", + ClassUtils.getShortName(fullText.getClass()))); } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 2ae0c30f3..083b778d7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -151,20 +151,7 @@ class MongoQueryCreator extends AbstractQueryCreator { @Override protected Query complete(Criteria criteria, Sort sort) { - Criteria toUse = null; - if (accessor.getSampleObject() != null) { - toUse = new Criteria().alike(accessor.getSampleObject()); - } - - if (criteria != null) { - if (toUse == null) { - toUse = criteria; - } else { - toUse.andOperator(criteria); - } - } - - Query query = (toUse == null ? new Query() : new Query(toUse)).with(sort); + Query query = (criteria == null ? new Query() : new Query(criteria)).with(sort); if (LOG.isDebugEnabled()) { LOG.debug("Created query " + query); @@ -298,8 +285,8 @@ class MongoQueryCreator extends AbstractQueryCreator { case ALWAYS: if (path.getType() != String.class) { - throw new IllegalArgumentException(String.format("Part %s must be of type String but was %s", path, - path.getType())); + throw new IllegalArgumentException( + String.format("Part %s must be of type String but was %s", path, path.getType())); } // fall-through @@ -385,8 +372,8 @@ class MongoQueryCreator extends AbstractQueryCreator { return (T) parameter; } - throw new IllegalArgumentException(String.format("Expected parameter type of %s but got %s!", type, - parameter.getClass())); + throw new IllegalArgumentException( + String.format("Expected parameter type of %s but got %s!", type, parameter.getClass())); } private Object[] nextAsArray(Iterator iterator) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java index a87c184f5..fcca93e66 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 the original author or authors. + * Copyright 2010-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. @@ -30,6 +30,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.domain.TypedExampleSpec; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; @@ -44,6 +45,7 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class SimpleMongoRepository implements MongoRepository { @@ -54,7 +56,7 @@ public class SimpleMongoRepository implements MongoR * Creates a new {@link SimpleMongoRepository} for the given {@link MongoEntityInformation} and {@link MongoTemplate}. * * @param metadata must not be {@literal null}. - * @param template must not be {@literal null}. + * @param mongoOperations must not be {@literal null}. */ public SimpleMongoRepository(MongoEntityInformation metadata, MongoOperations mongoOperations) { @@ -265,18 +267,18 @@ public class SimpleMongoRepository implements MongoR * @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable) */ @Override - public Page findAllByExample(Example example, Pageable pageable) { + public Page findAll(Example example, Pageable pageable) { Assert.notNull(example, "Sample must not be null!"); Query q = new Query(new Criteria().alike(example)).with(pageable); - long count = mongoOperations.count(q, entityInformation.getJavaType(), entityInformation.getCollectionName()); + long count = mongoOperations.count(q, getResultType(example), entityInformation.getCollectionName()); if (count == 0) { - return new PageImpl(Collections. emptyList()); + return new PageImpl(Collections. emptyList()); } - return new PageImpl(mongoOperations.find(q, entityInformation.getJavaType(), - entityInformation.getCollectionName()), pageable, count); + return new PageImpl(mongoOperations.find(q, getResultType(example), entityInformation.getCollectionName()), pageable, + count); } /* @@ -284,7 +286,7 @@ public class SimpleMongoRepository implements MongoR * @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Sort) */ @Override - public List findAllByExample(Example example, Sort sort) { + public List findAll(Example example, Sort sort) { Assert.notNull(example, "Sample must not be null!"); @@ -294,7 +296,7 @@ public class SimpleMongoRepository implements MongoR q.with(sort); } - return findAll(q); + return mongoOperations.find(q, getResultType(example), entityInformation.getCollectionName()); } /* @@ -302,13 +304,56 @@ public class SimpleMongoRepository implements MongoR * @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example) */ @Override - public List findAllByExample(Example example) { + public List findAll(Example example) { + return findAll(example, (Sort) null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example) + */ + @Override + public S findOne(Example example) { Assert.notNull(example, "Sample must not be null!"); Query q = new Query(new Criteria().alike(example)); + return mongoOperations.findOne(q, getResultType(example), entityInformation.getCollectionName()); + } - return findAll(q); + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryByExampleExecutor#count(org.springframework.data.domain.Example) + */ + @Override + public long count(Example example) { + + Assert.notNull(example, "Sample must not be null!"); + + Query q = new Query(new Criteria().alike(example)); + return mongoOperations.count(q, getResultType(example), entityInformation.getCollectionName()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryByExampleExecutor#exists(org.springframework.data.domain.Example) + */ + @Override + public boolean exists(Example example) { + + Assert.notNull(example, "Sample must not be null!"); + + Query q = new Query(new Criteria().alike(example)); + return mongoOperations.exists(q, getResultType(example), entityInformation.getCollectionName()); + } + + private Class getResultType(Example example) { + + if (example.getExampleSpec() instanceof TypedExampleSpec) { + return example.getResultType(); + } + + return (Class) entityInformation.getJavaType(); } private List findAll(Query query) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java index 231070d34..0b42b27c0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-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. @@ -23,8 +23,6 @@ import java.util.Arrays; import java.util.Map; import org.hamcrest.Matcher; -import org.hamcrest.collection.IsMapContaining; -import org.hamcrest.core.Is; import org.junit.Test; import org.springframework.data.mongodb.core.query.SerializationUtils; @@ -70,68 +68,68 @@ public class SerializationUtilsUnitTests { * @see DATAMONGO-1245 */ @Test - public void flatMapShouldFlatOutNestedStructureCorrectly() { + public void flattenMapShouldFlatOutNestedStructureCorrectly() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", "conflux")).get(); - assertThat(flatMap(dbo), IsMapContaining. hasEntry("_id", 1)); - assertThat(flatMap(dbo), IsMapContaining. hasEntry("nested.value", "conflux")); + assertThat(flattenMap(dbo), hasEntry("_id", (Object) 1)); + assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) "conflux")); } /** * @see DATAMONGO-1245 */ @Test - public void flatMapShouldFlatOutNestedStructureWithListCorrectly() { + public void flattenMapShouldFlatOutNestedStructureWithListCorrectly() { BasicDBList dbl = new BasicDBList(); dbl.addAll(Arrays.asList("nightwielder", "calamity")); DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", dbl)).get(); - assertThat(flatMap(dbo), IsMapContaining. hasEntry("_id", 1)); - assertThat(flatMap(dbo), IsMapContaining. hasEntry("nested.value", dbl)); + assertThat(flattenMap(dbo), hasEntry("_id", (Object) 1)); + assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) dbl)); } /** * @see DATAMONGO-1245 */ @Test - public void flatMapShouldLeaveKeywordsUntouched() { + public void flattenMapShouldLeaveKeywordsUntouched() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("$regex", "^conflux$")) .get(); - Map map = flatMap(dbo); + Map map = flattenMap(dbo); - assertThat(map, IsMapContaining. hasEntry("_id", 1)); + assertThat(map, hasEntry("_id", (Object) 1)); assertThat(map.get("nested"), notNullValue()); - assertThat(((Map) map.get("nested")).get("$regex"), Is. is("^conflux$")); + assertThat(((Map) map.get("nested")).get("$regex"), is((Object) "^conflux$")); } /** * @see DATAMONGO-1245 */ @Test - public void flatMapSouldAppendCommandsCorrectly() { + public void flattenMapShouldAppendCommandsCorrectly() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1) .add("nested", new BasicDBObjectBuilder().add("$regex", "^conflux$").add("$options", "i").get()).get(); - Map map = flatMap(dbo); + Map map = flattenMap(dbo); - assertThat(map, IsMapContaining. hasEntry("_id", 1)); + assertThat(map, hasEntry("_id", (Object) 1)); assertThat(map.get("nested"), notNullValue()); - assertThat(((Map) map.get("nested")).get("$regex"), Is. is("^conflux$")); - assertThat(((Map) map.get("nested")).get("$options"), Is. is("i")); + assertThat(((Map) map.get("nested")).get("$regex"), is((Object) "^conflux$")); + assertThat(((Map) map.get("nested")).get("$options"), is((Object) "i")); } /** * @see DATAMONGO-1245 */ @Test - public void flatMapShouldReturnEmptyMapWhenSourceIsNull() { - assertThat(flatMap(null).isEmpty(), is(true)); + public void flattenMapShouldReturnEmptyMapWhenSourceIsNull() { + assertThat(flattenMap(null).isEmpty(), is(true)); } static class Complex { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java index 956e6bc40..ba01bacaa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -18,7 +18,6 @@ package org.springframework.data.mongodb.core.convert; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.data.domain.Example.*; -import static org.springframework.data.domain.PropertySpecifier.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -34,6 +33,9 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.data.domain.ExampleSpec.GenericPropertyMatcher; +import org.springframework.data.domain.ExampleSpec.StringMatcher; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes; @@ -49,6 +51,7 @@ import com.mongodb.DBObject; /** * @author Christoph Strobl + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class MongoExampleMapperUnitTests { @@ -79,7 +82,7 @@ public class MongoExampleMapperUnitTests { FlatDocument probe = new FlatDocument(); probe.id = "steelheart"; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); assertThat(dbo, is(new BasicDBObjectBuilder().add("_id", "steelheart").get())); } @@ -95,11 +98,10 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.intValue = 100; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); - assertThat(dbo, - is(new BasicDBObjectBuilder().add("_id", "steelheart").add("stringValue", "firefight").add("intValue", 100) - .get())); + assertThat(dbo, is(new BasicDBObjectBuilder().add("_id", "steelheart").add("stringValue", "firefight") + .add("intValue", 100).get())); } /** @@ -112,7 +114,7 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.intValue = 100; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "firefight").add("intValue", 100).get())); } @@ -126,7 +128,7 @@ public class MongoExampleMapperUnitTests { FlatDocument probe = new FlatDocument(); probe.listOfString = Arrays.asList("Prof", "Tia", "David"); - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); assertThat(dbo, is(new BasicDBObjectBuilder().add("listOfString", Arrays.asList("Prof", "Tia", "David")).get())); } @@ -140,7 +142,7 @@ public class MongoExampleMapperUnitTests { FlatDocument probe = new FlatDocument(); probe.customNamedField = "Mitosis"; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "Mitosis").get())); } @@ -149,13 +151,30 @@ public class MongoExampleMapperUnitTests { * @see DATAMONGO-1245 */ @Test - public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenienMatchMode() { + public void typedExampleShouldContainTypeRestriction() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); probe.flatDoc.stringValue = "conflux"; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WrapperDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe, ExampleSpec.typed(WrapperDocument.class)), + context.getPersistentEntity(WrapperDocument.class)); + + assertThat(dbo, + isBsonObject().containing("_class", new BasicDBObject("$in", new String[] { probe.getClass().getName() }))); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() { + + WrapperDocument probe = new WrapperDocument(); + probe.flatDoc = new FlatDocument(); + probe.flatDoc.stringValue = "conflux"; + + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(WrapperDocument.class)); assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "conflux").get())); } @@ -164,13 +183,13 @@ public class MongoExampleMapperUnitTests { * @see DATAMONGO-1245 */ @Test - public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStriktMatchMode() { + public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); probe.flatDoc.stringValue = "conflux"; - Example example = newExampleOf(probe).includeNullValues().get(); + Example example = Example.of(probe, ExampleSpec.untyped().withIncludeNullValues()); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); @@ -187,13 +206,30 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.intValue = 100; - Example example = newExampleOf(probe).matchStringsStartingWith().get(); + Example example = Example.of(probe, ExampleSpec.untyped().withStringMatcher(StringMatcher.STARTING)); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); - assertThat(dbo, - is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "^firefight")) - .add("intValue", 100).get())); + assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "^firefight")) + .add("intValue", 100).get())); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() { + + FlatDocument probe = new FlatDocument(); + probe.stringValue = "fire.ight"; + probe.intValue = 100; + + Example example = Example.of(probe, ExampleSpec.untyped().withStringMatcherStarting()); + + DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); + + assertThat(dbo, is(new BasicDBObjectBuilder() + .add("stringValue", new BasicDBObject("$regex", "^" + Pattern.quote("fire.ight"))).add("intValue", 100).get())); } /** @@ -206,13 +242,30 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.intValue = 100; - Example example = newExampleOf(probe).matchStringsEndingWith().get(); + Example example = Example.of(probe, ExampleSpec.untyped().withStringMatcher(StringMatcher.ENDING)); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); - assertThat(dbo, - is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight$")) - .add("intValue", 100).get())); + assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight$")) + .add("intValue", 100).get())); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() { + + FlatDocument probe = new FlatDocument(); + probe.stringValue = "firefight"; + probe.customNamedField = "^(cat|dog).*shelter\\d?"; + + Example example = Example.of(probe, ExampleSpec.untyped().withStringMatcher(StringMatcher.REGEX)); + + DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); + + assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight")) + .add("custom_field_name", new BasicDBObject("$regex", "^(cat|dog).*shelter\\d?")).get())); } /** @@ -225,12 +278,12 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.intValue = 100; - Example example = newExampleOf(probe).matchStringsEndingWith().matchStringsWithIgnoreCase().get(); + Example example = Example.of(probe, + ExampleSpec.untyped().withStringMatcher(StringMatcher.ENDING).withIgnoreCase()); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); - assertThat( - dbo, + assertThat(dbo, is(new BasicDBObjectBuilder() .add("stringValue", new BasicDBObjectBuilder().add("$regex", "firefight$").add("$options", "i").get()) .add("intValue", 100).get())); @@ -246,12 +299,11 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.intValue = 100; - Example example = newExampleOf(probe).matchStringsWithIgnoreCase().get(); + Example example = Example.of(probe, ExampleSpec.untyped().withIgnoreCase()); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); - assertThat( - dbo, + assertThat(dbo, is(new BasicDBObjectBuilder() .add("stringValue", new BasicDBObjectBuilder().add("$regex", Pattern.quote("firefight")).add("$options", "i").get()) @@ -269,7 +321,7 @@ public class MongoExampleMapperUnitTests { probe.referenceDocument = new ReferenceDocument(); probe.referenceDocument.id = "200"; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(WithDBRef.class)); com.mongodb.DBRef reference = getTypedValue(dbo, "referenceDocument", com.mongodb.DBRef.class); assertThat(reference.getId(), Is. is("200")); @@ -285,7 +337,7 @@ public class MongoExampleMapperUnitTests { FlatDocument probe = new FlatDocument(); probe.stringValue = "steelheart"; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "steelheart").get())); } @@ -299,7 +351,7 @@ public class MongoExampleMapperUnitTests { ClassWithGeoTypes probe = new ClassWithGeoTypes(); probe.legacyPoint = new Point(10D, 20D); - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(WithDBRef.class)); assertThat(dbo.get("legacyPoint.x"), Is. is(10D)); assertThat(dbo.get("legacyPoint.y"), Is. is(20D)); @@ -316,7 +368,7 @@ public class MongoExampleMapperUnitTests { probe.intValue = 10; probe.stringValue = "string"; - Example example = newExampleOf(probe).ignore("customNamedField").get(); + Example example = Example.of(probe, ExampleSpec.untyped().withIgnorePaths("customNamedField")); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); @@ -334,7 +386,7 @@ public class MongoExampleMapperUnitTests { probe.intValue = 10; probe.stringValue = "string"; - Example example = newExampleOf(probe).ignore("stringValue").get(); + Example example = Example.of(probe, ExampleSpec.untyped().withIgnorePaths("stringValue")); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); @@ -353,12 +405,12 @@ public class MongoExampleMapperUnitTests { probe.flatDoc.intValue = 10; probe.flatDoc.stringValue = "string"; - Example example = newExampleOf(probe).ignore("flatDoc.stringValue").get(); + Example example = Example.of(probe, ExampleSpec.untyped().withIgnorePaths("flatDoc.stringValue")); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); - assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.custom_field_name", "foo").add("flatDoc.intValue", 10) - .get())); + assertThat(dbo, + is(new BasicDBObjectBuilder().add("flatDoc.custom_field_name", "foo").add("flatDoc.intValue", 10).get())); } /** @@ -373,12 +425,12 @@ public class MongoExampleMapperUnitTests { probe.flatDoc.intValue = 10; probe.flatDoc.stringValue = "string"; - Example example = newExampleOf(probe).ignore("flatDoc.customNamedField").get(); + Example example = Example.of(probe, ExampleSpec.untyped().withIgnorePaths("flatDoc.customNamedField")); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); - assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "string").add("flatDoc.intValue", 10) - .get())); + assertThat(dbo, + is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "string").add("flatDoc.intValue", 10).get())); } /** @@ -391,15 +443,13 @@ public class MongoExampleMapperUnitTests { probe.stringValue = "firefight"; probe.customNamedField = "steelheart"; - Example example = newExampleOf(probe).specify(newPropertySpecifier("stringValue").matchStringContaining().get()) - .get(); + Example example = Example.of(probe, + ExampleSpec.untyped().withMatcher("stringValue", new GenericPropertyMatcher().contains())); DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); - assertThat( - dbo, - is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", ".*firefight.*")) - .add("custom_field_name", "steelheart").get())); + assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", ".*firefight.*")) + .add("custom_field_name", "steelheart").get())); } /** @@ -413,7 +463,7 @@ public class MongoExampleMapperUnitTests { probe.customNamedField = "steelheart"; probe.anotherStringValue = "calamity"; - DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); + DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)); assertThat(dbo, isBsonObject().containing("anotherStringValue", "calamity")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java index 677ec3bb1..57a92e08c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * 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. @@ -32,6 +32,10 @@ import org.springframework.data.mongodb.core.query.Query; import com.mongodb.MongoClient; +/** + * @author Christoph Strobl + * @author Mark Paluch + */ public class QueryByExampleTests { MongoTemplate template; @@ -54,7 +58,9 @@ public class QueryByExampleTests { Person sample = new Person(); sample.lastname = "stark"; - List result = template.findByExample(sample); + Query query = new Query(new Criteria().alike(Example.of(sample))); + + List result = template.find(query, Person.class); Assert.assertThat(result.size(), Is.is(2)); } @@ -70,7 +76,9 @@ public class QueryByExampleTests { sample.lastname = "stark"; sample.firstname = "arya"; - List result = template.findByExample(sample); + Query query = new Query(new Criteria().alike(Example.of(sample))); + + List result = template.find(query, Person.class); Assert.assertThat(result.size(), Is.is(1)); } @@ -88,7 +96,9 @@ public class QueryByExampleTests { Person sample = new Person(); sample.id = p4.id; - List result = template.findByExample(sample); + Query query = new Query(new Criteria().alike(Example.of(sample))); + + List result = template.find(query, Person.class); Assert.assertThat(result.size(), Is.is(1)); } @@ -104,7 +114,10 @@ public class QueryByExampleTests { sample.firstname = "jon"; sample.firstname = "stark"; - List result = template.findByExample(sample); + + Query query = new Query(new Criteria().alike(Example.of(sample))); + + List result = template.find(query, Person.class); Assert.assertThat(result.size(), Is.is(0)); } @@ -118,7 +131,9 @@ public class QueryByExampleTests { Person sample = new Person(); - List result = template.findByExample(sample); + Query query = new Query(new Criteria().alike(Example.of(sample))); + + List result = template.find(query, Person.class); Assert.assertThat(result.size(), Is.is(3)); } @@ -133,7 +148,7 @@ public class QueryByExampleTests { Person sample = new Person(); sample.lastname = "stark"; - Query query = new Query(new Criteria().alike(new Example(sample)).and("firstname").regex("^ary*")); + Query query = new Query(new Criteria().alike(Example.of(sample)).and("firstname").regex("^ary*")); List result = template.find(query, Person.class); Assert.assertThat(result.size(), Is.is(1)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 0f716fb6c..b924d8188 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-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. @@ -28,8 +28,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.hamcrest.Matchers; -import org.hamcrest.core.Is; -import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -66,6 +64,7 @@ import org.springframework.test.util.ReflectionTestUtils; * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractPersonRepositoryIntegrationTests { @@ -179,8 +178,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void executesPagedFinderCorrectly() throws Exception { - Page page = repository.findByLastnameLike("*a*", new PageRequest(0, 2, Direction.ASC, "lastname", - "firstname")); + Page page = repository.findByLastnameLike("*a*", + new PageRequest(0, 2, Direction.ASC, "lastname", "firstname")); assertThat(page.isFirst(), is(true)); assertThat(page.isLast(), is(false)); assertThat(page.getNumberOfElements(), is(2)); @@ -190,8 +189,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void executesPagedFinderWithAnnotatedQueryCorrectly() throws Exception { - Page page = repository.findByLastnameLikeWithPageable(".*a.*", new PageRequest(0, 2, Direction.ASC, - "lastname", "firstname")); + Page page = repository.findByLastnameLikeWithPageable(".*a.*", + new PageRequest(0, 2, Direction.ASC, "lastname", "firstname")); assertThat(page.isFirst(), is(true)); assertThat(page.isLast(), is(false)); assertThat(page.getNumberOfElements(), is(2)); @@ -315,8 +314,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void findsPagedPeopleByPredicate() throws Exception { - Page page = repository.findAll(person.lastname.contains("a"), new PageRequest(0, 2, Direction.ASC, - "lastname")); + Page page = repository.findAll(person.lastname.contains("a"), + new PageRequest(0, 2, Direction.ASC, "lastname")); assertThat(page.isFirst(), is(true)); assertThat(page.isLast(), is(false)); assertThat(page.getNumberOfElements(), is(2)); @@ -402,8 +401,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { dave.setLocation(point); repository.save(dave); - GeoResults results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, - Metrics.KILOMETERS)); + GeoResults results = repository.findByLocationNear(new Point(-73.99, 40.73), + new Distance(2000, Metrics.KILOMETERS)); assertThat(results.getContent().isEmpty(), is(false)); } @@ -414,8 +413,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { dave.setLocation(point); repository.save(dave); - GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, - Metrics.KILOMETERS), new PageRequest(0, 20)); + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), + new Distance(2000, Metrics.KILOMETERS), new PageRequest(0, 20)); assertThat(results.getContent().isEmpty(), is(false)); // DATAMONGO-607 @@ -625,8 +624,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { repository.save(Arrays.asList(dave, oliver, carter, boyd, leroi)); - GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, - Metrics.KILOMETERS), new PageRequest(1, 2)); + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), + new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2)); assertThat(results.getContent().isEmpty(), is(false)); assertThat(results.getNumberOfElements(), is(2)); @@ -650,8 +649,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { repository.save(Arrays.asList(dave, oliver, carter)); - GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, - Metrics.KILOMETERS), new PageRequest(1, 2)); + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), + new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2)); assertThat(results.getContent().isEmpty(), is(false)); assertThat(results.getNumberOfElements(), is(1)); assertThat(results.isFirst(), is(false)); @@ -669,8 +668,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { dave.setLocation(point); repository.save(dave); - GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, - Metrics.KILOMETERS), new PageRequest(0, 2)); + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), + new Distance(2000, Metrics.KILOMETERS), new PageRequest(0, 2)); assertThat(results.getContent().isEmpty(), is(false)); assertThat(results.getNumberOfElements(), is(1)); @@ -688,8 +687,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { dave.setLocation(new Point(-73.99171, 40.738868)); repository.save(dave); - GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, - Metrics.KILOMETERS), new PageRequest(1, 2)); + GeoPage results = repository.findByLocationNear(new Point(-73.99, 40.73), + new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2)); assertThat(results.getContent().isEmpty(), is(true)); assertThat(results.getNumberOfElements(), is(0)); @@ -939,8 +938,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void shouldLimitCollectionQueryToMaxResultsWhenPresent() { - repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3", - "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); + repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), + new Person("Bob-3", "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); List result = repository.findTop3ByLastnameStartingWith("Dylan"); assertThat(result.size(), is(3)); } @@ -951,8 +950,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() { - repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3", - "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); + repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), + new Person("Bob-3", "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); Page result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(0, 2)); assertThat(result.getContent().size(), is(2)); } @@ -963,8 +962,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() { - repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3", - "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); + repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), + new Person("Bob-3", "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); Page result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(1, 2)); assertThat(result.getContent().size(), is(1)); } @@ -975,8 +974,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Test public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() { - repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3", - "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); + repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), + new Person("Bob-3", "Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan"))); Page result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(2, 2)); assertThat(result.getContent().size(), is(0)); } @@ -1240,8 +1239,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { ReflectionTestUtils.setField(sample, "createdAt", null); ReflectionTestUtils.setField(sample, "email", null); - Page result = repository.findAllByExample(new Example(sample), new PageRequest(0, 10)); - Assert.assertThat(result.getNumberOfElements(), Is.is(2)); + Page result = repository.findAll(Example.of(sample), new PageRequest(0, 10)); + assertThat(result.getNumberOfElements(), is(2)); } /** @@ -1258,8 +1257,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { ReflectionTestUtils.setField(sample, "createdAt", null); ReflectionTestUtils.setField(sample, "email", null); - List result = repository.findAllByExample(new Example(sample)); - Assert.assertThat(result.size(), Is.is(2)); + List result = repository.findAll(Example.of(sample)); + assertThat(result.size(), is(2)); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java index 1a7383601..1d15730af 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java @@ -27,7 +27,8 @@ import org.springframework.data.mongodb.core.mapping.Document; @Document public abstract class Contact { - @Id protected String id; + @Id + protected final String id; public Contact() { this.id = new ObjectId().toString(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java index d71e09a3f..224b85a53 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 the original author or authors. + * Copyright 2010-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. @@ -15,11 +15,15 @@ */ package org.springframework.data.mongodb.repository; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -27,6 +31,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * Integration tests for {@link ContactRepository}. Mostly related to mapping inheritance. * * @author Oliver Gierke + * @author Mark Paluch */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("config/MongoNamespaceIntegrationTests-context.xml") @@ -35,6 +40,11 @@ public class ContactRepositoryIntegrationTests { @Autowired ContactRepository repository; + @Before + public void setUp() throws Exception { + repository.deleteAll(); + } + @Test public void readsAndWritesContactCorrectly() { @@ -43,4 +53,46 @@ public class ContactRepositoryIntegrationTests { assertTrue(repository.findOne(result.getId().toString()) instanceof Person); } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void findsContactByUntypedExample() { + + Person person = new Person("Oliver", "Gierke"); + Contact result = repository.save(person); + + Example example = Example.of(person, ExampleSpec.untyped()); + + assertThat(repository.findOne(example), instanceOf(Person.class)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void findsContactByTypedExample() { + + Person person = new Person("Oliver", "Gierke"); + Contact result = repository.save(person); + + Example example = Example.of(person, ExampleSpec.typed(Person.class)); + + assertThat(repository.findOne(example), instanceOf(Person.class)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void findsNoContactByExample() { + + Person person = new Person("Oliver", "Gierke"); + Contact result = repository.save(person); + + Example example = Example.of(person, ExampleSpec.typed(Contact.class)); + + assertThat(repository.findOne(example), is(nullValue())); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 0796ba201..eae2c02e1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -334,23 +334,22 @@ public interface PersonRepository extends MongoRepository, Query */ @Query("{ firstname : { $in : ?0 }}") Stream findByCustomQueryWithStreamingCursorByFirstnames(List firstnames); - + /** * @see DATAMONGO-990 */ @Query("{ firstname : ?#{[0]}}") List findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname); - + /** * @see DATAMONGO-990 */ @Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }") List findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname); - + /** * @see DATAMONGO-990 */ @Query("{ firstname : :#{#firstname}}") List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname); - } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java index 1be130373..c13b27eb0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java @@ -18,7 +18,6 @@ package org.springframework.data.mongodb.repository.query; import java.util.Arrays; import java.util.Iterator; -import org.springframework.data.domain.Example; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Range; import org.springframework.data.domain.Sort; @@ -148,13 +147,4 @@ class StubParameterAccessor implements MongoParameterAccessor { public Class getDynamicProjection() { return null; } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getSampleObject() - */ - @Override - public Example getSampleObject() { - return null; - } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java index 6b75c51ed..1d4b4460a 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-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. @@ -32,6 +32,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.data.domain.ExampleSpec.StringMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.geo.Point; @@ -51,6 +53,7 @@ import org.springframework.test.util.ReflectionTestUtils; * @author A. B. M. Kowser * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -130,7 +133,7 @@ public class SimpleMongoRepositoryTests { * @see DATAMONGO-1054 */ @Test - public void shouldInsertMutlipleFromList() { + public void shouldInsertMultipleFromList() { String randomId = UUID.randomUUID().toString(); Map idToPerson = new HashMap(); @@ -180,7 +183,7 @@ public class SimpleMongoRepositoryTests { sample.setLastname("Matthews"); trimDomainType(sample, "id", "createdAt", "email"); - Page result = repository.findAllByExample(new Example(sample), new PageRequest(0, 10)); + Page result = repository.findAll(Example.of(sample), new PageRequest(0, 10)); assertThat(result.getContent(), hasItems(dave, oliver)); assertThat(result.getContent(), hasSize(2)); @@ -196,9 +199,9 @@ public class SimpleMongoRepositoryTests { sample.setLastname("Matthews"); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); - assertThat(result, hasItems(dave, oliver)); + assertThat(result, containsInAnyOrder(dave, oliver)); assertThat(result, hasSize(2)); } @@ -218,7 +221,7 @@ public class SimpleMongoRepositoryTests { sample.setAddress(dave.getAddress()); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); assertThat(result, hasItem(dave)); assertThat(result, hasSize(1)); @@ -240,7 +243,7 @@ public class SimpleMongoRepositoryTests { sample.setAddress(new Address(null, null, "Washington")); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); assertThat(result, hasItems(dave, oliver)); assertThat(result, hasSize(2)); @@ -259,7 +262,8 @@ public class SimpleMongoRepositoryTests { sample.setAddress(new Address(null, null, "Washington")); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(Example.newExampleOf(sample).includeNullValues().get()); + Example example = Example.of(sample, ExampleSpec.typed(Person.class).withIncludeNullValues()); + List result = repository.findAll(example); assertThat(result, empty()); } @@ -277,7 +281,8 @@ public class SimpleMongoRepositoryTests { sample.setAddress(dave.getAddress()); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(Example.newExampleOf(sample).includeNullValues().get()); + Example example = Example.of(sample, ExampleSpec.untyped().withIncludeNullValues()); + List result = repository.findAll(example); assertThat(result, hasItem(dave)); assertThat(result, hasSize(1)); @@ -293,7 +298,8 @@ public class SimpleMongoRepositoryTests { sample.setLastname("Mat"); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(Example.newExampleOf(sample).matchStringsStartingWith().get()); + Example example = Example.of(sample, ExampleSpec.untyped().withStringMatcher(StringMatcher.STARTING)); + List result = repository.findAll(example); assertThat(result, hasItems(dave, oliver)); assertThat(result, hasSize(2)); @@ -319,7 +325,7 @@ public class SimpleMongoRepositoryTests { sample.setCreator(user); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); assertThat(result, hasItem(megan)); assertThat(result, hasSize(1)); @@ -340,7 +346,7 @@ public class SimpleMongoRepositoryTests { sample.setLocation(megan.getLocation()); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); assertThat(result, hasItem(megan)); assertThat(result, hasSize(1)); @@ -361,7 +367,7 @@ public class SimpleMongoRepositoryTests { sample.setLocation(megan.getLocation()); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); assertThat(result, hasItem(megan)); assertThat(result, hasSize(1)); @@ -377,12 +383,59 @@ public class SimpleMongoRepositoryTests { sample.setLastname("Matthews"); trimDomainType(sample, "id", "createdAt", "email"); - List result = repository.findAllByExample(new Example(sample)); + List result = repository.findAll(Example.of(sample)); - assertThat(result, hasItems(dave, oliver)); + assertThat(result, containsInAnyOrder(dave, oliver)); assertThat(result, hasSize(2)); } + /** + * @see DATAMONGO-1245 + */ + @Test + public void findOneByExampleShouldLookUpEntriesCorrectly() { + + Person sample = new Person(); + sample.setFirstname("Dave"); + sample.setLastname("Matthews"); + trimDomainType(sample, "id", "createdAt", "email"); + + Person result = repository.findOne(Example.of(sample)); + + assertThat(result, is(equalTo(dave))); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void existsByExampleShouldLookUpEntriesCorrectly() { + + Person sample = new Person(); + sample.setFirstname("Dave"); + sample.setLastname("Matthews"); + trimDomainType(sample, "id", "createdAt", "email"); + + boolean result = repository.exists(Example.of(sample)); + + assertThat(result, is(true)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void countByExampleShouldLookUpEntriesCorrectly() { + + Person sample = new Person(); + sample.setLastname("Matthews"); + trimDomainType(sample, "id", "createdAt", "email"); + + long result = repository.count(Example.of(sample)); + + assertThat(result, is(equalTo(2L))); + } + @Document(collection = "customizedPerson") static class PersonExtended extends Person { diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 4ae939dba..aee79f8ae 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -1,5 +1,5 @@ = Spring Data MongoDB - Reference Documentation -Mark Pollack; Thomas Risberg; Oliver Gierke; Costin Leau; Jon Brisbin; Thomas Darimont; Christoph Strobl +Mark Pollack; Thomas Risberg; Oliver Gierke; Costin Leau; Jon Brisbin; Thomas Darimont; Christoph Strobl; Mark Paluch :revnumber: {version} :revdate: {localdate} :toc: diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index eb7948871..2e4d10e3b 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1330,6 +1330,10 @@ TextQuery.searching(new TextCriteria().matching("\"coffee cake\"")); TextQuery.searching(new TextCriteria().phrase("coffee cake")); ---- +include::../{spring-data-commons-docs}/query-by-example.adoc[] +include::query-by-example.adoc[] + + [[mongo.mapreduce]] == Map-Reduce Operations diff --git a/src/main/asciidoc/reference/query-by-example.adoc b/src/main/asciidoc/reference/query-by-example.adoc new file mode 100644 index 000000000..d3c0f0b1a --- /dev/null +++ b/src/main/asciidoc/reference/query-by-example.adoc @@ -0,0 +1,71 @@ +[[query.by.example.execution]] +=== Executing Example + +.Query by Example using a Repository +==== +[source, java] +---- +public interface PersonRepository extends QueryByExampleExecutor { + +} + +public class PersonService { + + @Autowired PersonRepository personRepository; + + public List findPeople(Person probe) { + return personRepository.findAll(Example.of(probe)); + } +} +---- +==== + +An `Example` containing an untyped `ExampleSpec` uses the Repository type and its collection name. Typed `ExampleSpec` use their type as result type and the collection name from the Repository. + +NOTE: When including `null` values in the `ExampleSpec` Spring Data Mongo uses embedded document matching instead of dot notation property matching. This forces exact document matching for all property values and the property order in the embedded document. + +Spring Data MongoDB provides support for the following matching options: + +[cols="1,2", options="header"] +.`StringMatcher` options +|=== +| Matching +| Logical result + +| `DEFAULT` (case-sensitive) +| `{"firstname" : firstname}` + +| `DEFAULT` (case-insensitive) +| `{"firstname" : { $regex: firstname, $options: 'i'}}` + +| `EXACT` (case-sensitive) +| `{"firstname" : { $regex: /^firstname$/}}` + +| `EXACT` (case-insensitive) +| `{"firstname" : { $regex: /^firstname$/, $options: 'i'}}` + +| `STARTING` (case-sensitive) +| `{"firstname" : { $regex: /^firstname/}}` + +| `STARTING` (case-insensitive) +| `{"firstname" : { $regex: /^firstname/, $options: 'i'}}` + +| `ENDING` (case-sensitive) +| `{"firstname" : { $regex: /firstname$/}}` + +| `ENDING` (case-insensitive) +| `{"firstname" : { $regex: /firstname$/, $options: 'i'}}` + +| `CONTAINING` (case-sensitive) +| `{"firstname" : { $regex: /.\*firstname.*/}}` + +| `CONTAINING` (case-insensitive) +| `{"firstname" : { $regex: /.\*firstname.*/, $options: 'i'}}` + +| `REGEX` (case-sensitive) +| `{"firstname" : { $regex: /firstname/}}` + +| `REGEX` (case-insensitive) +| `{"firstname" : { $regex: /firstname/, $options: 'i'}}` + +|===