Browse Source

Rename Embedded annotation to Unwrapped.

The meaning of embedding a Document in MongoDB is different compared to column based stores. Typically the term is used for a Document in Document approach and not for flattening out a values into the enclosing Document.

Closes: #3600
Original pull request: #3604.
pull/3611/head
Christoph Strobl 5 years ago committed by Mark Paluch
parent
commit
5a87dec2d5
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 22
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  2. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  3. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java
  4. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java
  5. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java
  6. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java
  7. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java
  8. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrapEntityContext.java
  9. 50
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Unwrapped.java
  10. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentEntity.java
  11. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java
  12. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java
  13. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateFieldProjectionTests.java
  14. 46
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnwrappedTests.java
  15. 81
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java
  16. 68
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
  17. 66
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java
  18. 132
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java
  19. 74
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java
  20. 30
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java
  21. 12
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  22. 14
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java
  23. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  24. 2
      src/main/asciidoc/new-features.adoc
  25. 2
      src/main/asciidoc/reference/mapping.adoc
  26. 140
      src/main/asciidoc/reference/unwrapping-entities.adoc

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

@ -62,10 +62,10 @@ import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.mongodb.CodecRegistryProvider; import org.springframework.data.mongodb.CodecRegistryProvider;
import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Embedded.OnEmpty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.mapping.Unwrapped.OnEmpty;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback; import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent; import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent; import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
@ -447,10 +447,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
continue; continue;
} }
if (prop.isEmbedded()) { if (prop.isUnwrapped()) {
accessor.setProperty(prop, accessor.setProperty(prop,
readEmbedded(context, documentAccessor, prop, mappingContext.getRequiredPersistentEntity(prop))); readUnwrapped(context, documentAccessor, prop, mappingContext.getRequiredPersistentEntity(prop)));
continue; continue;
} }
@ -500,17 +500,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
} }
@Nullable @Nullable
private Object readEmbedded(ConversionContext context, DocumentAccessor documentAccessor, private Object readUnwrapped(ConversionContext context, DocumentAccessor documentAccessor,
MongoPersistentProperty prop, MongoPersistentProperty prop,
MongoPersistentEntity<?> embeddedEntity) { MongoPersistentEntity<?> unwrappedEntity) {
if (prop.findAnnotation(Embedded.class).onEmpty().equals(OnEmpty.USE_EMPTY)) { if (prop.findAnnotation(Unwrapped.class).onEmpty().equals(OnEmpty.USE_EMPTY)) {
return read(context, embeddedEntity, (Document) documentAccessor.getDocument()); return read(context, unwrappedEntity, (Document) documentAccessor.getDocument());
} }
for (MongoPersistentProperty persistentProperty : embeddedEntity) { for (MongoPersistentProperty persistentProperty : unwrappedEntity) {
if (documentAccessor.hasValue(persistentProperty)) { if (documentAccessor.hasValue(persistentProperty)) {
return read(context, embeddedEntity, (Document) documentAccessor.getDocument()); return read(context, unwrappedEntity, (Document) documentAccessor.getDocument());
} }
} }
return null; return null;
@ -680,7 +680,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass()); TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
TypeInformation<?> type = prop.getTypeInformation(); TypeInformation<?> type = prop.getTypeInformation();
if (prop.isEmbedded()) { if (prop.isUnwrapped()) {
Document target = new Document(); Document target = new Document();
writeInternal(obj, target, mappingContext.getPersistentEntity(prop)); writeInternal(obj, target, mappingContext.getPersistentEntity(prop));

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

@ -144,7 +144,7 @@ public class QueryMapper {
Field field = createPropertyField(entity, key, mappingContext); Field field = createPropertyField(entity, key, mappingContext);
// TODO: move to dedicated method // TODO: move to dedicated method
if (field.getProperty() != null && field.getProperty().isEmbedded()) { if (field.getProperty() != null && field.getProperty().isUnwrapped()) {
Object theNestedObject = BsonUtils.get(query, key); Object theNestedObject = BsonUtils.get(query, key);
Document mappedValue = (Document) getMappedValue(field, theNestedObject); Document mappedValue = (Document) getMappedValue(field, theNestedObject);
@ -189,13 +189,13 @@ public class QueryMapper {
return new Document(); return new Document();
} }
sortObject = filterEmbeddedObjects(sortObject, entity); sortObject = filterUnwrappedObjects(sortObject, entity);
Document mappedSort = new Document(); Document mappedSort = new Document();
for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) { for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
Field field = createPropertyField(entity, entry.getKey(), mappingContext); Field field = createPropertyField(entity, entry.getKey(), mappingContext);
if (field.getProperty() != null && field.getProperty().isEmbedded()) { if (field.getProperty() != null && field.getProperty().isUnwrapped()) {
continue; continue;
} }
@ -219,7 +219,7 @@ public class QueryMapper {
Assert.notNull(fieldsObject, "FieldsObject must not be null!"); Assert.notNull(fieldsObject, "FieldsObject must not be null!");
fieldsObject = filterEmbeddedObjects(fieldsObject, entity); fieldsObject = filterUnwrappedObjects(fieldsObject, entity);
Document mappedFields = getMappedObject(fieldsObject, entity); Document mappedFields = getMappedObject(fieldsObject, entity);
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE); mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
@ -241,7 +241,7 @@ public class QueryMapper {
} }
} }
private Document filterEmbeddedObjects(Document fieldsObject, @Nullable MongoPersistentEntity<?> entity) { private Document filterUnwrappedObjects(Document fieldsObject, @Nullable MongoPersistentEntity<?> entity) {
if (fieldsObject.isEmpty() || entity == null) { if (fieldsObject.isEmpty() || entity == null) {
return fieldsObject; return fieldsObject;
@ -258,13 +258,13 @@ public class QueryMapper {
.getPersistentPropertyPath(path); .getPersistentPropertyPath(path);
MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getRequiredLeafProperty(); MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getRequiredLeafProperty();
if (property.isEmbedded() && property.isEntity()) { if (property.isUnwrapped() && property.isEntity()) {
MongoPersistentEntity<?> embeddedEntity = mappingContext.getRequiredPersistentEntity(property); MongoPersistentEntity<?> unwrappedEntity = mappingContext.getRequiredPersistentEntity(property);
for (MongoPersistentProperty embedded : embeddedEntity) { for (MongoPersistentProperty unwrappedProperty : unwrappedEntity) {
DotPath dotPath = DotPath.from(persistentPropertyPath.toDotPath()).append(embedded.getName()); DotPath dotPath = DotPath.from(persistentPropertyPath.toDotPath()).append(unwrappedProperty.getName());
target.put(dotPath.toString(), field.getValue()); target.put(dotPath.toString(), field.getValue());
} }
@ -564,7 +564,7 @@ public class QueryMapper {
@Nullable @Nullable
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) { protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
if (entity != null && entity.isEmbedded()) { if (entity != null && entity.isUnwrapped()) {
return converter.convertToMongoType(source, entity); return converter.convertToMongoType(source, entity);
} }

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

@ -133,7 +133,7 @@ public class UpdateMapper extends QueryMapper {
@Override @Override
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) { protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
if(entity != null && entity.isEmbedded()) { if(entity != null && entity.isUnwrapped()) {
return converter.convertToMongoType(source, entity); return converter.convertToMongoType(source, entity);
} }

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java

@ -137,7 +137,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
try { try {
if (persistentProperty.isEntity()) { if (persistentProperty.isEntity()) {
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
persistentProperty.isEmbedded() ? "" : persistentProperty.getFieldName(), Path.of(persistentProperty), persistentProperty.isUnwrapped() ? "" : persistentProperty.getFieldName(), Path.of(persistentProperty),
root.getCollection(), guard)); root.getCollection(), guard));
} }
@ -187,7 +187,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
DotPath propertyDotPath = DotPath.from(dotPath); DotPath propertyDotPath = DotPath.from(dotPath);
if (!persistentProperty.isEmbedded()) { if (!persistentProperty.isUnwrapped()) {
propertyDotPath = propertyDotPath.append(persistentProperty.getFieldName()); propertyDotPath = propertyDotPath.append(persistentProperty.getFieldName());
} }
@ -216,11 +216,11 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
List<IndexDefinitionHolder> indices = new ArrayList<>(2); List<IndexDefinitionHolder> indices = new ArrayList<>(2);
if (persistentProperty.isEmbedded() && (persistentProperty.isAnnotationPresent(Indexed.class) if (persistentProperty.isUnwrapped() && (persistentProperty.isAnnotationPresent(Indexed.class)
|| persistentProperty.isAnnotationPresent(HashIndexed.class) || persistentProperty.isAnnotationPresent(HashIndexed.class)
|| persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class))) { || persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class))) {
throw new InvalidDataAccessApiUsageException( throw new InvalidDataAccessApiUsageException(
String.format("Index annotation not allowed on embedded object for path '%s'.", dotPath)); String.format("Index annotation not allowed on unwrapped object for path '%s'.", dotPath));
} }
if (persistentProperty.isAnnotationPresent(Indexed.class)) { if (persistentProperty.isAnnotationPresent(Indexed.class)) {

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoMappingContext.java

@ -133,10 +133,10 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
MongoPersistentEntity<?> entity = super.getPersistentEntity(persistentProperty); MongoPersistentEntity<?> entity = super.getPersistentEntity(persistentProperty);
if(entity == null || !persistentProperty.isEmbedded()) { if(entity == null || !persistentProperty.isUnwrapped()) {
return entity; return entity;
} }
return new EmbeddedMongoPersistentEntity<>(entity, new EmbeddedEntityContext(persistentProperty)); return new UnwrappedMongoPersistentEntity<>(entity, new UnwrapEntityContext(persistentProperty));
} }
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java

@ -95,10 +95,10 @@ public interface MongoPersistentEntity<T> extends MutablePersistentEntity<T, Mon
} }
/** /**
* @return {@literal true} if the entity should be embedded. * @return {@literal true} if the entity should be unwrapped.
* @since 3.2 * @since 3.2
*/ */
default boolean isEmbedded() { default boolean isUnwrapped() {
return false; return false;
} }

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java

@ -124,11 +124,11 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
} }
/** /**
* @return {@literal true} if the property should be embedded. * @return {@literal true} if the property should be unwrapped.
* @since 3.2 * @since 3.2
*/ */
default boolean isEmbedded() { default boolean isUnwrapped() {
return isEntity() && isAnnotationPresent(Embedded.class); return isEntity() && isAnnotationPresent(Unwrapped.class);
} }
/** /**
@ -145,7 +145,7 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/ */
public String convert(MongoPersistentProperty source) { public String convert(MongoPersistentProperty source) {
if (!source.isEmbedded()) { if (!source.isUnwrapped()) {
return source.getFieldName(); return source.getFieldName();
} }
return ""; return "";

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedEntityContext.java → spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrapEntityContext.java

@ -19,11 +19,11 @@ package org.springframework.data.mongodb.core.mapping;
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.2 * @since 3.2
*/ */
class EmbeddedEntityContext { class UnwrapEntityContext {
private final MongoPersistentProperty property; private final MongoPersistentProperty property;
public EmbeddedEntityContext(MongoPersistentProperty property) { public UnwrapEntityContext(MongoPersistentProperty property) {
this.property = property; this.property = property;
} }

50
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Embedded.java → spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Unwrapped.java

@ -26,10 +26,10 @@ import javax.annotation.meta.When;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
/** /**
* The annotation to configure a value object as embedded (flattened out) in the target document. * The annotation to configure a value object as flattened out in the target document.
* <p /> * <p />
* Depending on the {@link OnEmpty value} of {@link #onEmpty()} the property is set to {@literal null} or an empty * Depending on the {@link OnEmpty value} of {@link #onEmpty()} the property is set to {@literal null} or an empty
* instance in the case all embedded values are {@literal null} when reading from the result set. * instance in the case all unwrapped values are {@literal null} when reading from the result set.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.2 * @since 3.2
@ -37,24 +37,24 @@ import org.springframework.core.annotation.AliasFor;
@Documented @Documented
@Retention(value = RetentionPolicy.RUNTIME) @Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD }) @Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
public @interface Embedded { public @interface Unwrapped {
/** /**
* Set the load strategy for the embedded object if all contained fields yield {@literal null} values. * Set the load strategy for the unwrapped object if all contained fields yield {@literal null} values.
* <p /> * <p />
* {@link Nullable @Embedded.Nullable} and {@link Empty @Embedded.Empty} offer shortcuts for this. * {@link Nullable @Unwrapped.Nullable} and {@link Empty @Unwrapped.Empty} offer shortcuts for this.
* *
* @return never {@link} null. * @return never {@link} null.
*/ */
OnEmpty onEmpty(); OnEmpty onEmpty();
/** /**
* @return prefix for columns in the embedded value object. An empty {@link String} by default. * @return prefix for columns in the unwrapped value object. An empty {@link String} by default.
*/ */
String prefix() default ""; String prefix() default "";
/** /**
* Load strategy to be used {@link Embedded#onEmpty()}. * Load strategy to be used {@link Unwrapped#onEmpty()}.
* *
* @author Christoph Strobl * @author Christoph Strobl
*/ */
@ -63,22 +63,22 @@ public @interface Embedded {
} }
/** /**
* Shortcut for a nullable embedded property. * Shortcut for a nullable unwrapped property.
* *
* <pre class="code"> * <pre class="code">
* &#64;Embedded.Nullable private Address address; * &#64;Unwrapped.Nullable private Address address;
* </pre> * </pre>
* *
* as alternative to the more verbose * as alternative to the more verbose
* *
* <pre class="code"> * <pre class="code">
* &#64;Embedded(onEmpty = USE_NULL) &#64;javax.annotation.Nonnull(when = When.MAYBE) private Address address; * &#64;Unwrapped(onEmpty = USE_NULL) &#64;javax.annotation.Nonnull(when = When.MAYBE) private Address address;
* </pre> * </pre>
* *
* @author Christoph Strobl * @author Christoph Strobl
* @see Embedded#onEmpty() * @see Unwrapped#onEmpty()
*/ */
@Embedded(onEmpty = OnEmpty.USE_NULL) @Unwrapped(onEmpty = OnEmpty.USE_NULL)
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD }) @Target({ ElementType.FIELD, ElementType.METHOD })
@ -86,35 +86,35 @@ public @interface Embedded {
@interface Nullable { @interface Nullable {
/** /**
* @return prefix for columns in the embedded value object. An empty {@link String} by default. * @return prefix for columns in the unwrapped value object. An empty {@link String} by default.
*/ */
@AliasFor(annotation = Embedded.class, attribute = "prefix") @AliasFor(annotation = Unwrapped.class, attribute = "prefix")
String prefix() default ""; String prefix() default "";
/** /**
* @return value for columns in the embedded value object. An empty {@link String} by default. * @return value for columns in the unwrapped value object. An empty {@link String} by default.
*/ */
@AliasFor(annotation = Embedded.class, attribute = "prefix") @AliasFor(annotation = Unwrapped.class, attribute = "prefix")
String value() default ""; String value() default "";
} }
/** /**
* Shortcut for an empty embedded property. * Shortcut for an empty unwrapped property.
* *
* <pre class="code"> * <pre class="code">
* &#64;Embedded.Empty private Address address; * &#64;Unwrapped.Empty private Address address;
* </pre> * </pre>
* *
* as alternative to the more verbose * as alternative to the more verbose
* *
* <pre class="code"> * <pre class="code">
* &#64;Embedded(onEmpty = USE_EMPTY) &#64;javax.annotation.Nonnull(when = When.NEVER) private Address address; * &#64;Unwrapped(onEmpty = USE_EMPTY) &#64;javax.annotation.Nonnull(when = When.NEVER) private Address address;
* </pre> * </pre>
* *
* @author Christoph Strobl * @author Christoph Strobl
* @see Embedded#onEmpty() * @see Unwrapped#onEmpty()
*/ */
@Embedded(onEmpty = OnEmpty.USE_EMPTY) @Unwrapped(onEmpty = OnEmpty.USE_EMPTY)
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD }) @Target({ ElementType.FIELD, ElementType.METHOD })
@ -122,15 +122,15 @@ public @interface Embedded {
@interface Empty { @interface Empty {
/** /**
* @return prefix for columns in the embedded value object. An empty {@link String} by default. * @return prefix for columns in the unwrapped value object. An empty {@link String} by default.
*/ */
@AliasFor(annotation = Embedded.class, attribute = "prefix") @AliasFor(annotation = Unwrapped.class, attribute = "prefix")
String prefix() default ""; String prefix() default "";
/** /**
* @return value for columns in the embedded value object. An empty {@link String} by default. * @return value for columns in the unwrapped value object. An empty {@link String} by default.
*/ */
@AliasFor(annotation = Embedded.class, attribute = "prefix") @AliasFor(annotation = Unwrapped.class, attribute = "prefix")
String value() default ""; String value() default "";
} }
} }

18
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentEntity.java → spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentEntity.java

@ -32,18 +32,18 @@ import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* Embedded variant of {@link MongoPersistentEntity}. * Unwrapped variant of {@link MongoPersistentEntity}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.2 * @since 3.2
* @see Embedded * @see Unwrapped
*/ */
class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> { class UnwrappedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
private final EmbeddedEntityContext context; private final UnwrapEntityContext context;
private final MongoPersistentEntity<T> delegate; private final MongoPersistentEntity<T> delegate;
public EmbeddedMongoPersistentEntity(MongoPersistentEntity<T> delegate, EmbeddedEntityContext context) { public UnwrappedMongoPersistentEntity(MongoPersistentEntity<T> delegate, UnwrapEntityContext context) {
this.context = context; this.context = context;
this.delegate = delegate; this.delegate = delegate;
@ -153,7 +153,7 @@ class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
return persistentProperty; return persistentProperty;
} }
throw new RuntimeException(":kladjnf"); throw new IllegalStateException(String.format("Required property %s not found for %s!", name, getType()));
} }
@Override @Override
@ -291,7 +291,7 @@ class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
if (source == null) { if (source == null) {
return source; return source;
} }
return new EmbeddedMongoPersistentProperty(source, context); return new UnwrappedMongoPersistentProperty(source, context);
} }
@Override @Override
@ -320,7 +320,7 @@ class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
} }
@Override @Override
public boolean isEmbedded() { public boolean isUnwrapped() {
return context.getProperty().isEmbedded(); return context.getProperty().isUnwrapped();
} }
} }

