diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java index 44c5f75f2..53dd03f06 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java @@ -67,8 +67,17 @@ class DocumentAccessor { return this.document; } - public void putAll(MongoPersistentProperty prop, Document value) { - value.entrySet().forEach(entry -> BsonUtils.asMap(document).put(entry.getKey(), entry.getValue())); + /** + * Copies all of the mappings from the given {@link Document} to the underlying target {@link Document}. These + * mappings will replace any mappings that the target document had for any of the keys currently in the specified map. + * + * @param source + */ + public void putAll(Document source) { + + Map target = BsonUtils.asMap(document); + + target.putAll(source); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 2e0f1e6ba..fd3a832e5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -672,7 +672,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App Document target = new Document(); writeInternal(obj, target, mappingContext.getPersistentEntity(prop)); - accessor.putAll(prop, target); + accessor.putAll(target); return; } 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 606cb03e8..93f935990 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 @@ -40,6 +40,7 @@ import org.springframework.data.mongodb.core.query.MongoRegexCreator; import org.springframework.data.mongodb.core.query.MongoRegexCreator.MatchMode; import org.springframework.data.mongodb.core.query.SerializationUtils; import org.springframework.data.mongodb.core.query.UntypedExampleMatcher; +import org.springframework.data.mongodb.util.DotPath; import org.springframework.data.support.ExampleMatcherAccessor; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; @@ -134,7 +135,7 @@ public class MongoExampleMapper { while (iter.hasNext()) { Map.Entry entry = iter.next(); - String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey(); + String propertyPath = DotPath.from(path).append(entry.getKey()).toString(); String mappedPropertyPath = getMappedPropertyPath(propertyPath, probeType); if (isEmptyIdProperty(entry)) { 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 e54deca25..e29397207 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 @@ -24,6 +24,7 @@ import org.bson.BsonValue; import org.bson.Document; import org.bson.conversions.Bson; import org.bson.types.ObjectId; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.data.domain.Example; @@ -32,7 +33,6 @@ import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.data.mapping.context.InvalidPersistentPropertyPath; @@ -43,6 +43,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.util.BsonUtils; +import org.springframework.data.mongodb.util.DotPath; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; @@ -255,17 +256,18 @@ public class QueryMapper { PropertyPath path = PropertyPath.from(field.getKey(), entity.getTypeInformation()); PersistentPropertyPath persistentPropertyPath = mappingContext .getPersistentPropertyPath(path); - MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getLeafProperty(); + MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getRequiredLeafProperty(); if (property.isEmbedded() && property.isEntity()) { - mappingContext.getPersistentEntity(property) - .doWithProperties((PropertyHandler) embedded -> { + MongoPersistentEntity embeddedEntity = mappingContext.getRequiredPersistentEntity(property); + + for (MongoPersistentProperty embedded : embeddedEntity) { + + DotPath dotPath = DotPath.from(persistentPropertyPath.toDotPath()).append(embedded.getName()); + target.put(dotPath.toString(), field.getValue()); + } - String dotPath = persistentPropertyPath.toDotPath(); - dotPath = dotPath + (StringUtils.hasText(dotPath) ? "." : "") + embedded.getName(); - target.put(dotPath, field.getValue()); - }); } else { target.put(field.getKey(), field.getValue()); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index 51d7badaa..85c15b7a1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java @@ -26,7 +26,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mongodb.core.mapping.EmbeddedMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.query.Query; @@ -164,10 +163,6 @@ public class UpdateMapper extends QueryMapper { return getMappedUpdateModifier(field, rawValue); } - if(field.getProperty() != null && field.getProperty().isEmbedded()) { - System.out.println("here we are: "); - } - return super.getMappedObjectForField(field, rawValue); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java index a912ae56c..6ecbdd8e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java @@ -28,17 +28,7 @@ import org.springframework.lang.Nullable; public interface IndexOperationsProvider { /** - * Returns the operations that can be performed on indexes - * - * @param collectionName name of the MongoDB collection, must not be {@literal null}. - * @param type the type used for field mapping. Can be {@literal null}. - * @return index operations on the named collection - * @since 2.5 - */ - IndexOperations indexOps(String collectionName, @Nullable Class type); - - /** - * Returns the operations that can be performed on indexes + * Returns the operations that can be performed on indexes. * * @param collectionName name of the MongoDB collection, must not be {@literal null}. * @return index operations on the named collection @@ -46,4 +36,14 @@ public interface IndexOperationsProvider { default IndexOperations indexOps(String collectionName) { return indexOps(collectionName, null); } + + /** + * Returns the operations that can be performed on indexes. + * + * @param collectionName name of the MongoDB collection, must not be {@literal null}. + * @param type the type used for field mapping. Can be {@literal null}. + * @return index operations on the named collection + * @since 3.2 + */ + IndexOperations indexOps(String collectionName, @Nullable Class type); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java index 8c158c34b..413ea661b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java @@ -47,6 +47,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.util.BsonUtils; +import org.springframework.data.mongodb.util.DotPath; import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.util.TypeInformation; import org.springframework.expression.EvaluationContext; @@ -161,16 +162,16 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { * @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property * types. Will never be {@code null}. */ - private List resolveIndexForClass(final TypeInformation type, final String dotPath, - final Path path, final String collection, final CycleGuard guard) { + private List resolveIndexForClass( TypeInformation type, String dotPath, + Path path, String collection, CycleGuard guard) { return resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type), dotPath, path, collection, guard); } - private List resolveIndexForEntity(MongoPersistentEntity entity, final String dotPath, - final Path path, final String collection, final CycleGuard guard) { + private List resolveIndexForEntity(MongoPersistentEntity entity, String dotPath, + Path path, String collection, CycleGuard guard) { - final List indexInformation = new ArrayList<>(); + List indexInformation = new ArrayList<>(); indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity)); entity.doWithProperties((PropertyHandler) property -> this @@ -184,10 +185,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { private void guardAndPotentiallyAddIndexForProperty(MongoPersistentProperty persistentProperty, String dotPath, Path path, String collection, List indexes, CycleGuard guard) { - String propertyDotPath = dotPath; + DotPath propertyDotPath = DotPath.from(dotPath); if (!persistentProperty.isEmbedded()) { - propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + persistentProperty.getFieldName(); + propertyDotPath = propertyDotPath.append(persistentProperty.getFieldName()); } Path propertyPath = path.append(persistentProperty); @@ -195,14 +196,14 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { if (persistentProperty.isEntity()) { try { - indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), propertyDotPath, + indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), propertyDotPath.toString(), propertyPath, collection, guard)); } catch (CyclicPropertyReferenceException e) { LOGGER.info(e.getMessage()); } } - List indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath, collection, + List indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(), collection, persistentProperty); if (!indexDefinitions.isEmpty()) { @@ -270,7 +271,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { } try { - appendTextIndexInformation("", Path.empty(), indexDefinitionBuilder, root, + appendTextIndexInformation(DotPath.empty(), Path.empty(), indexDefinitionBuilder, root, new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard()); } catch (CyclicPropertyReferenceException e) { LOGGER.info(e.getMessage()); @@ -291,9 +292,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { } - private void appendTextIndexInformation(final String dotPath, final Path path, - final TextIndexDefinitionBuilder indexDefinitionBuilder, final MongoPersistentEntity entity, - final TextIndexIncludeOptions includeOptions, final CycleGuard guard) { + private void appendTextIndexInformation(DotPath dotPath, Path path, + TextIndexDefinitionBuilder indexDefinitionBuilder, MongoPersistentEntity entity, + TextIndexIncludeOptions includeOptions, CycleGuard guard) { entity.doWithProperties(new PropertyHandler() { @@ -302,7 +303,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { guard.protect(persistentProperty, path); - if (persistentProperty.isExplicitLanguageProperty() && !StringUtils.hasText(dotPath)) { + if (persistentProperty.isExplicitLanguageProperty() && dotPath.isEmpty()) { indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName()); } @@ -310,8 +311,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) { - String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") - + persistentProperty.getFieldName(); + DotPath propertyDotPath = dotPath + .append(persistentProperty.getFieldName()); Path propertyPath = path.append(persistentProperty); @@ -324,7 +325,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { TextIndexIncludeOptions optionsForNestedType = includeOptions; if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) { optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE, - new TextIndexedFieldSpec(propertyDotPath, weight)); + new TextIndexedFieldSpec(propertyDotPath.toString(), weight)); } try { @@ -337,7 +338,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { entity.getName()), e); } } else if (includeOptions.isForce() || indexed != null) { - indexDefinitionBuilder.onField(propertyDotPath, weight); + indexDefinitionBuilder.onField(propertyDotPath.toString(), weight); } } @@ -648,7 +649,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { MongoPersistentProperty property = association.getInverse(); - String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + property.getFieldName(); + DotPath propertyDotPath = DotPath.from(path).append(property.getFieldName()); if (property.isAnnotationPresent(GeoSpatialIndexed.class) || property.isAnnotationPresent(TextIndexed.class)) { throw new MappingException( @@ -656,7 +657,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { propertyDotPath)); } - List indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath, collection, + List indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(), collection, property); if (!indexDefinitions.isEmpty()) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedEntityContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedEntityContext.java index a3390e8e5..c319d0fde 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedEntityContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedEntityContext.java @@ -19,7 +19,7 @@ package org.springframework.data.mongodb.core.mapping; * @author Christoph Strobl * @since 3.2 */ -public class EmbeddedEntityContext { +class EmbeddedEntityContext { private final MongoPersistentProperty property; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentEntity.java index 9029cfc2f..e5ab991cc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentEntity.java @@ -32,13 +32,16 @@ import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; /** + * Embedded variant of {@link MongoPersistentEntity}. + * * @author Christoph Strobl - * @since 2020/12 + * @since 3.2 + * @see Embedded */ -public class EmbeddedMongoPersistentEntity implements MongoPersistentEntity { +class EmbeddedMongoPersistentEntity implements MongoPersistentEntity { - private EmbeddedEntityContext context; - private MongoPersistentEntity delegate; + private final EmbeddedEntityContext context; + private final MongoPersistentEntity delegate; public EmbeddedMongoPersistentEntity(MongoPersistentEntity delegate, EmbeddedEntityContext context) { @@ -46,84 +49,103 @@ public class EmbeddedMongoPersistentEntity implements MongoPersistentEntity getPersistenceConstructor() { return delegate.getPersistenceConstructor(); } + @Override public boolean isConstructorArgument(PersistentProperty property) { return delegate.isConstructorArgument(property); } + @Override public boolean isIdProperty(PersistentProperty property) { return delegate.isIdProperty(property); } + @Override public boolean isVersionProperty(PersistentProperty property) { return delegate.isVersionProperty(property); } + @Override @Nullable public MongoPersistentProperty getIdProperty() { return delegate.getIdProperty(); } + @Override public MongoPersistentProperty getRequiredIdProperty() { return delegate.getRequiredIdProperty(); } + @Override @Nullable public MongoPersistentProperty getVersionProperty() { return delegate.getVersionProperty(); } + @Override public MongoPersistentProperty getRequiredVersionProperty() { return delegate.getRequiredVersionProperty(); } + @Override @Nullable public MongoPersistentProperty getPersistentProperty(String name) { return wrap(delegate.getPersistentProperty(name)); } + @Override public MongoPersistentProperty getRequiredPersistentProperty(String name) { MongoPersistentProperty persistentProperty = getPersistentProperty(name); @@ -134,36 +156,44 @@ public class EmbeddedMongoPersistentEntity implements MongoPersistentEntity annotationType) { return wrap(delegate.getPersistentProperty(annotationType)); } + @Override public Iterable getPersistentProperties(Class annotationType) { return Streamable.of(delegate.getPersistentProperties(annotationType)).stream().map(this::wrap) .collect(Collectors.toList()); } + @Override public boolean hasIdProperty() { return delegate.hasIdProperty(); } + @Override public boolean hasVersionProperty() { return delegate.hasVersionProperty(); } + @Override public Class getType() { return delegate.getType(); } + @Override public Alias getTypeAlias() { return delegate.getTypeAlias(); } + @Override public TypeInformation getTypeInformation() { return delegate.getTypeInformation(); } + @Override public void doWithProperties(PropertyHandler handler) { delegate.doWithProperties((PropertyHandler) property -> { @@ -171,6 +201,7 @@ public class EmbeddedMongoPersistentEntity implements MongoPersistentEntity { @@ -182,51 +213,63 @@ public class EmbeddedMongoPersistentEntity implements MongoPersistentEntity handler) { delegate.doWithAssociations(handler); } + @Override public void doWithAssociations(SimpleAssociationHandler handler) { delegate.doWithAssociations(handler); } + @Override @Nullable public A findAnnotation(Class annotationType) { return delegate.findAnnotation(annotationType); } + @Override public A getRequiredAnnotation(Class annotationType) throws IllegalStateException { return delegate.getRequiredAnnotation(annotationType); } + @Override public boolean isAnnotationPresent(Class annotationType) { return delegate.isAnnotationPresent(annotationType); } + @Override public PersistentPropertyAccessor getPropertyAccessor(B bean) { return delegate.getPropertyAccessor(bean); } + @Override public PersistentPropertyPathAccessor getPropertyPathAccessor(B bean) { return delegate.getPropertyPathAccessor(bean); } + @Override public IdentifierAccessor getIdentifierAccessor(Object bean) { return delegate.getIdentifierAccessor(bean); } + @Override public boolean isNew(Object bean) { return delegate.isNew(bean); } + @Override public boolean isImmutable() { return delegate.isImmutable(); } + @Override public boolean requiresPropertyPopulation() { return delegate.requiresPropertyPopulation(); } + @Override public Iterator iterator() { List target = new ArrayList<>(); @@ -234,10 +277,12 @@ public class EmbeddedMongoPersistentEntity implements MongoPersistentEntity action) { delegate.forEach(it -> action.accept(wrap(it))); } + @Override public Spliterator spliterator() { return delegate.spliterator(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentProperty.java index e307de451..8d2b8b5c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentProperty.java @@ -26,10 +26,13 @@ import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; /** + * Embedded variant of {@link MongoPersistentProperty}. + * * @author Christoph Strobl - * @since 2020/12 + * @since 3.2 + * @see Embedded */ -public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty { +class EmbeddedMongoPersistentProperty implements MongoPersistentProperty { private final MongoPersistentProperty delegate; private final EmbeddedEntityContext context; @@ -40,6 +43,7 @@ public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty this.context = context; } + @Override public String getFieldName() { if (!context.getProperty().isEmbedded()) { @@ -49,210 +53,254 @@ public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty return context.getProperty().findAnnotation(Embedded.class).prefix() + delegate.getFieldName(); } + @Override public Class getFieldType() { return delegate.getFieldType(); } + @Override public int getFieldOrder() { return delegate.getFieldOrder(); } + @Override public boolean isDbReference() { return delegate.isDbReference(); } + @Override public boolean isExplicitIdProperty() { return delegate.isExplicitIdProperty(); } + @Override public boolean isLanguageProperty() { return delegate.isLanguageProperty(); } + @Override public boolean isExplicitLanguageProperty() { return delegate.isExplicitLanguageProperty(); } + @Override public boolean isTextScoreProperty() { return delegate.isTextScoreProperty(); } + @Override @Nullable public DBRef getDBRef() { return delegate.getDBRef(); } + @Override public boolean usePropertyAccess() { return delegate.usePropertyAccess(); } + @Override public boolean hasExplicitWriteTarget() { return delegate.hasExplicitWriteTarget(); } + @Override public PersistentEntity getOwner() { return delegate.getOwner(); } + @Override public String getName() { return delegate.getName(); } + @Override public Class getType() { return delegate.getType(); } + @Override public TypeInformation getTypeInformation() { return delegate.getTypeInformation(); } + @Override public Iterable> getPersistentEntityTypes() { return delegate.getPersistentEntityTypes(); } + @Override @Nullable public Method getGetter() { return delegate.getGetter(); } + @Override public Method getRequiredGetter() { return delegate.getRequiredGetter(); } + @Override @Nullable public Method getSetter() { return delegate.getSetter(); } + @Override public Method getRequiredSetter() { return delegate.getRequiredSetter(); } + @Override @Nullable public Method getWither() { return delegate.getWither(); } + @Override public Method getRequiredWither() { return delegate.getRequiredWither(); } + @Override @Nullable public Field getField() { return delegate.getField(); } + @Override public Field getRequiredField() { return delegate.getRequiredField(); } + @Override @Nullable public String getSpelExpression() { return delegate.getSpelExpression(); } + @Override @Nullable public Association getAssociation() { return delegate.getAssociation(); } + @Override public Association getRequiredAssociation() { return delegate.getRequiredAssociation(); } + @Override public boolean isEntity() { return delegate.isEntity(); } + @Override public boolean isIdProperty() { return delegate.isIdProperty(); } + @Override public boolean isVersionProperty() { return delegate.isVersionProperty(); } + @Override public boolean isCollectionLike() { return delegate.isCollectionLike(); } + @Override public boolean isMap() { return delegate.isMap(); } + @Override public boolean isArray() { return delegate.isArray(); } + @Override public boolean isTransient() { return delegate.isTransient(); } + @Override public boolean isWritable() { return delegate.isWritable(); } + @Override public boolean isImmutable() { return delegate.isImmutable(); } + @Override public boolean isAssociation() { return delegate.isAssociation(); } + @Override public boolean isEmbedded() { return delegate.isEmbedded(); } - public boolean isNullable() { - return delegate.isNullable(); - } - + @Override @Nullable public Class getComponentType() { return delegate.getComponentType(); } + @Override public Class getRawType() { return delegate.getRawType(); } + @Override @Nullable public Class getMapValueType() { return delegate.getMapValueType(); } + @Override public Class getActualType() { return delegate.getActualType(); } + @Override @Nullable public A findAnnotation(Class annotationType) { return delegate.findAnnotation(annotationType); } + @Override public A getRequiredAnnotation(Class annotationType) throws IllegalStateException { return delegate.getRequiredAnnotation(annotationType); } + @Override @Nullable public A findPropertyOrOwnerAnnotation(Class annotationType) { return delegate.findPropertyOrOwnerAnnotation(annotationType); } + @Override public boolean isAnnotationPresent(Class annotationType) { return delegate.isAnnotationPresent(annotationType); } + @Override public boolean hasActualTypeAnnotation(Class annotationType) { return delegate.hasActualTypeAnnotation(annotationType); } + @Override @Nullable public Class getAssociationTargetType() { return delegate.getAssociationTargetType(); } + @Override public PersistentPropertyAccessor getAccessorForOwner(T owner) { return delegate.getAccessorForOwner(owner); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java index 0c7bea76d..3e18fd463 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java @@ -35,6 +35,7 @@ import org.springframework.lang.Nullable; * * @author Jon Brisbin * @author Oliver Gierke + * @author Christoph Strobl */ public class MongoMappingContext extends AbstractMappingContext, MongoPersistentProperty> implements ApplicationContextAware { @@ -81,18 +82,13 @@ public class MongoMappingContext extends AbstractMappingContext owner, SimpleTypeHolder simpleTypeHolder) { -// return null; -// } - /* * (non-Javadoc) * @see org.springframework.data.mapping.BasicMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.model.MappingContext) */ @Override protected BasicMongoPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new BasicMongoPersistentEntity(typeInformation); + return new BasicMongoPersistentEntity<>(typeInformation); } /* @@ -101,7 +97,6 @@ public class MongoMappingContext extends AbstractMappingContext getPersistentEntity(MongoPersistentProperty persistentProperty) { - MongoPersistentEntity entity = super.getPersistentEntity(persistentProperty); + MongoPersistentEntity entity = super.getPersistentEntity(persistentProperty); + if(entity == null || !persistentProperty.isEmbedded()) { return entity; } - return new EmbeddedMongoPersistentEntity(entity, new EmbeddedEntityContext(persistentProperty)); + return new EmbeddedMongoPersistentEntity<>(entity, new EmbeddedEntityContext(persistentProperty)); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java index 1f18f984e..d2e74d4de 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java @@ -94,6 +94,10 @@ public interface MongoPersistentEntity extends MutablePersistentEntity pipeline = agg.toPipeline(contextFor(Supplier.class)); - System.out.println("pipeline: " + pipeline); assertThat(pipeline).containsExactly(new Document("$project", new Document("supplier", 1)), // new Document("$unionWith", new Document("coll", "coll-1").append("pipeline", Arrays.asList(new Document("$project", new Document("name", 1)))))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index 57eb620a7..996309dcd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -1155,7 +1155,6 @@ class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(WrapperAroundWithEmbedded.class)); - System.out.println("mappedUpdate.toJson(): " + mappedUpdate.toJson()); assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("withPrefixedEmbedded", new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2"))))); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java index 32468fde1..5672ee2d6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java @@ -79,19 +79,6 @@ public class MapReduceTests { template.getMongoDbFactory().getMongoDatabase("jmr1-out-db").drop(); } - @Test // DATADOC-7 - @Ignore - public void testForDocs() { - - createMapReduceData(); - MapReduceResults results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION, - ValueObject.class); - - for (ValueObject valueObject : results) { - System.out.println(valueObject); - } - } - @Test // DATAMONGO-260 public void testIssue260() { diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 305e349cd..9cf28cb30 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -1,6 +1,11 @@ [[new-features]] = New & Noteworthy +[[new-features.3.2]] +== What's New in Spring Data MongoDB 3.2 + +* Support for <> to unwrap nested objects into the parent `Document`. + [[new-features.3.1]] == What's New in Spring Data MongoDB 3.1 diff --git a/src/main/asciidoc/reference/embedded-documents.adoc b/src/main/asciidoc/reference/embedded-documents.adoc index 1cfc91e64..2d83706a0 100644 --- a/src/main/asciidoc/reference/embedded-documents.adoc +++ b/src/main/asciidoc/reference/embedded-documents.adoc @@ -1,30 +1,33 @@ [[embedded-entities]] == Embedded Types -Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the MongoDB Document. +Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the parent's MongoDB Document. [[embedded-entities.mapping]] === Embedded Types Mapping -In the example below you see, that `User.name` is annotated with `@Embedded`. -The consequence of this is that all properties of `UserName` are folded into the `user` document. +Consider the following domain model where `User.name` is annotated with `@Embedded`. +The `@Embedded` annotation signals that all properties of `UserName` should be unwrapped into the `user` document that owns the `name` property. .Sample Code of embedding objects ==== [source,java] ---- -public class User { +class User { - @Id - private String userId; + @Id + String userId; @Embedded(onEmpty = USE_NULL) <1> UserName name; } -public class UserName { - private String firstname; - private String lastname; +class UserName { + + String firstname; + + String lastname; + } ---- @@ -41,37 +44,42 @@ By using `onEmpty=USE_EMPTY` an empty `UserName`, with potential `null` value fo ==== For less verbose embeddable type declarations use `@Embedded.Nullable` and `@Embedded.Empty` instead `@Embedded(onEmpty = USE_NULL)` and `@Embedded(onEmpty = USE_EMPTY)`. -Using those annotations simultaneously set JSR-305 `@javax.annotation.Nonnull` accordingly. +Both annotations are meta-annotated with JSR-305 `@javax.annotation.Nonnull` to aid with nullability inspections. [WARNING] ==== It is possible to use complex types within an embedded object. -However those must not be, nor contain embedded fields themselves. +However, those must not be, nor contain embedded fields themselves. ==== [[embedded-entities.mapping.field-names]] === Embedded Types field names A value object can be embedded multiple times by using the optional `prefix` attribute of the `@Embedded` annotation. -By dosing so the chosen prefix is prepended to each property or `@Field("...")` name in the embedded object. +By dosing so the chosen prefix is prepended to each property or `@Field("…")` name in the embedded object. Please note that values will overwrite each other if multiple properties render to the same field name. .Sample Code of embedded object with name prefix ==== [source,java] ---- -public class User { +class User { - @Id - private String userId; + @Id + String userId; - @Embedded.Nullable(prefix = "u") <1> + @Embedded.Nullable(prefix = "u_") <1> + UserName name; + + @Embedded.Nullable(prefix = "a_") <2> UserName name; } -public class UserName { - private String firstname; - private String lastname; +class UserName { + + String firstname; + + String lastname; } ---- @@ -79,11 +87,14 @@ public class UserName { ---- { "_id" : "a6a805bd-f95f", - "ufirstname" : "Jean", - "ulastname" : "Grey" + "u_firstname" : "Jean", <1> + "u_lastname" : "Grey", + "a_firstname" : "Something", <2> + "a_lastname" : "Else" } ---- -<1> The prefix `u` is prepended to all properties of `UserName`. +<1> All properties of `UserName` are prefixed with `u_`. +<2> All properties of `UserName` are prefixed with `a_`. ==== While combining the `@Field` annotation with `@Embedded` on the very same property does not make sense and therefore leads to an error. @@ -104,7 +115,7 @@ public class User { public class UserName { - @Field("first-name") <2> + @Field("first-name") <2> private String firstname; @Field("last-name") @@ -116,18 +127,18 @@ public class UserName { ---- { "_id" : "2647f7b9-89da", - "u-first-name" : "Barbara", <2> + "u-first-name" : "Barbara", <2> "u-last-name" : "Gordon" } ---- -<1> The prefix `u-` is prepended to all properties of `UserName`. -<2> The field name is the result of the combination of the annotated field name an the chosen prefix. +<1> All properties of `UserName` are prefixed with `u-`. +<2> Final field names are a result of concatenating `@Embedded(prefix)` and `@Field(name)`. ==== [[embedded-entities.queries]] === Query on Embedded Objects -Defining queries on embedded properties is possible on type as well as field level as the provided `Critieria` is matched against the domain type. +Defining queries on embedded properties is possible on type- as well as field-level as the provided `Criteria` is matched against the domain type. Prefixes and potential custom field names will be considered when rendering the actual query. Use the property name of the embedded object to match against all contained fields as shown in the sample below. @@ -149,7 +160,7 @@ db.collection.find({ ---- ==== -It is also possible to address any field of the embedded object directly via its property name as shown in the snippet below. +It is also possible to address any field of the embedded object directly using its property name as shown in the snippet below. .Query on field of embedded object ==== @@ -194,7 +205,7 @@ Though possible, using the embedded object itself as sort criteria includes all ==== [[embedded-entities.queries.project]] -==== Project on embedded object +==== Field projection on embedded objects Fields of embedded objects can be subject for projection either as a whole or via single fields as shown in the samples below. @@ -203,7 +214,7 @@ Fields of embedded objects can be subject for projection either as a whole or vi [source,java] ---- Query findByUserLastName = query(where("name.firstname").is("Gamora")); -findByUserLastName.fields().include("name"); <1> +findByUserLastName.fields().include("name"); <1> List user = template.findAll(findByUserName, User.class); ---- @@ -225,7 +236,7 @@ db.collection.find({ [source,java] ---- Query findByUserLastName = query(where("name.lastname").is("Smoak")); -findByUserLastName.fields().include("name.firstname"); <1> +findByUserLastName.fields().include("name.firstname"); <1> List user = template.findAll(findByUserName, User.class); ---- @@ -258,9 +269,9 @@ The `Repository` abstraction allows deriving queries on fields of embedded objec ---- interface UserRepository extends CrudRepository { - List findByName(UserName username); <1> + List findByName(UserName username); <1> - List findByNameFirstname(String firstname); <1> + List findByNameFirstname(String firstname); <2> } ---- <1> Matches against all fields of the embedded object. @@ -330,14 +341,14 @@ db.collection.update({ === Aggregations on Embedded Objects The <> will attempt to map embedded values of typed aggregations. -Please make sure to work with the properties path including the embedded wrapper object when referencing one of it's values. +Please make sure to work with the property path including the embedded wrapper object when referencing one of its values. Other than that no special action is required. [[embedded-entities.indexes]] === Index on Embedded Objects It is possible to attach the `@Indexed` annotation to properties of an embedded type just as it is done with regular objects. -However it is not possible to use `@Indexed` along with the `@Embedded` annotation on the very same property of an object. +It is not possible to use `@Indexed` along with the `@Embedded` annotation on the owning property. ==== [source,java] @@ -348,9 +359,10 @@ public class User { private String userId; @Embedded(onEmpty = USE_NULL) - UserName name; <1> + UserName name; <1> - @Indexed <2> // Invalid -> InvalidDataAccessApiUsageException + // Invalid -> InvalidDataAccessApiUsageException + @Indexed <2> @Embedded(onEmpty = USE_Empty) Address address; } @@ -360,7 +372,7 @@ public class UserName { private String firstname; @Indexed - private String lastname; <1> + private String lastname; <1> } ---- <1> Index created for `lastname` in `users` collection.