18
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/EmbeddedMongoPersistentProperty.java → spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java

@ -26,18 +26,18 @@ import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* Embedded variant of {@link MongoPersistentProperty}. * Unwrapped variant of {@link MongoPersistentProperty}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.2 * @since 3.2
* @see Embedded * @see Unwrapped
*/ */
class EmbeddedMongoPersistentProperty implements MongoPersistentProperty { class UnwrappedMongoPersistentProperty implements MongoPersistentProperty {
private final MongoPersistentProperty delegate; private final MongoPersistentProperty delegate;
private final EmbeddedEntityContext context; private final UnwrapEntityContext context;
public EmbeddedMongoPersistentProperty(MongoPersistentProperty delegate, EmbeddedEntityContext context) { public UnwrappedMongoPersistentProperty(MongoPersistentProperty delegate, UnwrapEntityContext context) {
this.delegate = delegate; this.delegate = delegate;
this.context = context; this.context = context;
@ -46,11 +46,11 @@ class EmbeddedMongoPersistentProperty implements MongoPersistentProperty {
@Override @Override
public String getFieldName() { public String getFieldName() {
if (!context.getProperty().isEmbedded()) { if (!context.getProperty().isUnwrapped()) {
return delegate.getFieldName(); return delegate.getFieldName();
} }
return context.getProperty().findAnnotation(Embedded.class).prefix() + delegate.getFieldName(); return context.getProperty().findAnnotation(Unwrapped.class).prefix() + delegate.getFieldName();
} }
@Override @Override
@ -241,8 +241,8 @@ class EmbeddedMongoPersistentProperty implements MongoPersistentProperty {
} }
@Override @Override
public boolean isEmbedded() { public boolean isUnwrapped() {
return delegate.isEmbedded(); return delegate.isUnwrapped();
} }
@Override @Override

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java

@ -30,7 +30,7 @@ import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexOperationsProvider; import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
import org.springframework.data.mongodb.core.mapping.Embedded; import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.repository.query.MongoEntityMetadata; import org.springframework.data.mongodb.repository.query.MongoEntityMetadata;
import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery; import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery;
@ -90,7 +90,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
if (GEOSPATIAL_TYPES.contains(part.getType())) { if (GEOSPATIAL_TYPES.contains(part.getType())) {
return; return;
} }
if (isIndexOnEmbeddedType(part)) { if (isIndexOnUnwrappedType(part)) {
return; return;
} }
@ -138,7 +138,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
LOG.debug(String.format("Created %s!", index)); LOG.debug(String.format("Created %s!", index));
} }
public boolean isIndexOnEmbeddedType(Part part) { public boolean isIndexOnUnwrappedType(Part part) {
// TODO we could do it for nested fields in the // TODO we could do it for nested fields in the
Field field = ReflectionUtils.findField(part.getProperty().getOwningType().getType(), Field field = ReflectionUtils.findField(part.getProperty().getOwningType().getType(),
@ -148,7 +148,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
return false; return false;
} }
return AnnotatedElementUtils.hasAnnotation(field, Embedded.class); return AnnotatedElementUtils.hasAnnotation(field, Unwrapped.class);
} }
private static Direction toDirection(Sort sort, String property) { private static Direction toDirection(Sort sort, String property) {

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateFieldProjectionTests.java

@ -30,8 +30,8 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.MongoExpression; import org.springframework.data.mongodb.MongoExpression;
import org.springframework.data.mongodb.core.aggregation.AggregationSpELExpression; import org.springframework.data.mongodb.core.aggregation.AggregationSpELExpression;
import org.springframework.data.mongodb.core.aggregation.StringOperators; import org.springframework.data.mongodb.core.aggregation.StringOperators;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.MongoTemplateExtension; import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
@ -126,7 +126,7 @@ class MongoTemplateFieldProjectionTests {
} }
@Test // GH-3583 @Test // GH-3583
void mapsProjectionOnEmbedded() { void mapsProjectionOnUnwrapped() {
luke.address = new Address(); luke.address = new Address();
luke.address.planet = "tatoine"; luke.address.planet = "tatoine";
@ -164,7 +164,7 @@ class MongoTemplateFieldProjectionTests {
@Field("last_name") // @Field("last_name") //
String lastname; String lastname;
@Embedded.Nullable Address address; @Unwrapped.Nullable Address address;
Person toUpperCaseLastnameClone(Person source) { Person toUpperCaseLastnameClone(Person source) {

46
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateEmbeddedTests.java → spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnwrappedTests.java

@ -20,50 +20,50 @@ import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*; import static org.springframework.data.mongodb.core.query.Query.*;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import lombok.ToString;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.MongoTemplateExtension; import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
import org.springframework.data.mongodb.test.util.Template; import org.springframework.data.mongodb.test.util.Template;
/** /**
* Integration tests for {@link Embedded}. * Integration tests for {@link Unwrapped}.
* *
* @author Christoph Strobl * @author Christoph Strobl
*/ */
@ExtendWith(MongoTemplateExtension.class) @ExtendWith(MongoTemplateExtension.class)
class MongoTemplateEmbeddedTests { class MongoTemplateUnwrappedTests {
private static @Template MongoTemplate template; private static @Template MongoTemplate template;
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readWrite() { void readWrite() {
WithEmbedded source = new WithEmbedded(); WithUnwrapped source = new WithUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new UnwrappableType();
source.embeddableValue.stringValue = "string-val"; source.embeddableValue.stringValue = "string-val";
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
source.embeddableValue.atFieldAnnotatedValue = "@Field"; source.embeddableValue.atFieldAnnotatedValue = "@Field";
template.save(source); template.save(source);
assertThat(template.findOne(query(where("id").is(source.id)), WithEmbedded.class)).isEqualTo(source); assertThat(template.findOne(query(where("id").is(source.id)), WithUnwrapped.class)).isEqualTo(source);
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void filterOnEmbeddedValue() { void filterOnUnwrappedValue() {
WithEmbedded source = new WithEmbedded(); WithUnwrapped source = new WithUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new UnwrappableType();
source.embeddableValue.stringValue = "string-val"; source.embeddableValue.stringValue = "string-val";
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
source.embeddableValue.atFieldAnnotatedValue = "@Field"; source.embeddableValue.atFieldAnnotatedValue = "@Field";
@ -71,31 +71,31 @@ class MongoTemplateEmbeddedTests {
template.save(source); template.save(source);
assertThat(template.findOne( assertThat(template.findOne(
Query.query(where("embeddableValue.stringValue").is(source.embeddableValue.stringValue)), WithEmbedded.class)) Query.query(where("embeddableValue.stringValue").is(source.embeddableValue.stringValue)), WithUnwrapped.class))
.isEqualTo(source); .isEqualTo(source);
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readWritePrefixed() { void readWritePrefixed() {
WithPrefixedEmbedded source = new WithPrefixedEmbedded(); WithPrefixedUnwrapped source = new WithPrefixedUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new UnwrappableType();
source.embeddableValue.stringValue = "string-val"; source.embeddableValue.stringValue = "string-val";
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
source.embeddableValue.atFieldAnnotatedValue = "@Field"; source.embeddableValue.atFieldAnnotatedValue = "@Field";
template.save(source); template.save(source);
assertThat(template.findOne(query(where("id").is(source.id)), WithPrefixedEmbedded.class)).isEqualTo(source); assertThat(template.findOne(query(where("id").is(source.id)), WithPrefixedUnwrapped.class)).isEqualTo(source);
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void filterOnPrefixedEmbeddedValue() { void filterOnPrefixedUnwrappedValue() {
WithPrefixedEmbedded source = new WithPrefixedEmbedded(); WithPrefixedUnwrapped source = new WithPrefixedUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new UnwrappableType();
source.embeddableValue.stringValue = "string-val"; source.embeddableValue.stringValue = "string-val";
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
source.embeddableValue.atFieldAnnotatedValue = "@Field"; source.embeddableValue.atFieldAnnotatedValue = "@Field";
@ -104,30 +104,30 @@ class MongoTemplateEmbeddedTests {
assertThat( assertThat(
template.findOne(Query.query(where("embeddableValue.stringValue").is(source.embeddableValue.stringValue)), template.findOne(Query.query(where("embeddableValue.stringValue").is(source.embeddableValue.stringValue)),
WithPrefixedEmbedded.class)).isEqualTo(source); WithPrefixedUnwrapped.class)).isEqualTo(source);
} }
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
static class WithEmbedded { static class WithUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableValue; @Unwrapped.Nullable UnwrappableType embeddableValue;
} }
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
static class WithPrefixedEmbedded { static class WithPrefixedUnwrapped {
String id; String id;
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue; @Unwrapped.Nullable("prefix-") UnwrappableType embeddableValue;
} }
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
static class EmbeddableType { static class UnwrappableType {
String stringValue; String stringValue;
List<String> listValue; List<String> listValue;

81
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java

@ -45,8 +45,8 @@ import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
/** /**
@ -358,11 +358,11 @@ public class TypeBasedAggregationOperationContextUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnEmbeddedFieldCorrectly() { void rendersProjectOnUnwrappableTypeFieldCorrectly() {
AggregationOperationContext context = getContext(WithEmbedded.class); AggregationOperationContext context = getContext(WithUnwrapped.class);
Document agg = newAggregation(WithEmbedded.class, project().and("embeddableType.stringValue").as("val")) Document agg = newAggregation(WithUnwrapped.class, project().and("unwrappedValue.stringValue").as("val"))
.toDocument("collection", context); .toDocument("collection", context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
@ -370,11 +370,11 @@ public class TypeBasedAggregationOperationContextUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnEmbeddedFieldWithAtFieldAnnotationCorrectly() { void rendersProjectOnUnwrappedFieldWithAtFieldAnnotationCorrectly() {
AggregationOperationContext context = getContext(WithEmbedded.class); AggregationOperationContext context = getContext(WithUnwrapped.class);
Document agg = newAggregation(WithEmbedded.class, project().and("embeddableType.atFieldAnnotatedValue").as("val")) Document agg = newAggregation(WithUnwrapped.class, project().and("unwrappedValue.atFieldAnnotatedValue").as("val"))
.toDocument("collection", context); .toDocument("collection", context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
@ -382,11 +382,11 @@ public class TypeBasedAggregationOperationContextUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnPrefixedEmbeddedFieldCorrectly() { void rendersProjectOnPrefixedUnwrappedFieldCorrectly() {
AggregationOperationContext context = getContext(WithEmbedded.class); AggregationOperationContext context = getContext(WithUnwrapped.class);
Document agg = newAggregation(WithEmbedded.class, project().and("prefixedEmbeddableValue.stringValue").as("val")) Document agg = newAggregation(WithUnwrapped.class, project().and("prefixedUnwrappedValue.stringValue").as("val"))
.toDocument("collection", context); .toDocument("collection", context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
@ -394,64 +394,65 @@ public class TypeBasedAggregationOperationContextUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnPrefixedEmbeddedFieldWithAtFieldAnnotationCorrectly() { void rendersProjectOnPrefixedUnwrappedFieldWithAtFieldAnnotationCorrectly() {
AggregationOperationContext context = getContext(WithEmbedded.class); AggregationOperationContext context = getContext(WithUnwrapped.class);
Document agg = newAggregation(WithEmbedded.class, Document agg = newAggregation(WithUnwrapped.class,
project().and("prefixedEmbeddableValue.atFieldAnnotatedValue").as("val")).toDocument("collection", context); project().and("prefixedUnwrappedValue.atFieldAnnotatedValue").as("val")).toDocument("collection", context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
.isEqualTo(new Document("val", "$prefix-with-at-field-annotation")); .isEqualTo(new Document("val", "$prefix-with-at-field-annotation"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnNestedEmbeddedFieldCorrectly() { void rendersProjectOnNestedUnwrappedFieldCorrectly() {
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class); AggregationOperationContext context = getContext(WrapperAroundWithUnwrapped.class);
Document agg = newAggregation(WrapperAroundWithEmbedded.class, Document agg = newAggregation(WrapperAroundWithUnwrapped.class,
project().and("withEmbedded.embeddableType.stringValue").as("val")).toDocument("collection", context); project().and("withUnwrapped.unwrappedValue.stringValue").as("val")).toDocument("collection", context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
.isEqualTo(new Document("val", "$withEmbedded.stringValue")); .isEqualTo(new Document("val", "$withUnwrapped.stringValue"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnNestedEmbeddedFieldWithAtFieldAnnotationCorrectly() { void rendersProjectOnNestedUnwrappedFieldWithAtFieldAnnotationCorrectly() {
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class); AggregationOperationContext context = getContext(WrapperAroundWithUnwrapped.class);
Document agg = newAggregation(WrapperAroundWithEmbedded.class, Document agg = newAggregation(WrapperAroundWithUnwrapped.class,
project().and("withEmbedded.embeddableType.atFieldAnnotatedValue").as("val")).toDocument("collection", context); project().and("withUnwrapped.unwrappedValue.atFieldAnnotatedValue").as("val")).toDocument("collection",
context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
.isEqualTo(new Document("val", "$withEmbedded.with-at-field-annotation")); .isEqualTo(new Document("val", "$withUnwrapped.with-at-field-annotation"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnNestedPrefixedEmbeddedFieldCorrectly() { void rendersProjectOnNestedPrefixedUnwrappedFieldCorrectly() {
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class); AggregationOperationContext context = getContext(WrapperAroundWithUnwrapped.class);
Document agg = newAggregation(WrapperAroundWithEmbedded.class, Document agg = newAggregation(WrapperAroundWithUnwrapped.class,
project().and("withEmbedded.prefixedEmbeddableValue.stringValue").as("val")).toDocument("collection", context); project().and("withUnwrapped.prefixedUnwrappedValue.stringValue").as("val")).toDocument("collection", context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
.isEqualTo(new Document("val", "$withEmbedded.prefix-stringValue")); .isEqualTo(new Document("val", "$withUnwrapped.prefix-stringValue"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersProjectOnNestedPrefixedEmbeddedFieldWithAtFieldAnnotationCorrectly() { void rendersProjectOnNestedPrefixedUnwrappedFieldWithAtFieldAnnotationCorrectly() {
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class); AggregationOperationContext context = getContext(WrapperAroundWithUnwrapped.class);
Document agg = newAggregation(WrapperAroundWithEmbedded.class, Document agg = newAggregation(WrapperAroundWithUnwrapped.class,
project().and("withEmbedded.prefixedEmbeddableValue.atFieldAnnotatedValue").as("val")).toDocument("collection", project().and("withUnwrapped.prefixedUnwrappedValue.atFieldAnnotatedValue").as("val")).toDocument("collection",
context); context);
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project")) assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
.isEqualTo(new Document("val", "$withEmbedded.prefix-with-at-field-annotation")); .isEqualTo(new Document("val", "$withUnwrapped.prefix-with-at-field-annotation"));
} }
@org.springframework.data.mongodb.core.mapping.Document(collection = "person") @org.springframework.data.mongodb.core.mapping.Document(collection = "person")
@ -531,21 +532,21 @@ public class TypeBasedAggregationOperationContextUnitTests {
@org.springframework.data.mongodb.core.mapping.Field("nestedValue2") String value2; @org.springframework.data.mongodb.core.mapping.Field("nestedValue2") String value2;
} }
static class WrapperAroundWithEmbedded { static class WrapperAroundWithUnwrapped {
String id; String id;
WithEmbedded withEmbedded; WithUnwrapped withUnwrapped;
} }
static class WithEmbedded { static class WithUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableType; @Unwrapped.Nullable UnwrappableType unwrappedValue;
@Embedded.Nullable("prefix-") EmbeddableType prefixedEmbeddableValue; @Unwrapped.Nullable("prefix-") UnwrappableType prefixedUnwrappedValue;
} }
static class EmbeddableType { static class UnwrappableType {
String stringValue; String stringValue;

68
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

@ -72,13 +72,13 @@ import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.P
import org.springframework.data.mongodb.core.convert.MappingMongoConverterUnitTests.ClassWithMapUsingEnumAsKey.FooBarEnum; import org.springframework.data.mongodb.core.convert.MappingMongoConverterUnitTests.ClassWithMapUsingEnumAsKey.FooBarEnum;
import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.geo.Sphere;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType; import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId; import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
import org.springframework.data.mongodb.core.mapping.TextScore; import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback; import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
@ -2193,9 +2193,9 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void writeFlattensEmbeddedType() { void writeFlattensUnwrappedType() {
WithNullableEmbedded source = new WithNullableEmbedded(); WithNullableUnwrapped source = new WithNullableUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new EmbeddableType();
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
@ -2215,9 +2215,9 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void writePrefixesEmbeddedType() { void writePrefixesUnwrappedType() {
WithPrefixedNullableEmbedded source = new WithPrefixedNullableEmbedded(); WithPrefixedNullableUnwrapped source = new WithPrefixedNullableUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new EmbeddableType();
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
@ -2238,9 +2238,9 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void writeNullEmbeddedType() { void writeNullUnwrappedType() {
WithNullableEmbedded source = new WithNullableEmbedded(); WithNullableUnwrapped source = new WithNullableUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = null; source.embeddableValue = null;
@ -2253,11 +2253,11 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void writeDeepNestedEmbeddedType() { void writeDeepNestedUnwrappedType() {
WrapperAroundWithEmbedded source = new WrapperAroundWithEmbedded(); WrapperAroundWithUnwrapped source = new WrapperAroundWithUnwrapped();
source.someValue = "root-level-value"; source.someValue = "root-level-value";
source.nullableEmbedded = new WithNullableEmbedded(); source.nullableEmbedded = new WithNullableUnwrapped();
source.nullableEmbedded.id = "id-1"; source.nullableEmbedded.id = "id-1";
source.nullableEmbedded.embeddableValue = new EmbeddableType(); source.nullableEmbedded.embeddableValue = new EmbeddableType();
source.nullableEmbedded.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); source.nullableEmbedded.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
@ -2275,7 +2275,7 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readEmbeddedType() { void readUnwrappedType() {
org.bson.Document source = new org.bson.Document("_id", "id-1") // org.bson.Document source = new org.bson.Document("_id", "id-1") //
.append("stringValue", "string-val") // .append("stringValue", "string-val") //
@ -2287,12 +2287,12 @@ class MappingMongoConverterUnitTests {
embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
embeddableValue.atFieldAnnotatedValue = "@Field"; embeddableValue.atFieldAnnotatedValue = "@Field";
WithNullableEmbedded target = converter.read(WithNullableEmbedded.class, source); WithNullableUnwrapped target = converter.read(WithNullableUnwrapped.class, source);
assertThat(target.embeddableValue).isEqualTo(embeddableValue); assertThat(target.embeddableValue).isEqualTo(embeddableValue);
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readPrefixedEmbeddedType() { void readPrefixedUnwrappedType() {
org.bson.Document source = new org.bson.Document("_id", "id-1") // org.bson.Document source = new org.bson.Document("_id", "id-1") //
.append("prefix-stringValue", "string-val") // .append("prefix-stringValue", "string-val") //
@ -2304,37 +2304,37 @@ class MappingMongoConverterUnitTests {
embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2"); embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
embeddableValue.atFieldAnnotatedValue = "@Field"; embeddableValue.atFieldAnnotatedValue = "@Field";
WithPrefixedNullableEmbedded target = converter.read(WithPrefixedNullableEmbedded.class, source); WithPrefixedNullableUnwrapped target = converter.read(WithPrefixedNullableUnwrapped.class, source);
assertThat(target.embeddableValue).isEqualTo(embeddableValue); assertThat(target.embeddableValue).isEqualTo(embeddableValue);
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readNullableEmbeddedTypeWhenSourceDoesNotContainValues() { void readNullableUnwrappedTypeWhenSourceDoesNotContainValues() {
org.bson.Document source = new org.bson.Document("_id", "id-1"); org.bson.Document source = new org.bson.Document("_id", "id-1");
WithNullableEmbedded target = converter.read(WithNullableEmbedded.class, source); WithNullableUnwrapped target = converter.read(WithNullableUnwrapped.class, source);
assertThat(target.embeddableValue).isNull(); assertThat(target.embeddableValue).isNull();
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readEmptyEmbeddedTypeWhenSourceDoesNotContainValues() { void readEmptyUnwrappedTypeWhenSourceDoesNotContainValues() {
org.bson.Document source = new org.bson.Document("_id", "id-1"); org.bson.Document source = new org.bson.Document("_id", "id-1");
WithEmptyEmbeddedType target = converter.read(WithEmptyEmbeddedType.class, source); WithEmptyUnwrappedType target = converter.read(WithEmptyUnwrappedType.class, source);
assertThat(target.embeddableValue).isNotNull(); assertThat(target.embeddableValue).isNotNull();
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readDeepNestedEmbeddedType() { void readDeepNestedUnwrappedType() {
org.bson.Document source = new org.bson.Document("someValue", "root-level-value").append("nullableEmbedded", org.bson.Document source = new org.bson.Document("someValue", "root-level-value").append("nullableEmbedded",
new org.bson.Document("_id", "id-1").append("stringValue", "string-val") // new org.bson.Document("_id", "id-1").append("stringValue", "string-val") //
.append("listValue", Arrays.asList("list-val-1", "list-val-2")) // .append("listValue", Arrays.asList("list-val-1", "list-val-2")) //
.append("with-at-field-annotation", "@Field")); .append("with-at-field-annotation", "@Field"));
WrapperAroundWithEmbedded target = converter.read(WrapperAroundWithEmbedded.class, source); WrapperAroundWithUnwrapped target = converter.read(WrapperAroundWithUnwrapped.class, source);
EmbeddableType embeddableValue = new EmbeddableType(); EmbeddableType embeddableValue = new EmbeddableType();
embeddableValue.stringValue = "string-val"; embeddableValue.stringValue = "string-val";
@ -2347,12 +2347,12 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void readEmbeddedTypeWithComplexValue() { void readUnwrappedTypeWithComplexValue() {
org.bson.Document source = new org.bson.Document("_id", "id-1").append("address", org.bson.Document source = new org.bson.Document("_id", "id-1").append("address",
new org.bson.Document("street", "1007 Mountain Drive").append("city", "Gotham")); new org.bson.Document("street", "1007 Mountain Drive").append("city", "Gotham"));
WithNullableEmbedded target = converter.read(WithNullableEmbedded.class, source); WithNullableUnwrapped target = converter.read(WithNullableUnwrapped.class, source);
Address expected = new Address(); Address expected = new Address();
expected.city = "Gotham"; expected.city = "Gotham";
@ -2363,9 +2363,9 @@ class MappingMongoConverterUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void writeEmbeddedTypeWithComplexValue() { void writeUnwrappedTypeWithComplexValue() {
WithNullableEmbedded source = new WithNullableEmbedded(); WithNullableUnwrapped source = new WithNullableUnwrapped();
source.id = "id-1"; source.id = "id-1";
source.embeddableValue = new EmbeddableType(); source.embeddableValue = new EmbeddableType();
source.embeddableValue.address = new Address(); source.embeddableValue.address = new Address();
@ -2891,33 +2891,33 @@ class MappingMongoConverterUnitTests {
Date dateAsObjectId; Date dateAsObjectId;
} }
static class WrapperAroundWithEmbedded { static class WrapperAroundWithUnwrapped {
String someValue; String someValue;
WithNullableEmbedded nullableEmbedded; WithNullableUnwrapped nullableEmbedded;
WithEmptyEmbeddedType emptyEmbedded; WithEmptyUnwrappedType emptyEmbedded;
WithPrefixedNullableEmbedded prefixedEmbedded; WithPrefixedNullableUnwrapped prefixedEmbedded;
} }
static class WithNullableEmbedded { static class WithNullableUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableValue; @Unwrapped.Nullable EmbeddableType embeddableValue;
} }
static class WithPrefixedNullableEmbedded { static class WithPrefixedNullableUnwrapped {
String id; String id;
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue; @Unwrapped.Nullable("prefix-") EmbeddableType embeddableValue;
} }
static class WithEmptyEmbeddedType { static class WithEmptyUnwrappedType {
String id; String id;
@Embedded.Empty EmbeddableType embeddableValue; @Unwrapped.Empty EmbeddableType embeddableValue;
} }
@EqualsAndHashCode @EqualsAndHashCode

66
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java

@ -43,9 +43,9 @@ import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.WithDB
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher; import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
@ -456,12 +456,12 @@ public class MongoExampleMapperUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mapsEmbeddedType() { void mapsUnwrappedType() {
WithEmbedded probe = new WithEmbedded(); WithUnwrapped probe = new WithUnwrapped();
probe.embeddableType = new EmbeddableType(); probe.unwrappedValue = new UnwrappableType();
probe.embeddableType.atFieldAnnotatedValue = "@Field"; probe.unwrappedValue.atFieldAnnotatedValue = "@Field";
probe.embeddableType.stringValue = "string-value"; probe.unwrappedValue.stringValue = "string-value";
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching())); org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
assertThat(document).containsEntry("stringValue", "string-value").containsEntry("with-at-field-annotation", assertThat(document).containsEntry("stringValue", "string-value").containsEntry("with-at-field-annotation",
@ -469,12 +469,12 @@ public class MongoExampleMapperUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mapsPrefixedEmbeddedType() { void mapsPrefixedUnwrappedType() {
WithEmbedded probe = new WithEmbedded(); WithUnwrapped probe = new WithUnwrapped();
probe.prefixedEmbeddableValue = new EmbeddableType(); probe.prefixedUnwrappedValue = new UnwrappableType();
probe.prefixedEmbeddableValue.atFieldAnnotatedValue = "@Field"; probe.prefixedUnwrappedValue.atFieldAnnotatedValue = "@Field";
probe.prefixedEmbeddableValue.stringValue = "string-value"; probe.prefixedUnwrappedValue.stringValue = "string-value";
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching())); org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
assertThat(document).containsEntry("prefix-stringValue", "string-value") assertThat(document).containsEntry("prefix-stringValue", "string-value")
@ -482,31 +482,31 @@ public class MongoExampleMapperUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mapsNestedEmbeddedType() { void mapsNestedUnwrappedType() {
WrapperAroundWithEmbedded probe = new WrapperAroundWithEmbedded(); WrapperAroundWithUnwrapped probe = new WrapperAroundWithUnwrapped();
probe.withEmbedded = new WithEmbedded(); probe.withUnwrapped = new WithUnwrapped();
probe.withEmbedded.embeddableType = new EmbeddableType(); probe.withUnwrapped.unwrappedValue = new UnwrappableType();
probe.withEmbedded.embeddableType.atFieldAnnotatedValue = "@Field"; probe.withUnwrapped.unwrappedValue.atFieldAnnotatedValue = "@Field";
probe.withEmbedded.embeddableType.stringValue = "string-value"; probe.withUnwrapped.unwrappedValue.stringValue = "string-value";
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching())); org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
assertThat(document).containsEntry("withEmbedded.stringValue", "string-value") assertThat(document).containsEntry("withUnwrapped.stringValue", "string-value")
.containsEntry("withEmbedded.with-at-field-annotation", "@Field"); .containsEntry("withUnwrapped.with-at-field-annotation", "@Field");
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mapsNestedPrefixedEmbeddedType() { void mapsNestedPrefixedUnwrappedType() {
WrapperAroundWithEmbedded probe = new WrapperAroundWithEmbedded(); WrapperAroundWithUnwrapped probe = new WrapperAroundWithUnwrapped();
probe.withEmbedded = new WithEmbedded(); probe.withUnwrapped = new WithUnwrapped();
probe.withEmbedded.prefixedEmbeddableValue = new EmbeddableType(); probe.withUnwrapped.prefixedUnwrappedValue = new UnwrappableType();
probe.withEmbedded.prefixedEmbeddableValue.atFieldAnnotatedValue = "@Field"; probe.withUnwrapped.prefixedUnwrappedValue.atFieldAnnotatedValue = "@Field";
probe.withEmbedded.prefixedEmbeddableValue.stringValue = "string-value"; probe.withUnwrapped.prefixedUnwrappedValue.stringValue = "string-value";
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching())); org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
assertThat(document).containsEntry("withEmbedded.prefix-stringValue", "string-value") assertThat(document).containsEntry("withUnwrapped.prefix-stringValue", "string-value")
.containsEntry("withEmbedded.prefix-with-at-field-annotation", "@Field"); .containsEntry("withUnwrapped.prefix-with-at-field-annotation", "@Field");
} }
static class FlatDocument { static class FlatDocument {
@ -538,22 +538,22 @@ public class MongoExampleMapperUnitTests {
} }
@Document @Document
static class WrapperAroundWithEmbedded { static class WrapperAroundWithUnwrapped {
String id; String id;
WithEmbedded withEmbedded; WithUnwrapped withUnwrapped;
} }
@Document @Document
static class WithEmbedded { static class WithUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableType; @Unwrapped.Nullable UnwrappableType unwrappedValue;
@Embedded.Nullable("prefix-") EmbeddableType prefixedEmbeddableValue; @Unwrapped.Nullable("prefix-") UnwrappableType prefixedUnwrappedValue;
} }
static class EmbeddableType { static class UnwrappableType {
@Indexed String stringValue; @Indexed String stringValue;

132
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java

@ -48,12 +48,12 @@ import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType; import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.TextScore; import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
@ -1014,179 +1014,179 @@ public class QueryMapperUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnEmbeddedObjectCorrectly() { void rendersQueryOnUnwrappedObjectCorrectly() {
EmbeddableType embeddableType = new EmbeddableType(); UnwrappableType unwrappableType = new UnwrappableType();
embeddableType.stringValue = "test"; unwrappableType.stringValue = "test";
Query source = query(Criteria.where("embeddableValue").is(embeddableType)); Query source = query(Criteria.where("unwrappedValue").is(unwrappableType));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(target).isEqualTo(new org.bson.Document("stringValue", "test")); assertThat(target).isEqualTo(new org.bson.Document("stringValue", "test"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnEmbeddedCorrectly() { void rendersQueryOnUnwrappedCorrectly() {
Query source = query(Criteria.where("embeddableValue.stringValue").is("test")); Query source = query(Criteria.where("unwrappedValue.stringValue").is("test"));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(target).isEqualTo(new org.bson.Document("stringValue", "test")); assertThat(target).isEqualTo(new org.bson.Document("stringValue", "test"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnPrefixedEmbeddedCorrectly() { void rendersQueryOnPrefixedUnwrappedCorrectly() {
Query source = query(Criteria.where("embeddableValue.stringValue").is("test")); Query source = query(Criteria.where("unwrappedValue.stringValue").is("test"));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WithPrefixedEmbedded.class)); context.getPersistentEntity(WithPrefixedUnwrapped.class));
assertThat(target).isEqualTo(new org.bson.Document("prefix-stringValue", "test")); assertThat(target).isEqualTo(new org.bson.Document("prefix-stringValue", "test"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnNestedEmbeddedObjectCorrectly() { void rendersQueryOnNestedUnwrappedObjectCorrectly() {
EmbeddableType embeddableType = new EmbeddableType(); UnwrappableType unwrappableType = new UnwrappableType();
embeddableType.stringValue = "test"; unwrappableType.stringValue = "test";
Query source = query(Criteria.where("withEmbedded.embeddableValue").is(embeddableType)); Query source = query(Criteria.where("withUnwrapped.unwrappedValue").is(unwrappableType));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(target).isEqualTo(new org.bson.Document("withEmbedded", new org.bson.Document("stringValue", "test"))); assertThat(target).isEqualTo(new org.bson.Document("withUnwrapped", new org.bson.Document("stringValue", "test")));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnNestedPrefixedEmbeddedObjectCorrectly() { void rendersQueryOnNestedPrefixedUnwrappedObjectCorrectly() {
EmbeddableType embeddableType = new EmbeddableType(); UnwrappableType unwrappableType = new UnwrappableType();
embeddableType.stringValue = "test"; unwrappableType.stringValue = "test";
Query source = query(Criteria.where("withPrefixedEmbedded.embeddableValue").is(embeddableType)); Query source = query(Criteria.where("withPrefixedUnwrapped.unwrappedValue").is(unwrappableType));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(target) assertThat(target)
.isEqualTo(new org.bson.Document("withPrefixedEmbedded", new org.bson.Document("prefix-stringValue", "test"))); .isEqualTo(new org.bson.Document("withPrefixedUnwrapped", new org.bson.Document("prefix-stringValue", "test")));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnNestedEmbeddedCorrectly() { void rendersQueryOnNestedUnwrappedCorrectly() {
Query source = query(Criteria.where("withEmbedded.embeddableValue.stringValue").is("test")); Query source = query(Criteria.where("withUnwrapped.unwrappedValue.stringValue").is("test"));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(target).isEqualTo(new org.bson.Document("withEmbedded.stringValue", "test")); assertThat(target).isEqualTo(new org.bson.Document("withUnwrapped.stringValue", "test"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void rendersQueryOnNestedPrefixedEmbeddedCorrectly() { void rendersQueryOnNestedPrefixedUnwrappedCorrectly() {
Query source = query(Criteria.where("withPrefixedEmbedded.embeddableValue.stringValue").is("test")); Query source = query(Criteria.where("withPrefixedUnwrapped.unwrappedValue.stringValue").is("test"));
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(), org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(target).isEqualTo(new org.bson.Document("withPrefixedEmbedded.prefix-stringValue", "test")); assertThat(target).isEqualTo(new org.bson.Document("withPrefixedUnwrapped.prefix-stringValue", "test"));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void sortByEmbeddableIsEmpty() { void sortByUnwrappedIsEmpty() {
Query query = new Query().with(Sort.by("embeddableValue")); Query query = new Query().with(Sort.by("unwrappedValue"));
org.bson.Document document = mapper.getMappedSort(query.getSortObject(), org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(document).isEqualTo( assertThat(document).isEqualTo(
new org.bson.Document("stringValue", 1).append("listValue", 1).append("with-at-field-annotation", 1)); new org.bson.Document("stringValue", 1).append("listValue", 1).append("with-at-field-annotation", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void sortByEmbeddableValue() { void sortByUnwrappedValue() {
// atFieldAnnotatedValue // atFieldAnnotatedValue
Query query = new Query().with(Sort.by("embeddableValue.stringValue")); Query query = new Query().with(Sort.by("unwrappedValue.stringValue"));
org.bson.Document document = mapper.getMappedSort(query.getSortObject(), org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(document).isEqualTo(new org.bson.Document("stringValue", 1)); assertThat(document).isEqualTo(new org.bson.Document("stringValue", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void sortByEmbeddableValueWithFieldAnnotation() { void sortByUnwrappedValueWithFieldAnnotation() {
Query query = new Query().with(Sort.by("embeddableValue.atFieldAnnotatedValue")); Query query = new Query().with(Sort.by("unwrappedValue.atFieldAnnotatedValue"));
org.bson.Document document = mapper.getMappedSort(query.getSortObject(), org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(document).isEqualTo(new org.bson.Document("with-at-field-annotation", 1)); assertThat(document).isEqualTo(new org.bson.Document("with-at-field-annotation", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void sortByPrefixedEmbeddableValueWithFieldAnnotation() { void sortByPrefixedUnwrappedValueWithFieldAnnotation() {
Query query = new Query().with(Sort.by("embeddableValue.atFieldAnnotatedValue")); Query query = new Query().with(Sort.by("unwrappedValue.atFieldAnnotatedValue"));
org.bson.Document document = mapper.getMappedSort(query.getSortObject(), org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
context.getPersistentEntity(WithPrefixedEmbedded.class)); context.getPersistentEntity(WithPrefixedUnwrapped.class));
assertThat(document).isEqualTo(new org.bson.Document("prefix-with-at-field-annotation", 1)); assertThat(document).isEqualTo(new org.bson.Document("prefix-with-at-field-annotation", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void sortByNestedEmbeddableValueWithFieldAnnotation() { void sortByNestedUnwrappedValueWithFieldAnnotation() {
Query query = new Query().with(Sort.by("withEmbedded.embeddableValue.atFieldAnnotatedValue")); Query query = new Query().with(Sort.by("withUnwrapped.unwrappedValue.atFieldAnnotatedValue"));
org.bson.Document document = mapper.getMappedSort(query.getSortObject(), org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(document).isEqualTo(new org.bson.Document("withEmbedded.with-at-field-annotation", 1)); assertThat(document).isEqualTo(new org.bson.Document("withUnwrapped.with-at-field-annotation", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void sortByNestedPrefixedEmbeddableValueWithFieldAnnotation() { void sortByNestedPrefixedUnwrappedValueWithFieldAnnotation() {
Query query = new Query().with(Sort.by("withPrefixedEmbedded.embeddableValue.atFieldAnnotatedValue")); Query query = new Query().with(Sort.by("withPrefixedUnwrapped.unwrappedValue.atFieldAnnotatedValue"));
org.bson.Document document = mapper.getMappedSort(query.getSortObject(), org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(document).isEqualTo(new org.bson.Document("withPrefixedEmbedded.prefix-with-at-field-annotation", 1)); assertThat(document).isEqualTo(new org.bson.Document("withPrefixedUnwrapped.prefix-with-at-field-annotation", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void projectOnEmbeddableUsesFields() { void projectOnUnwrappedUsesFields() {
Query query = new Query(); Query query = new Query();
query.fields().include("embeddableValue"); query.fields().include("unwrappedValue");
org.bson.Document document = mapper.getMappedFields(query.getFieldsObject(), org.bson.Document document = mapper.getMappedFields(query.getFieldsObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(document).isEqualTo( assertThat(document).isEqualTo(
new org.bson.Document("stringValue", 1).append("listValue", 1).append("with-at-field-annotation", 1)); new org.bson.Document("stringValue", 1).append("listValue", 1).append("with-at-field-annotation", 1));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void projectOnEmbeddableValue() { void projectOnUnwrappedValue() {
Query query = new Query(); Query query = new Query();
query.fields().include("embeddableValue.stringValue"); query.fields().include("unwrappedValue.stringValue");
org.bson.Document document = mapper.getMappedFields(query.getFieldsObject(), org.bson.Document document = mapper.getMappedFields(query.getFieldsObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(document).isEqualTo(new org.bson.Document("stringValue", 1)); assertThat(document).isEqualTo(new org.bson.Document("stringValue", 1));
} }
@ -1375,28 +1375,28 @@ public class QueryMapperUnitTests {
} }
} }
static class WrapperAroundWithEmbedded { static class WrapperAroundWithUnwrapped {
String someValue; String someValue;
WithEmbedded withEmbedded; WithUnwrapped withUnwrapped;
WithPrefixedEmbedded withPrefixedEmbedded; WithPrefixedUnwrapped withPrefixedUnwrapped;
} }
static class WithEmbedded { static class WithUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableValue; @Unwrapped.Nullable UnwrappableType unwrappedValue;
} }
static class WithPrefixedEmbedded { static class WithPrefixedUnwrapped {
String id; String id;
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue; @Unwrapped.Nullable("prefix-") UnwrappableType unwrappedValue;
} }
static class EmbeddableType { static class UnwrappableType {
String stringValue; String stringValue;
List<String> listValue; List<String> listValue;

74
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

@ -48,9 +48,9 @@ import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.core.query.Update;
@ -1091,71 +1091,71 @@ class UpdateMapperUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mappingShouldConsiderValueOfEmbeddedType() { void mappingShouldConsiderValueOfUnwrappedType() {
Update update = new Update().set("embeddableValue.stringValue", "updated"); Update update = new Update().set("unwrappedValue.stringValue", "updated");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("stringValue", "updated"))); assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("stringValue", "updated")));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mappingShouldConsiderEmbeddedType() { void mappingShouldConsiderUnwrappedType() {
EmbeddableType embeddableType = new EmbeddableType(); UnwrappableType unwrappableType = new UnwrappableType();
embeddableType.stringValue = "updated"; unwrappableType.stringValue = "updated";
embeddableType.listValue = Arrays.asList("val-1", "val-2"); unwrappableType.listValue = Arrays.asList("val-1", "val-2");
Update update = new Update().set("embeddableValue", embeddableType); Update update = new Update().set("unwrappedValue", unwrappableType);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithEmbedded.class)); context.getPersistentEntity(WithUnwrapped.class));
assertThat(mappedUpdate).isEqualTo(new Document("$set", assertThat(mappedUpdate).isEqualTo(new Document("$set",
new Document("stringValue", "updated").append("listValue", Arrays.asList("val-1", "val-2")))); new Document("stringValue", "updated").append("listValue", Arrays.asList("val-1", "val-2"))));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mappingShouldConsiderValueOfPrefixedEmbeddedType() { void mappingShouldConsiderValueOfPrefixedUnwrappedType() {
Update update = new Update().set("embeddableValue.stringValue", "updated"); Update update = new Update().set("unwrappedValue.stringValue", "updated");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithPrefixedEmbedded.class)); context.getPersistentEntity(WithPrefixedUnwrapped.class));
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("prefix-stringValue", "updated"))); assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("prefix-stringValue", "updated")));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mappingShouldConsiderPrefixedEmbeddedType() { void mappingShouldConsiderPrefixedUnwrappedType() {
EmbeddableType embeddableType = new EmbeddableType(); UnwrappableType unwrappableType = new UnwrappableType();
embeddableType.stringValue = "updated"; unwrappableType.stringValue = "updated";
embeddableType.listValue = Arrays.asList("val-1", "val-2"); unwrappableType.listValue = Arrays.asList("val-1", "val-2");
Update update = new Update().set("embeddableValue", embeddableType); Update update = new Update().set("unwrappedValue", unwrappableType);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithPrefixedEmbedded.class)); context.getPersistentEntity(WithPrefixedUnwrapped.class));
assertThat(mappedUpdate).isEqualTo(new Document("$set", assertThat(mappedUpdate).isEqualTo(new Document("$set",
new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2")))); new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2"))));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void mappingShouldConsiderNestedPrefixedEmbeddedType() { void mappingShouldConsiderNestedPrefixedUnwrappedType() {
EmbeddableType embeddableType = new EmbeddableType(); UnwrappableType unwrappableType = new UnwrappableType();
embeddableType.stringValue = "updated"; unwrappableType.stringValue = "updated";
embeddableType.listValue = Arrays.asList("val-1", "val-2"); unwrappableType.listValue = Arrays.asList("val-1", "val-2");
Update update = new Update().set("withPrefixedEmbedded.embeddableValue", embeddableType); Update update = new Update().set("withPrefixedUnwrapped.unwrappedValue", unwrappableType);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class)); context.getPersistentEntity(WrapperAroundWithUnwrapped.class));
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("withPrefixedEmbedded", assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("withPrefixedUnwrapped",
new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2"))))); new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2")))));
} }
@ -1176,8 +1176,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class)); context.getPersistentEntity(EntityWithObjectMap.class));
assertThat(mappedUpdate) assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"map.601218778970110001827396.value\": \"testing\"}}");
.isEqualTo("{\"$set\": {\"map.601218778970110001827396.value\": \"testing\"}}");
} }
@Test // GH-3566 @Test // GH-3566
@ -1187,8 +1186,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class)); context.getPersistentEntity(EntityWithObjectMap.class));
assertThat(mappedUpdate) assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"map.class\": \"value\"}}");
.isEqualTo("{\"$set\": {\"map.class\": \"value\"}}");
} }
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes { static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
@ -1520,28 +1518,28 @@ class UpdateMapperUnitTests {
} }
static class WrapperAroundWithEmbedded { static class WrapperAroundWithUnwrapped {
String someValue; String someValue;
WithEmbedded withEmbedded; WithUnwrapped withUnwrapped;
WithPrefixedEmbedded withPrefixedEmbedded; WithPrefixedUnwrapped withPrefixedUnwrapped;
} }
static class WithEmbedded { static class WithUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableValue; @Unwrapped.Nullable UnwrappableType unwrappedValue;
} }
static class WithPrefixedEmbedded { static class WithPrefixedUnwrapped {
String id; String id;
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue; @Unwrapped.Nullable("prefix-") UnwrappableType unwrappedValue;
} }
static class EmbeddableType { static class UnwrappableType {
String stringValue; String stringValue;
List<String> listValue; List<String> listValue;

30
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java

@ -44,12 +44,12 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexRes
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.Language; import org.springframework.data.mongodb.core.mapping.Language;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
/** /**
@ -1286,10 +1286,10 @@ public class MongoPersistentEntityIndexResolverUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
public void resolvedIndexOnEmbeddedType() { public void resolvedIndexOnUnwrappedType() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(WithEmbedded.class, List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(WithUnwrapped.class,
EmbeddableType.class); UnwrappableType.class);
assertThat(indexDefinitions).hasSize(2); assertThat(indexDefinitions).hasSize(2);
assertThat(indexDefinitions.get(0)).satisfies(it -> { assertThat(indexDefinitions.get(0)).satisfies(it -> {
@ -1301,10 +1301,10 @@ public class MongoPersistentEntityIndexResolverUnitTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
public void resolvedIndexOnNestedEmbeddedType() { public void resolvedIndexOnNestedUnwrappedType() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
WrapperAroundWithEmbedded.class, WithEmbedded.class, EmbeddableType.class); WrapperAroundWithUnwrapped.class, WithUnwrapped.class, UnwrappableType.class);
assertThat(indexDefinitions).hasSize(2); assertThat(indexDefinitions).hasSize(2);
assertThat(indexDefinitions.get(0)).satisfies(it -> { assertThat(indexDefinitions.get(0)).satisfies(it -> {
@ -1319,7 +1319,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
public void errorsOnIndexOnEmbedded() { public void errorsOnIndexOnEmbedded() {
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(() -> prepareMappingContextAndResolveIndexForType(InvalidIndexOnEmbedded.class)); .isThrownBy(() -> prepareMappingContextAndResolveIndexForType(InvalidIndexOnUnwrapped.class));
} }
@ -1514,30 +1514,30 @@ public class MongoPersistentEntityIndexResolverUnitTests {
} }
@Document @Document
static class WrapperAroundWithEmbedded { static class WrapperAroundWithUnwrapped {
String id; String id;
WithEmbedded withEmbedded; WithUnwrapped withEmbedded;
} }
@Document @Document
static class WithEmbedded { static class WithUnwrapped {
String id; String id;
@Embedded.Nullable EmbeddableType embeddableType; @Unwrapped.Nullable UnwrappableType unwrappableType;
} }
@Document @Document
class InvalidIndexOnEmbedded { class InvalidIndexOnUnwrapped {
@Indexed // @Indexed //
@Embedded.Nullable // @Unwrapped.Nullable //
EmbeddableType embeddableType; UnwrappableType unwrappableType;
} }
static class EmbeddableType { static class UnwrappableType {
@Indexed String stringValue; @Indexed String stringValue;

12
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -1366,34 +1366,34 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void findByValueInsideEmbedded() { void findByValueInsideUnwrapped() {
Person bart = new Person("bart", "simpson"); Person bart = new Person("bart", "simpson");
User user = new User(); User user = new User();
user.setUsername("bartman"); user.setUsername("bartman");
user.setId("84r1m4n"); user.setId("84r1m4n");
bart.setEmbeddedUser(user); bart.setUnwrappedUser(user);
operations.save(bart); operations.save(bart);
List<Person> result = repository.findByEmbeddedUserUsername(user.getUsername()); List<Person> result = repository.findByUnwrappedUserUsername(user.getUsername());
assertThat(result).hasSize(1); assertThat(result).hasSize(1);
assertThat(result.get(0).getId().equals(bart.getId())); assertThat(result.get(0).getId().equals(bart.getId()));
} }
@Test // DATAMONGO-1902 @Test // DATAMONGO-1902
void findByEmbedded() { void findByUnwrapped() {
Person bart = new Person("bart", "simpson"); Person bart = new Person("bart", "simpson");
User user = new User(); User user = new User();
user.setUsername("bartman"); user.setUsername("bartman");
user.setId("84r1m4n"); user.setId("84r1m4n");
bart.setEmbeddedUser(user); bart.setUnwrappedUser(user);
operations.save(bart); operations.save(bart);
List<Person> result = repository.findByEmbeddedUser(user); List<Person> result = repository.findByUnwrappedUser(user);
assertThat(result).hasSize(1); assertThat(result).hasSize(1);
assertThat(result.get(0).getId().equals(bart.getId())); assertThat(result.get(0).getId().equals(bart.getId()));

14
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java

@ -27,8 +27,8 @@ import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Embedded;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
/** /**
* Sample domain class. * Sample domain class.
@ -71,8 +71,8 @@ public class Person extends Contact {
Credentials credentials; Credentials credentials;
@Embedded.Nullable(prefix = "u") // @Unwrapped.Nullable(prefix = "u") //
User embeddedUser; User unwrappedUser;
public Person() { public Person() {
@ -300,12 +300,12 @@ public class Person extends Contact {
return skills; return skills;
} }
public User getEmbeddedUser() { public User getUnwrappedUser() {
return embeddedUser; return unwrappedUser;
} }
public void setEmbeddedUser(User embeddedUser) { public void setUnwrappedUser(User unwrappedUser) {
this.embeddedUser = embeddedUser; this.unwrappedUser = unwrappedUser;
} }
/* /*

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -407,7 +407,7 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
Date createdAt, List<String> skills, String street, String zipCode, // Date createdAt, List<String> skills, String street, String zipCode, //
String city, UUID uniqueId, String username, String password); String city, UUID uniqueId, String username, String password);
List<Person> findByEmbeddedUserUsername(String username); List<Person> findByUnwrappedUserUsername(String username);
List<Person> findByEmbeddedUser(User user); List<Person> findByUnwrappedUser(User user);
} }

2
src/main/asciidoc/new-features.adoc

@ -4,7 +4,7 @@
[[new-features.3.2]] [[new-features.3.2]]
== What's New in Spring Data MongoDB 3.2 == What's New in Spring Data MongoDB 3.2
* Support for <<embedded-entities,Embedded Types>> to unwrap nested objects into the parent `Document`. * Support for <<unwrapped-entities,unwrapping>> nested objects into the parent `Document`.
* <<mongo-template.querying.field-selection,Support expressions to define field projections>>. * <<mongo-template.querying.field-selection,Support expressions to define field projections>>.
[[new-features.3.1]] [[new-features.3.1]]

2
src/main/asciidoc/reference/mapping.adoc

@ -833,6 +833,6 @@ Events are fired throughout the lifecycle of the mapping process. This is descri
Declaring these beans in your Spring ApplicationContext causes them to be invoked whenever the event is dispatched. Declaring these beans in your Spring ApplicationContext causes them to be invoked whenever the event is dispatched.
include::embedded-documents.adoc[] include::unwrapping-entities.adoc[]
include::mongo-custom-conversions.adoc[] include::mongo-custom-conversions.adoc[]

140
src/main/asciidoc/reference/embedded-documents.adoc → src/main/asciidoc/reference/unwrapping-entities.adoc

@ -1,15 +1,15 @@
[[embedded-entities]] [[unwrapped-entities]]
== Embedded Types == Unwrapping Types
Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the parent's MongoDB Document. Unwrapped 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]] [[unwrapped-entities.mapping]]
=== Embedded Types Mapping === Unwrapped Types Mapping
Consider the following domain model where `User.name` is annotated with `@Embedded`. Consider the following domain model where `User.name` is annotated with `@Unwrapped`.
The `@Embedded` annotation signals that all properties of `UserName` should be unwrapped into the `user` document that owns the `name` property. The `@Unwrapped` annotation signals that all properties of `UserName` should be flattened out into the `user` document that owns the `name` property.
.Sample Code of embedding objects .Sample Code of unwrapping objects
==== ====
[source,java] [source,java]
---- ----
@ -18,7 +18,7 @@ class User {
@Id @Id
String userId; String userId;
@Embedded(onEmpty = USE_NULL) <1> @Unwrapped(onEmpty = USE_NULL) <1>
UserName name; UserName name;
} }
@ -43,23 +43,23 @@ class UserName {
By using `onEmpty=USE_EMPTY` an empty `UserName`, with potential `null` value for its properties, will be created. By using `onEmpty=USE_EMPTY` an empty `UserName`, with potential `null` value for its properties, will be created.
==== ====
For less verbose embeddable type declarations use `@Embedded.Nullable` and `@Embedded.Empty` instead `@Embedded(onEmpty = USE_NULL)` and `@Embedded(onEmpty = USE_EMPTY)`. For less verbose embeddable type declarations use `@Unwrapped.Nullable` and `@Unwrapped.Empty` instead `@Unwrapped(onEmpty = USE_NULL)` and `@Unwrapped(onEmpty = USE_EMPTY)`.
Both annotations are meta-annotated with JSR-305 `@javax.annotation.Nonnull` to aid with nullability inspections. Both annotations are meta-annotated with JSR-305 `@javax.annotation.Nonnull` to aid with nullability inspections.
[WARNING] [WARNING]
==== ====
It is possible to use complex types within an embedded object. It is possible to use complex types within an unwrapped object.
However, those must not be, nor contain embedded fields themselves. However, those must not be, nor contain unwrapped fields themselves.
==== ====
[[embedded-entities.mapping.field-names]] [[unwrapped-entities.mapping.field-names]]
=== Embedded Types field names === Unwrapped Types field names
A value object can be embedded multiple times by using the optional `prefix` attribute of the `@Embedded` annotation. A value object can be unwrapped multiple times by using the optional `prefix` attribute of the `@Unwrapped` 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 unwrapped object.
Please note that values will overwrite each other if multiple properties render to the same field name. 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 .Sample Code of unwrapped object with name prefix
==== ====
[source,java] [source,java]
---- ----
@ -68,10 +68,10 @@ class User {
@Id @Id
String userId; String userId;
@Embedded.Nullable(prefix = "u_") <1> @Unwrapped.Nullable(prefix = "u_") <1>
UserName name; UserName name;
@Embedded.Nullable(prefix = "a_") <2> @Unwrapped.Nullable(prefix = "a_") <2>
UserName name; UserName name;
} }
@ -97,10 +97,10 @@ class UserName {
<2> All properties of `UserName` are prefixed with `a_`. <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. While combining the `@Field` annotation with `@Unwrapped` on the very same property does not make sense and therefore leads to an error.
It is a totally valid approach to use `@Field` on any of the embedded types properties. It is a totally valid approach to use `@Field` on any of the unwrapped types properties.
.Sample Code embedded object with `@Field` annotation .Sample Code unwrapping objects with `@Field` annotation
==== ====
[source,java] [source,java]
---- ----
@ -109,7 +109,7 @@ public class User {
@Id @Id
private String userId; private String userId;
@Embedded.Nullable(prefix = "u-") <1> @Unwrapped.Nullable(prefix = "u-") <1>
UserName name; UserName name;
} }
@ -132,17 +132,17 @@ public class UserName {
} }
---- ----
<1> All properties of `UserName` are prefixed with `u-`. <1> All properties of `UserName` are prefixed with `u-`.
<2> Final field names are a result of concatenating `@Embedded(prefix)` and `@Field(name)`. <2> Final field names are a result of concatenating `@Unwrapped(prefix)` and `@Field(name)`.
==== ====
[[embedded-entities.queries]] [[unwrapped-entities.queries]]
=== Query on Embedded Objects === Query on Unwrapped Objects
Defining queries on embedded properties is possible on type- as well as field-level as the provided `Criteria` is matched against the domain type. Defining queries on unwrapped 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. 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. Use the property name of the unwrapped object to match against all contained fields as shown in the sample below.
.Query on embedded object .Query on unwrapped object
==== ====
[source,java] [source,java]
---- ----
@ -160,9 +160,9 @@ db.collection.find({
---- ----
==== ====
It is also possible to address any field of the embedded object directly using its property name as shown in the snippet below. It is also possible to address any field of the unwrapped object directly using its property name as shown in the snippet below.
.Query on field of embedded object .Query on field of unwrapped object
==== ====
[source,java] [source,java]
---- ----
@ -178,12 +178,12 @@ db.collection.find({
---- ----
==== ====
[[embedded-entities.queries.sort]] [[unwrapped-entities.queries.sort]]
==== Sort by embedded field. ==== Sort by unwrapped field.
Fields of embedded objects can be used for sorting via their property path as shown in the sample below. Fields of unwrapped objects can be used for sorting via their property path as shown in the sample below.
.Sort on embedded field .Sort on unwrapped field
==== ====
[source,java] [source,java]
---- ----
@ -201,15 +201,15 @@ db.collection.find({
[NOTE] [NOTE]
==== ====
Though possible, using the embedded object itself as sort criteria includes all of its fields in unpredictable order and may result in inaccurate ordering. Though possible, using the unwrapped object itself as sort criteria includes all of its fields in unpredictable order and may result in inaccurate ordering.
==== ====
[[embedded-entities.queries.project]] [[unwrapped-entities.queries.project]]
==== Field projection on embedded objects ==== Field projection on unwrapped objects
Fields of embedded objects can be subject for projection either as a whole or via single fields as shown in the samples below. Fields of unwrapped objects can be subject for projection either as a whole or via single fields as shown in the samples below.
.Project on embedded object. .Project on unwrapped object.
==== ====
[source,java] [source,java]
---- ----
@ -228,10 +228,10 @@ db.collection.find({
"lastname" : 1 "lastname" : 1
}) })
---- ----
<1> A field projection on an embedded object includes all of its properties. <1> A field projection on an unwrapped object includes all of its properties.
==== ====
.Project on a field of an embedded object. .Project on a field of an unwrapped object.
==== ====
[source,java] [source,java]
---- ----
@ -249,21 +249,21 @@ db.collection.find({
"firstname" : 1 "firstname" : 1
}) })
---- ----
<1> A field projection on an embedded object includes all of its properties. <1> A field projection on an unwrapped object includes all of its properties.
==== ====
[[embedded-entities.queries.by-example]] [[unwrapped-entities.queries.by-example]]
==== Query By Example on embedded object. ==== Query By Example on unwrapped object.
Embedded objects can be used within an `Example` probe just as any other type. Unwrapped objects can be used within an `Example` probe just as any other type.
Please review the <<query-by-example.running,Query By Example>> section, to learn more about this feature. Please review the <<query-by-example.running,Query By Example>> section, to learn more about this feature.
[[embedded-entities.queries.repository]] [[unwrapped-entities.queries.repository]]
==== Repository Queries on embedded objects. ==== Repository Queries on unwrapped objects.
The `Repository` abstraction allows deriving queries on fields of embedded objects as well as the entire object. The `Repository` abstraction allows deriving queries on fields of unwrapped objects as well as the entire object.
.Repository queries on embedded objects. .Repository queries on unwrapped objects.
==== ====
[source,java] [source,java]
---- ----
@ -274,23 +274,23 @@ interface UserRepository extends CrudRepository<User, String> {
List<User> findByNameFirstname(String firstname); <2> List<User> findByNameFirstname(String firstname); <2>
} }
---- ----
<1> Matches against all fields of the embedded object. <1> Matches against all fields of the unwrapped object.
<2> Matches against the `firstname`. <2> Matches against the `firstname`.
==== ====
[NOTE] [NOTE]
==== ====
Index creation for embedded objects is suspended even if the repository `create-query-indexes` namespace attribute is set to `true`. Index creation for unwrapped objects is suspended even if the repository `create-query-indexes` namespace attribute is set to `true`.
==== ====
[[embedded-entities.update]] [[unwrapped-entities.update]]
=== Update on Embedded Objects === Update on Unwrapped Objects
Embedded objects can be updated as any other object that is part of the domain model. Unwrapped objects can be updated as any other object that is part of the domain model.
The mapping layer takes care of flattening embedded structures into their surroundings. The mapping layer takes care of flattening structures into their surroundings.
It is possible to update single attributes of the embedded object as well as the entire value as shown in the examples below. It is possible to update single attributes of the unwrapped object as well as the entire value as shown in the examples below.
.Update a single field of an embedded object. .Update a single field of an unwrapped object.
==== ====
[source,java] [source,java]
---- ----
@ -312,7 +312,7 @@ db.collection.update({
---- ----
==== ====
.Update an embedded object. .Update an unwrapped object.
==== ====
[source,java] [source,java]
---- ----
@ -337,18 +337,18 @@ db.collection.update({
---- ----
==== ====
[[embedded-entities.aggregations]] [[unwrapped-entities.aggregations]]
=== Aggregations on Embedded Objects === Aggregations on Unwrapped Objects
The <<mongo.aggregation,Aggregation Framework>> will attempt to map embedded values of typed aggregations. The <<mongo.aggregation,Aggregation Framework>> will attempt to map unwrapped values of typed aggregations.
Please make sure to work with the property path including the embedded wrapper object when referencing one of its values. Please make sure to work with the property path including the wrapper object when referencing one of its values.
Other than that no special action is required. Other than that no special action is required.
[[embedded-entities.indexes]] [[unwrapped-entities.indexes]]
=== Index on Embedded Objects === Index on Unwrapped Objects
It is possible to attach the `@Indexed` annotation to properties of an embedded type just as it is done with regular objects. It is possible to attach the `@Indexed` annotation to properties of an unwrapped type just as it is done with regular objects.
It is not possible to use `@Indexed` along with the `@Embedded` annotation on the owning property. It is not possible to use `@Indexed` along with the `@Unwrapped` annotation on the owning property.
==== ====
[source,java] [source,java]
@ -358,12 +358,12 @@ public class User {
@Id @Id
private String userId; private String userId;
@Embedded(onEmpty = USE_NULL) @Unwrapped(onEmpty = USE_NULL)
UserName name; <1> UserName name; <1>
// Invalid -> InvalidDataAccessApiUsageException // Invalid -> InvalidDataAccessApiUsageException
@Indexed <2> @Indexed <2>
@Embedded(onEmpty = USE_Empty) @Unwrapped(onEmpty = USE_Empty)
Address address; Address address;
} }
@ -376,7 +376,7 @@ public class UserName {
} }
---- ----
<1> Index created for `lastname` in `users` collection. <1> Index created for `lastname` in `users` collection.
<2> Invalid `@Indexed` usage along with `@Embedded` <2> Invalid `@Indexed` usage along with `@Unwrapped`
==== ====
Loading…
Cancel
Save