Add support for embedded types.
We now support embedded types in the sense of unwrapping nested objects into their parent Document to flatten out domain models where needed.
A domain class of:
public class User {
@Id
private String userId;
@Embedded(onEmpty = USE_NULL)
private UserName name;
}
public class UserName {
private String firstname;
private String lastname;
}
renders:
{
"_id" : "1da2ba06-3ba7",
"firstname" : "Emma",
"lastname" : "Frost"
}
Resolves #2803.
Original pull request: #896.
This commit is contained in:
committed by
Mark Paluch
parent
c1417c4e4b
commit
bd985a6589
@@ -47,7 +47,7 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider {
|
||||
* @see org.springframework.data.mongodb.core.index.IndexOperationsProvider#reactiveIndexOps(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
|
||||
public IndexOperations indexOps(String collectionName, Class<?> type) {
|
||||
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,12 +715,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return indexOps(collectionName, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.String)
|
||||
*/
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(this, collectionName, null);
|
||||
public IndexOperations indexOps(String collectionName, @Nullable Class<?> type) {
|
||||
return new DefaultIndexOperations(this, collectionName, type);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -728,7 +734,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class)
|
||||
*/
|
||||
public IndexOperations indexOps(Class<?> entityClass) {
|
||||
return new DefaultIndexOperations(this, getCollectionName(entityClass), entityClass);
|
||||
return indexOps(getCollectionName(entityClass), entityClass);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -67,6 +67,10 @@ class DocumentAccessor {
|
||||
return this.document;
|
||||
}
|
||||
|
||||
public void putAll(MongoPersistentProperty prop, Document value) {
|
||||
value.entrySet().forEach(entry -> BsonUtils.asMap(document).put(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
|
||||
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist
|
||||
|
||||
@@ -62,6 +62,8 @@ import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
|
||||
import org.springframework.data.mongodb.CodecRegistryProvider;
|
||||
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.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
|
||||
@@ -427,6 +429,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prop.isEmbedded()) {
|
||||
|
||||
accessor.setProperty(prop,
|
||||
readEmbedded(documentAccessor, currentPath, prop, mappingContext.getPersistentEntity(prop)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// We skip the id property since it was already set
|
||||
|
||||
if (entity.isIdProperty(prop)) {
|
||||
@@ -472,6 +481,22 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object readEmbedded(DocumentAccessor documentAccessor, ObjectPath currentPath, MongoPersistentProperty prop,
|
||||
MongoPersistentEntity<?> embeddedEntity) {
|
||||
|
||||
if (prop.findAnnotation(Embedded.class).onEmpty().equals(OnEmpty.USE_EMPTY)) {
|
||||
return read(embeddedEntity, (Document) documentAccessor.getDocument(), currentPath);
|
||||
}
|
||||
|
||||
for (MongoPersistentProperty persistentProperty : embeddedEntity) {
|
||||
if (documentAccessor.hasValue(persistentProperty)) {
|
||||
return read(embeddedEntity, (Document) documentAccessor.getDocument(), currentPath);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.MongoWriter#toDBRef(java.lang.Object, org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
|
||||
@@ -642,6 +667,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
|
||||
TypeInformation<?> type = prop.getTypeInformation();
|
||||
|
||||
if (prop.isEmbedded()) {
|
||||
|
||||
Document target = new Document();
|
||||
writeInternal(obj, target, mappingContext.getPersistentEntity(prop));
|
||||
|
||||
accessor.putAll(prop, target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType.isCollectionLike()) {
|
||||
List<Object> collectionInternal = createCollection(asCollection(obj), prop);
|
||||
accessor.put(prop, collectionInternal);
|
||||
@@ -1352,6 +1386,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return !obj.getClass().equals(typeInformation.getType()) ? newDocument : removeTypeInfo(newDocument, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity entity) {
|
||||
Document newDocument = new Document();
|
||||
writeInternal(obj, newDocument, entity);
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
public List<Object> maybeConvertList(Iterable<?> source, TypeInformation<?> typeInformation) {
|
||||
|
||||
List<Object> newDbl = new ArrayList<>();
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -144,9 +145,9 @@ public interface MongoConverter
|
||||
try {
|
||||
return getConversionService().canConvert(id.getClass(), targetType)
|
||||
? getConversionService().convert(id, targetType)
|
||||
: convertToMongoType(id, null);
|
||||
: convertToMongoType(id, (TypeInformation<?>) null);
|
||||
} catch (ConversionException o_O) {
|
||||
return convertToMongoType(id, null);
|
||||
return convertToMongoType(id,(TypeInformation<?>) null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -43,7 +44,7 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
|
||||
*/
|
||||
@Nullable
|
||||
default Object convertToMongoType(@Nullable Object obj) {
|
||||
return convertToMongoType(obj, null);
|
||||
return convertToMongoType(obj, (TypeInformation<?>) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +58,9 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
|
||||
@Nullable
|
||||
Object convertToMongoType(@Nullable Object obj, @Nullable TypeInformation<?> typeInformation);
|
||||
|
||||
default Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity<?> entity) {
|
||||
return convertToMongoType(obj, entity.getTypeInformation());
|
||||
}
|
||||
/**
|
||||
* Creates a {@link DBRef} to refer to the given object.
|
||||
*
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
|
||||
@@ -140,9 +141,23 @@ public class QueryMapper {
|
||||
try {
|
||||
|
||||
Field field = createPropertyField(entity, key, mappingContext);
|
||||
Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key));
|
||||
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
// TODO: move to dedicated method
|
||||
if (field.getProperty() != null && field.getProperty().isEmbedded()) {
|
||||
|
||||
Object theNestedObject = BsonUtils.get(query, key);
|
||||
Document mappedValue = (Document) getMappedValue(field, theNestedObject);
|
||||
if (!StringUtils.hasText(field.getMappedKey())) {
|
||||
result.putAll(mappedValue);
|
||||
} else {
|
||||
result.put(field.getMappedKey(), mappedValue);
|
||||
}
|
||||
} else {
|
||||
|
||||
Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key));
|
||||
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
} catch (InvalidPersistentPropertyPath invalidPathException) {
|
||||
|
||||
// in case the object has not already been mapped
|
||||
@@ -173,10 +188,16 @@ public class QueryMapper {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
sortObject = filterEmbeddedObjects(sortObject, entity);
|
||||
|
||||
Document mappedSort = new Document();
|
||||
for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
|
||||
|
||||
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
|
||||
if (field.getProperty() != null && field.getProperty().isEmbedded()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mappedSort.put(field.getMappedKey(), entry.getValue());
|
||||
}
|
||||
|
||||
@@ -197,7 +218,9 @@ public class QueryMapper {
|
||||
|
||||
Assert.notNull(fieldsObject, "FieldsObject must not be null!");
|
||||
|
||||
Document mappedFields = fieldsObject.isEmpty() ? new Document() : getMappedObject(fieldsObject, entity);
|
||||
fieldsObject = filterEmbeddedObjects(fieldsObject, entity);
|
||||
|
||||
Document mappedFields = getMappedObject(fieldsObject, entity);
|
||||
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
|
||||
return mappedFields;
|
||||
}
|
||||
@@ -217,6 +240,43 @@ public class QueryMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private Document filterEmbeddedObjects(Document fieldsObject, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (fieldsObject.isEmpty() || entity == null) {
|
||||
return fieldsObject;
|
||||
}
|
||||
|
||||
Document target = new Document();
|
||||
|
||||
for (Entry<String, Object> field : fieldsObject.entrySet()) {
|
||||
|
||||
try {
|
||||
|
||||
PropertyPath path = PropertyPath.from(field.getKey(), entity.getTypeInformation());
|
||||
PersistentPropertyPath<MongoPersistentProperty> persistentPropertyPath = mappingContext
|
||||
.getPersistentPropertyPath(path);
|
||||
MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getLeafProperty();
|
||||
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
|
||||
mappingContext.getPersistentEntity(property)
|
||||
.doWithProperties((PropertyHandler<MongoPersistentProperty>) embedded -> {
|
||||
|
||||
String dotPath = persistentPropertyPath.toDotPath();
|
||||
dotPath = dotPath + (StringUtils.hasText(dotPath) ? "." : "") + embedded.getName();
|
||||
target.put(dotPath, field.getValue());
|
||||
});
|
||||
} else {
|
||||
target.put(field.getKey(), field.getValue());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
target.put(field.getKey(), field.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private Document getMappedTextScoreField(MongoPersistentProperty property) {
|
||||
return new Document(property.getFieldName(), META_TEXT_SCORE);
|
||||
}
|
||||
@@ -497,6 +557,11 @@ public class QueryMapper {
|
||||
*/
|
||||
@Nullable
|
||||
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (entity != null && entity.isEmbedded()) {
|
||||
return converter.convertToMongoType(source, entity);
|
||||
}
|
||||
|
||||
return converter.convertToMongoType(source, entity == null ? null : entity.getTypeInformation());
|
||||
}
|
||||
|
||||
@@ -912,6 +977,7 @@ public class QueryMapper {
|
||||
public TypeInformation<?> getTypeHint() {
|
||||
return ClassTypeInformation.OBJECT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.EmbeddedMongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -132,6 +133,11 @@ public class UpdateMapper extends QueryMapper {
|
||||
*/
|
||||
@Override
|
||||
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if(entity != null && entity.isEmbedded()) {
|
||||
return converter.convertToMongoType(source, entity);
|
||||
}
|
||||
|
||||
return converter.convertToMongoType(source,
|
||||
entity == null ? ClassTypeInformation.OBJECT : getTypeHintForEntity(source, entity));
|
||||
}
|
||||
@@ -158,6 +164,10 @@ public class UpdateMapper extends QueryMapper {
|
||||
return getMappedUpdateModifier(field, rawValue);
|
||||
}
|
||||
|
||||
if(field.getProperty() != null && field.getProperty().isEmbedded()) {
|
||||
System.out.println("here we are: ");
|
||||
}
|
||||
|
||||
return super.getMappedObjectForField(field, rawValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Provider interface to obtain {@link IndexOperations} by MongoDB collection name.
|
||||
*
|
||||
@@ -25,11 +27,23 @@ package org.springframework.data.mongodb.core.index;
|
||||
@FunctionalInterface
|
||||
public interface IndexOperationsProvider {
|
||||
|
||||
/**
|
||||
* Returns the operations that can be performed on indexes
|
||||
*
|
||||
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
|
||||
* @param type the type used for field mapping. Can be {@literal null}.
|
||||
* @return index operations on the named collection
|
||||
* @since 2.5
|
||||
*/
|
||||
IndexOperations indexOps(String collectionName, @Nullable Class<?> type);
|
||||
|
||||
/**
|
||||
* Returns the operations that can be performed on indexes
|
||||
*
|
||||
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
|
||||
* @return index operations on the named collection
|
||||
*/
|
||||
IndexOperations indexOps(String collectionName);
|
||||
default IndexOperations indexOps(String collectionName) {
|
||||
return indexOps(collectionName, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +135,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
try {
|
||||
if (persistentProperty.isEntity()) {
|
||||
indexes.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(),
|
||||
persistentProperty.getFieldName(), Path.of(persistentProperty), root.getCollection(), guard));
|
||||
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
|
||||
persistentProperty.isEmbedded() ? "" : persistentProperty.getFieldName(), Path.of(persistentProperty),
|
||||
root.getCollection(), guard));
|
||||
}
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(
|
||||
@@ -163,7 +164,11 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String dotPath,
|
||||
final Path path, final String collection, final CycleGuard guard) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
|
||||
return resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type), dotPath, path, collection, guard);
|
||||
}
|
||||
|
||||
private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, final String dotPath,
|
||||
final Path path, final String collection, final CycleGuard guard) {
|
||||
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity));
|
||||
@@ -179,14 +184,18 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
private void guardAndPotentiallyAddIndexForProperty(MongoPersistentProperty persistentProperty, String dotPath,
|
||||
Path path, String collection, List<IndexDefinitionHolder> indexes, CycleGuard guard) {
|
||||
|
||||
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + persistentProperty.getFieldName();
|
||||
String propertyDotPath = dotPath;
|
||||
|
||||
if (!persistentProperty.isEmbedded()) {
|
||||
propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + persistentProperty.getFieldName();
|
||||
}
|
||||
|
||||
Path propertyPath = path.append(persistentProperty);
|
||||
guard.protect(persistentProperty, propertyPath);
|
||||
|
||||
if (persistentProperty.isEntity()) {
|
||||
try {
|
||||
indexes.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(), propertyDotPath,
|
||||
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), propertyDotPath,
|
||||
propertyPath, collection, guard));
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.info(e.getMessage());
|
||||
@@ -206,6 +215,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
List<IndexDefinitionHolder> indices = new ArrayList<>(2);
|
||||
|
||||
if (persistentProperty.isEmbedded() && (persistentProperty.isAnnotationPresent(Indexed.class)
|
||||
|| persistentProperty.isAnnotationPresent(HashIndexed.class)
|
||||
|| persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class))) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Index annotation not allowed on embedded object for path '%s'.", dotPath));
|
||||
}
|
||||
|
||||
if (persistentProperty.isAnnotationPresent(Indexed.class)) {
|
||||
indices.add(createIndexDefinition(dotPath, collection, persistentProperty));
|
||||
} else if (persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class)) {
|
||||
@@ -482,7 +498,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||
}
|
||||
|
||||
private PartialIndexFilter evaluatePartialFilter(String filterExpression, PersistentEntity<?,?> entity) {
|
||||
private PartialIndexFilter evaluatePartialFilter(String filterExpression, PersistentEntity<?, ?> entity) {
|
||||
|
||||
Object result = evaluate(filterExpression, getEvaluationContextForProperty(entity));
|
||||
|
||||
@@ -493,7 +509,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return PartialIndexFilter.of(BsonUtils.parse(filterExpression, null));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates {@link HashedIndex} wrapped in {@link IndexDefinitionHolder} out of {@link HashIndexed} for a given
|
||||
* {@link MongoPersistentProperty}.
|
||||
|
||||
@@ -164,7 +164,8 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
@Override
|
||||
public org.springframework.data.mongodb.core.query.Collation getCollation() {
|
||||
|
||||
Object collationValue = collationExpression != null ? collationExpression.getValue(getEvaluationContext(null), String.class)
|
||||
Object collationValue = collationExpression != null
|
||||
? collationExpression.getValue(getEvaluationContext(null), String.class)
|
||||
: this.collation;
|
||||
|
||||
if (collationValue == null) {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.annotation.meta.When;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The annotation to configure a value object as embedded (flattened out) in the target document.
|
||||
* <p />
|
||||
* 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.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
@Documented
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
|
||||
public @interface Embedded {
|
||||
|
||||
/**
|
||||
* Set the load strategy for the embedded object if all contained fields yield {@literal null} values.
|
||||
* <p />
|
||||
* {@link Nullable @Embedded.Nullable} and {@link Empty @Embedded.Empty} offer shortcuts for this.
|
||||
*
|
||||
* @return never {@link} null.
|
||||
*/
|
||||
OnEmpty onEmpty();
|
||||
|
||||
/**
|
||||
* @return prefix for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* Load strategy to be used {@link Embedded#onEmpty()}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
enum OnEmpty {
|
||||
USE_NULL, USE_EMPTY
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for a nullable embedded property.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Embedded.Nullable private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* as alternative to the more verbose
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Embedded(onEmpty = USE_NULL) @javax.annotation.Nonnull(when = When.MAYBE) private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see Embedded#onEmpty()
|
||||
*/
|
||||
@Embedded(onEmpty = OnEmpty.USE_NULL)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@javax.annotation.Nonnull(when = When.MAYBE)
|
||||
@interface Nullable {
|
||||
|
||||
/**
|
||||
* @return prefix for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* @return value for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for an empty embedded property.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Embedded.Empty private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* as alternative to the more verbose
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Embedded(onEmpty = USE_EMPTY) @javax.annotation.Nonnull(when = When.NEVER) private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see Embedded#onEmpty()
|
||||
*/
|
||||
@Embedded(onEmpty = OnEmpty.USE_EMPTY)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@javax.annotation.Nonnull(when = When.NEVER)
|
||||
@interface Empty {
|
||||
|
||||
/**
|
||||
* @return prefix for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* @return value for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String value() default "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class EmbeddedEntityContext {
|
||||
|
||||
private final MongoPersistentProperty property;
|
||||
|
||||
public EmbeddedEntityContext(MongoPersistentProperty property) {
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public MongoPersistentProperty getProperty() {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.mapping.*;
|
||||
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2020/12
|
||||
*/
|
||||
public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
|
||||
|
||||
private EmbeddedEntityContext context;
|
||||
private MongoPersistentEntity<T> delegate;
|
||||
|
||||
public EmbeddedMongoPersistentEntity(MongoPersistentEntity<T> delegate, EmbeddedEntityContext context) {
|
||||
|
||||
this.context = context;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public String getCollection() {
|
||||
return delegate.getCollection();
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return delegate.getLanguage();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MongoPersistentProperty getTextScoreProperty() {
|
||||
return delegate.getTextScoreProperty();
|
||||
}
|
||||
|
||||
public boolean hasTextScoreProperty() {
|
||||
return delegate.hasTextScoreProperty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Collation getCollation() {
|
||||
return delegate.getCollation();
|
||||
}
|
||||
|
||||
public boolean hasCollation() {
|
||||
return delegate.hasCollation();
|
||||
}
|
||||
|
||||
public ShardKey getShardKey() {
|
||||
return delegate.getShardKey();
|
||||
}
|
||||
|
||||
public boolean isSharded() {
|
||||
return delegate.isSharded();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PreferredConstructor<T, MongoPersistentProperty> getPersistenceConstructor() {
|
||||
return delegate.getPersistenceConstructor();
|
||||
}
|
||||
|
||||
public boolean isConstructorArgument(PersistentProperty<?> property) {
|
||||
return delegate.isConstructorArgument(property);
|
||||
}
|
||||
|
||||
public boolean isIdProperty(PersistentProperty<?> property) {
|
||||
return delegate.isIdProperty(property);
|
||||
}
|
||||
|
||||
public boolean isVersionProperty(PersistentProperty<?> property) {
|
||||
return delegate.isVersionProperty(property);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MongoPersistentProperty getIdProperty() {
|
||||
return delegate.getIdProperty();
|
||||
}
|
||||
|
||||
public MongoPersistentProperty getRequiredIdProperty() {
|
||||
return delegate.getRequiredIdProperty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MongoPersistentProperty getVersionProperty() {
|
||||
return delegate.getVersionProperty();
|
||||
}
|
||||
|
||||
public MongoPersistentProperty getRequiredVersionProperty() {
|
||||
return delegate.getRequiredVersionProperty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MongoPersistentProperty getPersistentProperty(String name) {
|
||||
return wrap(delegate.getPersistentProperty(name));
|
||||
}
|
||||
|
||||
public MongoPersistentProperty getRequiredPersistentProperty(String name) {
|
||||
|
||||
MongoPersistentProperty persistentProperty = getPersistentProperty(name);
|
||||
if (persistentProperty != null) {
|
||||
return persistentProperty;
|
||||
}
|
||||
|
||||
throw new RuntimeException(":kladjnf");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MongoPersistentProperty getPersistentProperty(Class<? extends Annotation> annotationType) {
|
||||
return wrap(delegate.getPersistentProperty(annotationType));
|
||||
}
|
||||
|
||||
public Iterable<MongoPersistentProperty> getPersistentProperties(Class<? extends Annotation> annotationType) {
|
||||
return Streamable.of(delegate.getPersistentProperties(annotationType)).stream().map(this::wrap)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean hasIdProperty() {
|
||||
return delegate.hasIdProperty();
|
||||
}
|
||||
|
||||
public boolean hasVersionProperty() {
|
||||
return delegate.hasVersionProperty();
|
||||
}
|
||||
|
||||
public Class<T> getType() {
|
||||
return delegate.getType();
|
||||
}
|
||||
|
||||
public Alias getTypeAlias() {
|
||||
return delegate.getTypeAlias();
|
||||
}
|
||||
|
||||
public TypeInformation<T> getTypeInformation() {
|
||||
return delegate.getTypeInformation();
|
||||
}
|
||||
|
||||
public void doWithProperties(PropertyHandler<MongoPersistentProperty> handler) {
|
||||
|
||||
delegate.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> {
|
||||
handler.doWithPersistentProperty(wrap(property));
|
||||
});
|
||||
}
|
||||
|
||||
public void doWithProperties(SimplePropertyHandler handler) {
|
||||
|
||||
delegate.doWithProperties((SimplePropertyHandler) property -> {
|
||||
if (property instanceof MongoPersistentProperty) {
|
||||
handler.doWithPersistentProperty(wrap((MongoPersistentProperty) property));
|
||||
} else {
|
||||
handler.doWithPersistentProperty(property);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void doWithAssociations(AssociationHandler<MongoPersistentProperty> handler) {
|
||||
delegate.doWithAssociations(handler);
|
||||
}
|
||||
|
||||
public void doWithAssociations(SimpleAssociationHandler handler) {
|
||||
delegate.doWithAssociations(handler);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
return delegate.findAnnotation(annotationType);
|
||||
}
|
||||
|
||||
public <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType) throws IllegalStateException {
|
||||
return delegate.getRequiredAnnotation(annotationType);
|
||||
}
|
||||
|
||||
public <A extends Annotation> boolean isAnnotationPresent(Class<A> annotationType) {
|
||||
return delegate.isAnnotationPresent(annotationType);
|
||||
}
|
||||
|
||||
public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
|
||||
return delegate.getPropertyAccessor(bean);
|
||||
}
|
||||
|
||||
public <B> PersistentPropertyPathAccessor<B> getPropertyPathAccessor(B bean) {
|
||||
return delegate.getPropertyPathAccessor(bean);
|
||||
}
|
||||
|
||||
public IdentifierAccessor getIdentifierAccessor(Object bean) {
|
||||
return delegate.getIdentifierAccessor(bean);
|
||||
}
|
||||
|
||||
public boolean isNew(Object bean) {
|
||||
return delegate.isNew(bean);
|
||||
}
|
||||
|
||||
public boolean isImmutable() {
|
||||
return delegate.isImmutable();
|
||||
}
|
||||
|
||||
public boolean requiresPropertyPopulation() {
|
||||
return delegate.requiresPropertyPopulation();
|
||||
}
|
||||
|
||||
public Iterator<MongoPersistentProperty> iterator() {
|
||||
|
||||
List<MongoPersistentProperty> target = new ArrayList<>();
|
||||
delegate.iterator().forEachRemaining(it -> target.add(wrap(it)));
|
||||
return target.iterator();
|
||||
}
|
||||
|
||||
public void forEach(Consumer<? super MongoPersistentProperty> action) {
|
||||
delegate.forEach(it -> action.accept(wrap(it)));
|
||||
}
|
||||
|
||||
public Spliterator<MongoPersistentProperty> spliterator() {
|
||||
return delegate.spliterator();
|
||||
}
|
||||
|
||||
private MongoPersistentProperty wrap(MongoPersistentProperty source) {
|
||||
if (source == null) {
|
||||
return source;
|
||||
}
|
||||
return new EmbeddedMongoPersistentProperty(source, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPersistentProperty(MongoPersistentProperty property) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAssociation(Association<MongoPersistentProperty> association) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify() throws MappingException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEvaluationContextProvider(EvaluationContextProvider provider) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmbedded() {
|
||||
return context.getProperty().isEmbedded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2020/12
|
||||
*/
|
||||
public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty {
|
||||
|
||||
private final MongoPersistentProperty delegate;
|
||||
private final EmbeddedEntityContext context;
|
||||
|
||||
public EmbeddedMongoPersistentProperty(MongoPersistentProperty delegate, EmbeddedEntityContext context) {
|
||||
|
||||
this.delegate = delegate;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
|
||||
if (!context.getProperty().isEmbedded()) {
|
||||
return delegate.getFieldName();
|
||||
}
|
||||
|
||||
return context.getProperty().findAnnotation(Embedded.class).prefix() + delegate.getFieldName();
|
||||
}
|
||||
|
||||
public Class<?> getFieldType() {
|
||||
return delegate.getFieldType();
|
||||
}
|
||||
|
||||
public int getFieldOrder() {
|
||||
return delegate.getFieldOrder();
|
||||
}
|
||||
|
||||
public boolean isDbReference() {
|
||||
return delegate.isDbReference();
|
||||
}
|
||||
|
||||
public boolean isExplicitIdProperty() {
|
||||
return delegate.isExplicitIdProperty();
|
||||
}
|
||||
|
||||
public boolean isLanguageProperty() {
|
||||
return delegate.isLanguageProperty();
|
||||
}
|
||||
|
||||
public boolean isExplicitLanguageProperty() {
|
||||
return delegate.isExplicitLanguageProperty();
|
||||
}
|
||||
|
||||
public boolean isTextScoreProperty() {
|
||||
return delegate.isTextScoreProperty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DBRef getDBRef() {
|
||||
return delegate.getDBRef();
|
||||
}
|
||||
|
||||
public boolean usePropertyAccess() {
|
||||
return delegate.usePropertyAccess();
|
||||
}
|
||||
|
||||
public boolean hasExplicitWriteTarget() {
|
||||
return delegate.hasExplicitWriteTarget();
|
||||
}
|
||||
|
||||
public PersistentEntity<?, MongoPersistentProperty> getOwner() {
|
||||
return delegate.getOwner();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return delegate.getType();
|
||||
}
|
||||
|
||||
public TypeInformation<?> getTypeInformation() {
|
||||
return delegate.getTypeInformation();
|
||||
}
|
||||
|
||||
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
|
||||
return delegate.getPersistentEntityTypes();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Method getGetter() {
|
||||
return delegate.getGetter();
|
||||
}
|
||||
|
||||
public Method getRequiredGetter() {
|
||||
return delegate.getRequiredGetter();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Method getSetter() {
|
||||
return delegate.getSetter();
|
||||
}
|
||||
|
||||
public Method getRequiredSetter() {
|
||||
return delegate.getRequiredSetter();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Method getWither() {
|
||||
return delegate.getWither();
|
||||
}
|
||||
|
||||
public Method getRequiredWither() {
|
||||
return delegate.getRequiredWither();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Field getField() {
|
||||
return delegate.getField();
|
||||
}
|
||||
|
||||
public Field getRequiredField() {
|
||||
return delegate.getRequiredField();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSpelExpression() {
|
||||
return delegate.getSpelExpression();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Association<MongoPersistentProperty> getAssociation() {
|
||||
return delegate.getAssociation();
|
||||
}
|
||||
|
||||
public Association<MongoPersistentProperty> getRequiredAssociation() {
|
||||
return delegate.getRequiredAssociation();
|
||||
}
|
||||
|
||||
public boolean isEntity() {
|
||||
return delegate.isEntity();
|
||||
}
|
||||
|
||||
public boolean isIdProperty() {
|
||||
return delegate.isIdProperty();
|
||||
}
|
||||
|
||||
public boolean isVersionProperty() {
|
||||
return delegate.isVersionProperty();
|
||||
}
|
||||
|
||||
public boolean isCollectionLike() {
|
||||
return delegate.isCollectionLike();
|
||||
}
|
||||
|
||||
public boolean isMap() {
|
||||
return delegate.isMap();
|
||||
}
|
||||
|
||||
public boolean isArray() {
|
||||
return delegate.isArray();
|
||||
}
|
||||
|
||||
public boolean isTransient() {
|
||||
return delegate.isTransient();
|
||||
}
|
||||
|
||||
public boolean isWritable() {
|
||||
return delegate.isWritable();
|
||||
}
|
||||
|
||||
public boolean isImmutable() {
|
||||
return delegate.isImmutable();
|
||||
}
|
||||
|
||||
public boolean isAssociation() {
|
||||
return delegate.isAssociation();
|
||||
}
|
||||
|
||||
public boolean isEmbedded() {
|
||||
return delegate.isEmbedded();
|
||||
}
|
||||
|
||||
public boolean isNullable() {
|
||||
return delegate.isNullable();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Class<?> getComponentType() {
|
||||
return delegate.getComponentType();
|
||||
}
|
||||
|
||||
public Class<?> getRawType() {
|
||||
return delegate.getRawType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Class<?> getMapValueType() {
|
||||
return delegate.getMapValueType();
|
||||
}
|
||||
|
||||
public Class<?> getActualType() {
|
||||
return delegate.getActualType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
return delegate.findAnnotation(annotationType);
|
||||
}
|
||||
|
||||
public <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType) throws IllegalStateException {
|
||||
return delegate.getRequiredAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
|
||||
return delegate.findPropertyOrOwnerAnnotation(annotationType);
|
||||
}
|
||||
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
|
||||
return delegate.isAnnotationPresent(annotationType);
|
||||
}
|
||||
|
||||
public boolean hasActualTypeAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return delegate.hasActualTypeAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Class<?> getAssociationTargetType() {
|
||||
return delegate.getAssociationTargetType();
|
||||
}
|
||||
|
||||
public <T> PersistentPropertyAccessor<T> getAccessorForOwner(T owner) {
|
||||
return delegate.getAccessorForOwner(owner);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersistentEntity<?>, MongoPersistentProperty>
|
||||
public class MongoMappingContext extends AbstractMappingContext<MongoPersistentEntity<?>, MongoPersistentProperty>
|
||||
implements ApplicationContextAware {
|
||||
|
||||
private static final FieldNamingStrategy DEFAULT_NAMING_STRATEGY = PropertyNameFieldNamingStrategy.INSTANCE;
|
||||
@@ -76,11 +76,16 @@ public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersis
|
||||
* @see org.springframework.data.mapping.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.MutablePersistentEntity, org.springframework.data.mapping.SimpleTypeHolder)
|
||||
*/
|
||||
@Override
|
||||
public MongoPersistentProperty createPersistentProperty(Property property, BasicMongoPersistentEntity<?> owner,
|
||||
public MongoPersistentProperty createPersistentProperty(Property property, MongoPersistentEntity<?> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
return new CachingMongoPersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected MongoPersistentProperty createPersistentProperty(Property property, MongoPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.BasicMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.model.MappingContext)
|
||||
@@ -126,4 +131,17 @@ public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersis
|
||||
public void setAutoIndexCreation(boolean autoCreateIndexes) {
|
||||
this.autoIndexCreation = autoCreateIndexes;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MongoPersistentEntity<?> getPersistentEntity(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
MongoPersistentEntity entity = super.getPersistentEntity(persistentProperty);
|
||||
if(entity == null || !persistentProperty.isEmbedded()) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
return new EmbeddedMongoPersistentEntity(entity, new EmbeddedEntityContext(persistentProperty));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.model.MutablePersistentEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -24,7 +25,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersistentProperty> {
|
||||
public interface MongoPersistentEntity<T> extends MutablePersistentEntity<T, MongoPersistentProperty> {
|
||||
|
||||
/**
|
||||
* Returns the collection the entity shall be persisted to.
|
||||
@@ -93,4 +94,8 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
|
||||
return getShardKey().isSharded();
|
||||
}
|
||||
|
||||
default boolean isEmbedded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.Embedded.OnEmpty;
|
||||
import org.springframework.data.util.NullableUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -123,6 +127,24 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
|
||||
return field != null ? !FieldType.IMPLICIT.equals(field.targetType()) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the property should be embedded.
|
||||
* @since 3.2
|
||||
*/
|
||||
default boolean isEmbedded() {
|
||||
return isEntity() && findAnnotation(Embedded.class) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the property generally allows {@literal null} values;
|
||||
* @since 3.2
|
||||
*/
|
||||
default boolean isNullable() {
|
||||
|
||||
return (isEmbedded() && findAnnotation(Embedded.class).onEmpty().equals(OnEmpty.USE_NULL))
|
||||
&& !NullableUtils.isNonNull(getField(), ElementType.FIELD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple {@link Converter} implementation to transform a {@link MongoPersistentProperty} into its field name.
|
||||
*
|
||||
@@ -137,7 +159,10 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
public String convert(MongoPersistentProperty source) {
|
||||
return source.getFieldName();
|
||||
if (!source.isEmbedded()) {
|
||||
return source.getFieldName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.support;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
@@ -28,6 +30,7 @@ import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
|
||||
import org.springframework.data.mongodb.core.mapping.Embedded;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityMetadata;
|
||||
import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery;
|
||||
@@ -36,6 +39,7 @@ import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.mongodb.MongoException;
|
||||
|
||||
@@ -82,9 +86,14 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
|
||||
Sort sort = tree.getSort();
|
||||
|
||||
for (Part part : tree.getParts()) {
|
||||
|
||||
if (GEOSPATIAL_TYPES.contains(part.getType())) {
|
||||
return;
|
||||
}
|
||||
if (isIndexOnEmbeddedType(part)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String property = part.getProperty().toDotPath();
|
||||
Direction order = toDirection(sort, property);
|
||||
index.on(property, order);
|
||||
@@ -107,7 +116,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
|
||||
|
||||
MongoEntityMetadata<?> metadata = query.getQueryMethod().getEntityInformation();
|
||||
try {
|
||||
indexOperationsProvider.indexOps(metadata.getCollectionName()).ensureIndex(index);
|
||||
indexOperationsProvider.indexOps(metadata.getCollectionName(), metadata.getJavaType()).ensureIndex(index);
|
||||
} catch (UncategorizedMongoDbException e) {
|
||||
|
||||
if (e.getCause() instanceof MongoException) {
|
||||
@@ -129,6 +138,19 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
|
||||
LOG.debug(String.format("Created %s!", index));
|
||||
}
|
||||
|
||||
public boolean isIndexOnEmbeddedType(Part part) {
|
||||
|
||||
// TODO we could do it for nested fields in the
|
||||
Field field = ReflectionUtils.findField(part.getProperty().getOwningType().getType(),
|
||||
part.getProperty().getSegment());
|
||||
|
||||
if (field == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AnnotatedElementUtils.hasAnnotation(field, Embedded.class);
|
||||
}
|
||||
|
||||
private static Direction toDirection(Sort sort, String property) {
|
||||
|
||||
if (sort.isUnsorted()) {
|
||||
|
||||
@@ -90,7 +90,7 @@ public class MongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID exten
|
||||
|
||||
if (createIndexesForQueryMethods) {
|
||||
factory.addQueryCreationListener(
|
||||
new IndexEnsuringQueryCreationListener(collectionName -> operations.indexOps(collectionName)));
|
||||
new IndexEnsuringQueryCreationListener((collectionName, javaType) -> operations.indexOps(javaType)));
|
||||
}
|
||||
|
||||
return factory;
|
||||
|
||||
@@ -97,7 +97,7 @@ public class ReactiveMongoRepositoryFactoryBean<T extends Repository<S, ID>, S,
|
||||
|
||||
if (createIndexesForQueryMethods) {
|
||||
factory.addQueryCreationListener(new IndexEnsuringQueryCreationListener(
|
||||
collectionName -> IndexOperationsAdapter.blocking(operations.indexOps(collectionName))));
|
||||
(collectionName, javaType) -> IndexOperationsAdapter.blocking(operations.indexOps(javaType))));
|
||||
}
|
||||
|
||||
return factory;
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.data.mongodb.core.convert.MongoTypeMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
@@ -103,7 +104,7 @@ public class AbstractMongoConfigurationUnitTests {
|
||||
|
||||
AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class);
|
||||
MongoMappingContext mappingContext = context.getBean(MongoMappingContext.class);
|
||||
BasicMongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Entity.class);
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Entity.class);
|
||||
EvaluationContextProvider provider = (EvaluationContextProvider) ReflectionTestUtils.getField(entity,
|
||||
"evaluationContextProvider");
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.data.mongodb.core.convert.MongoTypeMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.test.util.MongoTestUtils;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
@@ -103,7 +104,7 @@ public class AbstractReactiveMongoConfigurationUnitTests {
|
||||
|
||||
AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class);
|
||||
MongoMappingContext mappingContext = context.getBean(MongoMappingContext.class);
|
||||
BasicMongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Entity.class);
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Entity.class);
|
||||
EvaluationContextProvider provider = (EvaluationContextProvider) ReflectionTestUtils.getField(entity,
|
||||
"evaluationContextProvider");
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.query.Query;
|
||||
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
|
||||
import org.springframework.data.mongodb.test.util.Template;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ExtendWith(MongoTemplateExtension.class)
|
||||
class MongoTemplateEmbeddedTests {
|
||||
|
||||
private static @Template MongoTemplate template;
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readWrite() {
|
||||
|
||||
WithEmbedded source = new WithEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.stringValue = "string-val";
|
||||
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
template.save(source);
|
||||
|
||||
assertThat(template.findOne(query(where("id").is(source.id)), WithEmbedded.class)).isEqualTo(source);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void filterOnEmbeddedValue() {
|
||||
|
||||
WithEmbedded source = new WithEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.stringValue = "string-val";
|
||||
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
template.save(source);
|
||||
|
||||
assertThat(template.findOne(
|
||||
Query.query(where("embeddableValue.stringValue").is(source.embeddableValue.stringValue)), WithEmbedded.class))
|
||||
.isEqualTo(source);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readWritePrefixed() {
|
||||
|
||||
WithPrefixedEmbedded source = new WithPrefixedEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.stringValue = "string-val";
|
||||
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
template.save(source);
|
||||
|
||||
assertThat(template.findOne(query(where("id").is(source.id)), WithPrefixedEmbedded.class)).isEqualTo(source);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void filterOnPrefixedEmbeddedValue() {
|
||||
|
||||
WithPrefixedEmbedded source = new WithPrefixedEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.stringValue = "string-val";
|
||||
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
template.save(source);
|
||||
|
||||
assertThat(
|
||||
template.findOne(Query.query(where("embeddableValue.stringValue").is(source.embeddableValue.stringValue)),
|
||||
WithPrefixedEmbedded.class)).isEqualTo(source);
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
static class WithEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
static class WithPrefixedEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
static class EmbeddableType {
|
||||
|
||||
String stringValue;
|
||||
List<String> listValue;
|
||||
|
||||
@Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.annotation.Id;
|
||||
@@ -46,6 +45,7 @@ import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
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.query.Criteria;
|
||||
|
||||
@@ -357,6 +357,103 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
.isEqualTo(new Document("val", new Document("$add", Arrays.asList("$nested1.value1", "$field2.nestedValue2"))));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnEmbeddedFieldCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WithEmbedded.class, project().and("embeddableType.stringValue").as("val"))
|
||||
.toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$stringValue"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnEmbeddedFieldWithAtFieldAnnotationCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WithEmbedded.class, project().and("embeddableType.atFieldAnnotatedValue").as("val"))
|
||||
.toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$with-at-field-annotation"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnPrefixedEmbeddedFieldCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WithEmbedded.class, project().and("prefixedEmbeddableValue.stringValue").as("val"))
|
||||
.toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$prefix-stringValue"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnPrefixedEmbeddedFieldWithAtFieldAnnotationCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WithEmbedded.class,
|
||||
project().and("prefixedEmbeddableValue.atFieldAnnotatedValue").as("val")).toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$prefix-with-at-field-annotation"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnNestedEmbeddedFieldCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WrapperAroundWithEmbedded.class,
|
||||
project().and("withEmbedded.embeddableType.stringValue").as("val")).toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$withEmbedded.stringValue"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnNestedEmbeddedFieldWithAtFieldAnnotationCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WrapperAroundWithEmbedded.class,
|
||||
project().and("withEmbedded.embeddableType.atFieldAnnotatedValue").as("val")).toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$withEmbedded.with-at-field-annotation"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnNestedPrefixedEmbeddedFieldCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WrapperAroundWithEmbedded.class,
|
||||
project().and("withEmbedded.prefixedEmbeddableValue.stringValue").as("val")).toDocument("collection", context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$withEmbedded.prefix-stringValue"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersProjectOnNestedPrefixedEmbeddedFieldWithAtFieldAnnotationCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(WrapperAroundWithEmbedded.class);
|
||||
|
||||
Document agg = newAggregation(WrapperAroundWithEmbedded.class,
|
||||
project().and("withEmbedded.prefixedEmbeddableValue.atFieldAnnotatedValue").as("val")).toDocument("collection",
|
||||
context);
|
||||
|
||||
assertThat(getPipelineElementFromAggregationAt(agg, 0).get("$project"))
|
||||
.isEqualTo(new Document("val", "$withEmbedded.prefix-with-at-field-annotation"));
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = "person")
|
||||
@AllArgsConstructor
|
||||
public static class FooPerson {
|
||||
@@ -433,4 +530,26 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
String value1;
|
||||
@org.springframework.data.mongodb.core.mapping.Field("nestedValue2") String value2;
|
||||
}
|
||||
|
||||
static class WrapperAroundWithEmbedded {
|
||||
|
||||
String id;
|
||||
WithEmbedded withEmbedded;
|
||||
}
|
||||
|
||||
static class WithEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableType;
|
||||
@Embedded.Nullable("prefix-") EmbeddableType prefixedEmbeddableValue;
|
||||
}
|
||||
|
||||
static class EmbeddableType {
|
||||
|
||||
String stringValue;
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
@@ -71,6 +72,7 @@ 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.geo.Sphere;
|
||||
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.FieldType;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
@@ -2179,6 +2181,197 @@ public class MappingMongoConverterUnitTests {
|
||||
assertThat(((LinkedHashMap) result.get("cluster")).get("_id")).isEqualTo(100L);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void writeFlattensEmbeddedType() {
|
||||
|
||||
WithNullableEmbedded source = new WithNullableEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.embeddableValue.stringValue = "string-val";
|
||||
source.embeddableValue.transientValue = "must-not-be-written";
|
||||
source.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
org.bson.Document target = new org.bson.Document();
|
||||
converter.write(source, target);
|
||||
|
||||
assertThat(target).containsEntry("_id", "id-1") //
|
||||
.containsEntry("stringValue", "string-val") //
|
||||
.containsEntry("listValue", Arrays.asList("list-val-1", "list-val-2")) //
|
||||
.containsEntry("with-at-field-annotation", "@Field") //
|
||||
.doesNotContainKey("embeddableValue") //
|
||||
.doesNotContainKey("transientValue");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void writePrefixesEmbeddedType() {
|
||||
|
||||
WithPrefixedNullableEmbedded source = new WithPrefixedNullableEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.embeddableValue.stringValue = "string-val";
|
||||
source.embeddableValue.transientValue = "must-not-be-written";
|
||||
source.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
org.bson.Document target = new org.bson.Document();
|
||||
converter.write(source, target);
|
||||
|
||||
assertThat(target).containsEntry("_id", "id-1") //
|
||||
.containsEntry("prefix-stringValue", "string-val") //
|
||||
.containsEntry("prefix-listValue", Arrays.asList("list-val-1", "list-val-2")) //
|
||||
.containsEntry("prefix-with-at-field-annotation", "@Field") //
|
||||
.doesNotContainKey("embeddableValue") //
|
||||
.doesNotContainKey("transientValue") //
|
||||
.doesNotContainKey("prefix-transientValue");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void writeNullEmbeddedType() {
|
||||
|
||||
WithNullableEmbedded source = new WithNullableEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = null;
|
||||
|
||||
org.bson.Document target = new org.bson.Document();
|
||||
converter.write(source, target);
|
||||
|
||||
assertThat(target) //
|
||||
.doesNotContainKey("prefix-stringValue").doesNotContainKey("prefix-listValue")
|
||||
.doesNotContainKey("embeddableValue");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void writeDeepNestedEmbeddedType() {
|
||||
|
||||
WrapperAroundWithEmbedded source = new WrapperAroundWithEmbedded();
|
||||
source.someValue = "root-level-value";
|
||||
source.nullableEmbedded = new WithNullableEmbedded();
|
||||
source.nullableEmbedded.id = "id-1";
|
||||
source.nullableEmbedded.embeddableValue = new EmbeddableType();
|
||||
source.nullableEmbedded.embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
source.nullableEmbedded.embeddableValue.stringValue = "string-val";
|
||||
source.nullableEmbedded.embeddableValue.transientValue = "must-not-be-written";
|
||||
source.nullableEmbedded.embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
org.bson.Document target = new org.bson.Document();
|
||||
converter.write(source, target);
|
||||
|
||||
assertThat(target).containsEntry("someValue", "root-level-value") //
|
||||
.containsEntry("nullableEmbedded", new org.bson.Document("_id", "id-1").append("stringValue", "string-val") //
|
||||
.append("listValue", Arrays.asList("list-val-1", "list-val-2")) //
|
||||
.append("with-at-field-annotation", "@Field")); //
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readEmbeddedType() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("_id", "id-1") //
|
||||
.append("stringValue", "string-val") //
|
||||
.append("listValue", Arrays.asList("list-val-1", "list-val-2")) //
|
||||
.append("with-at-field-annotation", "@Field");
|
||||
|
||||
EmbeddableType embeddableValue = new EmbeddableType();
|
||||
embeddableValue.stringValue = "string-val";
|
||||
embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
WithNullableEmbedded target = converter.read(WithNullableEmbedded.class, source);
|
||||
assertThat(target.embeddableValue).isEqualTo(embeddableValue);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readPrefixedEmbeddedType() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("_id", "id-1") //
|
||||
.append("prefix-stringValue", "string-val") //
|
||||
.append("prefix-listValue", Arrays.asList("list-val-1", "list-val-2")) //
|
||||
.append("prefix-with-at-field-annotation", "@Field");
|
||||
|
||||
EmbeddableType embeddableValue = new EmbeddableType();
|
||||
embeddableValue.stringValue = "string-val";
|
||||
embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
WithPrefixedNullableEmbedded target = converter.read(WithPrefixedNullableEmbedded.class, source);
|
||||
assertThat(target.embeddableValue).isEqualTo(embeddableValue);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readNullableEmbeddedTypeWhenSourceDoesNotContainValues() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("_id", "id-1");
|
||||
|
||||
WithNullableEmbedded target = converter.read(WithNullableEmbedded.class, source);
|
||||
assertThat(target.embeddableValue).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readEmptyEmbeddedTypeWhenSourceDoesNotContainValues() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("_id", "id-1");
|
||||
|
||||
WithEmptyEmbeddedType target = converter.read(WithEmptyEmbeddedType.class, source);
|
||||
assertThat(target.embeddableValue).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readDeepNestedEmbeddedType() {
|
||||
|
||||
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") //
|
||||
.append("listValue", Arrays.asList("list-val-1", "list-val-2")) //
|
||||
.append("with-at-field-annotation", "@Field"));
|
||||
|
||||
WrapperAroundWithEmbedded target = converter.read(WrapperAroundWithEmbedded.class, source);
|
||||
|
||||
EmbeddableType embeddableValue = new EmbeddableType();
|
||||
embeddableValue.stringValue = "string-val";
|
||||
embeddableValue.listValue = Arrays.asList("list-val-1", "list-val-2");
|
||||
embeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
|
||||
assertThat(target.someValue).isEqualTo("root-level-value");
|
||||
assertThat(target.nullableEmbedded).isNotNull();
|
||||
assertThat(target.nullableEmbedded.embeddableValue).isEqualTo(embeddableValue);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void readEmbeddedTypeWithComplexValue() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("_id", "id-1").append("address",
|
||||
new org.bson.Document("street", "1007 Mountain Drive").append("city", "Gotham"));
|
||||
|
||||
WithNullableEmbedded target = converter.read(WithNullableEmbedded.class, source);
|
||||
|
||||
Address expected = new Address();
|
||||
expected.city = "Gotham";
|
||||
expected.street = "1007 Mountain Drive";
|
||||
|
||||
assertThat(target.embeddableValue.address) //
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void writeEmbeddedTypeWithComplexValue() {
|
||||
|
||||
WithNullableEmbedded source = new WithNullableEmbedded();
|
||||
source.id = "id-1";
|
||||
source.embeddableValue = new EmbeddableType();
|
||||
source.embeddableValue.address = new Address();
|
||||
source.embeddableValue.address.city = "Gotham";
|
||||
source.embeddableValue.address.street = "1007 Mountain Drive";
|
||||
|
||||
org.bson.Document target = new org.bson.Document();
|
||||
converter.write(source, target);
|
||||
|
||||
assertThat(target) //
|
||||
.containsEntry("address", new org.bson.Document("street", "1007 Mountain Drive").append("city", "Gotham")) //
|
||||
.doesNotContainKey("street") //
|
||||
.doesNotContainKey("address.street") //
|
||||
.doesNotContainKey("city") //
|
||||
.doesNotContainKey("address.city");
|
||||
}
|
||||
|
||||
static class GenericType<T> {
|
||||
T content;
|
||||
}
|
||||
@@ -2208,6 +2401,7 @@ public class MappingMongoConverterUnitTests {
|
||||
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
static class Address implements InterfaceType {
|
||||
String street;
|
||||
String city;
|
||||
@@ -2641,6 +2835,50 @@ public class MappingMongoConverterUnitTests {
|
||||
Date dateAsObjectId;
|
||||
}
|
||||
|
||||
static class WrapperAroundWithEmbedded {
|
||||
|
||||
String someValue;
|
||||
WithNullableEmbedded nullableEmbedded;
|
||||
WithEmptyEmbeddedType emptyEmbedded;
|
||||
WithPrefixedNullableEmbedded prefixedEmbedded;
|
||||
}
|
||||
|
||||
static class WithNullableEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
static class WithPrefixedNullableEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
static class WithEmptyEmbeddedType {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Empty EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
static class EmbeddableType {
|
||||
|
||||
String stringValue;
|
||||
List<String> listValue;
|
||||
|
||||
@Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
|
||||
@Transient //
|
||||
String transientValue;
|
||||
|
||||
Address address;
|
||||
}
|
||||
|
||||
static class ReturningAfterConvertCallback implements AfterConvertCallback<Person> {
|
||||
|
||||
@Override
|
||||
@@ -2649,4 +2887,5 @@ public class MappingMongoConverterUnitTests {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
@@ -41,8 +40,10 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.WithDBRef;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
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.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
|
||||
@@ -454,6 +455,60 @@ public class MongoExampleMapperUnitTests {
|
||||
assertThat(document).doesNotContainKey("_class");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mapsEmbeddedType() {
|
||||
|
||||
WithEmbedded probe = new WithEmbedded();
|
||||
probe.embeddableType = new EmbeddableType();
|
||||
probe.embeddableType.atFieldAnnotatedValue = "@Field";
|
||||
probe.embeddableType.stringValue = "string-value";
|
||||
|
||||
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
|
||||
assertThat(document).containsEntry("stringValue", "string-value").containsEntry("with-at-field-annotation",
|
||||
"@Field");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mapsPrefixedEmbeddedType() {
|
||||
|
||||
WithEmbedded probe = new WithEmbedded();
|
||||
probe.prefixedEmbeddableValue = new EmbeddableType();
|
||||
probe.prefixedEmbeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
probe.prefixedEmbeddableValue.stringValue = "string-value";
|
||||
|
||||
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
|
||||
assertThat(document).containsEntry("prefix-stringValue", "string-value")
|
||||
.containsEntry("prefix-with-at-field-annotation", "@Field");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mapsNestedEmbeddedType() {
|
||||
|
||||
WrapperAroundWithEmbedded probe = new WrapperAroundWithEmbedded();
|
||||
probe.withEmbedded = new WithEmbedded();
|
||||
probe.withEmbedded.embeddableType = new EmbeddableType();
|
||||
probe.withEmbedded.embeddableType.atFieldAnnotatedValue = "@Field";
|
||||
probe.withEmbedded.embeddableType.stringValue = "string-value";
|
||||
|
||||
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
|
||||
assertThat(document).containsEntry("withEmbedded.stringValue", "string-value")
|
||||
.containsEntry("withEmbedded.with-at-field-annotation", "@Field");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mapsNestedPrefixedEmbeddedType() {
|
||||
|
||||
WrapperAroundWithEmbedded probe = new WrapperAroundWithEmbedded();
|
||||
probe.withEmbedded = new WithEmbedded();
|
||||
probe.withEmbedded.prefixedEmbeddableValue = new EmbeddableType();
|
||||
probe.withEmbedded.prefixedEmbeddableValue.atFieldAnnotatedValue = "@Field";
|
||||
probe.withEmbedded.prefixedEmbeddableValue.stringValue = "string-value";
|
||||
|
||||
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
|
||||
assertThat(document).containsEntry("withEmbedded.prefix-stringValue", "string-value")
|
||||
.containsEntry("withEmbedded.prefix-with-at-field-annotation", "@Field");
|
||||
}
|
||||
|
||||
static class FlatDocument {
|
||||
|
||||
@Id String id;
|
||||
@@ -481,4 +536,29 @@ public class MongoExampleMapperUnitTests {
|
||||
@Id String id;
|
||||
String value;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class WrapperAroundWithEmbedded {
|
||||
|
||||
String id;
|
||||
WithEmbedded withEmbedded;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class WithEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableType;
|
||||
@Embedded.Nullable("prefix-") EmbeddableType prefixedEmbeddableValue;
|
||||
}
|
||||
|
||||
static class EmbeddableType {
|
||||
|
||||
@Indexed String stringValue;
|
||||
|
||||
@Indexed //
|
||||
@Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.geo.Point;
|
||||
@@ -45,9 +46,9 @@ import org.springframework.data.mongodb.core.DocumentTestUtils;
|
||||
import org.springframework.data.mongodb.core.Person;
|
||||
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
|
||||
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
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.FieldType;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
@@ -389,7 +390,7 @@ public class QueryMapperUnitTests {
|
||||
|
||||
Query query = query(where("reference").exists(false));
|
||||
|
||||
BasicMongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
org.bson.Document mappedObject = mapper.getMappedObject(query.getQueryObject(), entity);
|
||||
|
||||
org.bson.Document reference = getAsDocument(mappedObject, "reference");
|
||||
@@ -405,7 +406,7 @@ public class QueryMapperUnitTests {
|
||||
|
||||
Query query = query(where("someString").is("foo").andOperator(where("reference").in(reference)));
|
||||
|
||||
BasicMongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
org.bson.Document mappedObject = mapper.getMappedObject(query.getQueryObject(), entity);
|
||||
|
||||
assertThat(mappedObject).containsEntry("someString", "foo");
|
||||
@@ -446,7 +447,7 @@ public class QueryMapperUnitTests {
|
||||
Query query = query(where("someString").is("foo"));
|
||||
query.fields().exclude("reference");
|
||||
|
||||
BasicMongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
org.bson.Document queryResult = mapper.getMappedObject(query.getQueryObject(), entity);
|
||||
org.bson.Document fieldsResult = mapper.getMappedObject(query.getFieldsObject(), entity);
|
||||
|
||||
@@ -457,7 +458,7 @@ public class QueryMapperUnitTests {
|
||||
@Test // DATAMONGO-686
|
||||
void queryMapperShouldNotChangeStateInGivenQueryObjectWhenIdConstrainedByInList() {
|
||||
|
||||
BasicMongoPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(Sample.class);
|
||||
MongoPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(Sample.class);
|
||||
String idPropertyName = persistentEntity.getIdProperty().getName();
|
||||
org.bson.Document queryObject = query(where(idPropertyName).in("42")).getQueryObject();
|
||||
|
||||
@@ -515,7 +516,7 @@ public class QueryMapperUnitTests {
|
||||
@Test // DATAMONGO-773
|
||||
void queryMapperShouldBeAbleToProcessQueriesThatIncludeDbRefFields() {
|
||||
|
||||
BasicMongoPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
MongoPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(WithDBRef.class);
|
||||
|
||||
Query qry = query(where("someString").is("abc"));
|
||||
qry.fields().include("reference");
|
||||
@@ -781,7 +782,8 @@ public class QueryMapperUnitTests {
|
||||
Query query = query(byExample(probe).and("listOfItems").exists(true));
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class));
|
||||
|
||||
assertThat(document).containsEntry("embedded\\._id", "conflux").containsEntry("my_items", new org.bson.Document("$exists", true));
|
||||
assertThat(document).containsEntry("embedded\\._id", "conflux").containsEntry("my_items",
|
||||
new org.bson.Document("$exists", true));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1988
|
||||
@@ -1011,6 +1013,184 @@ public class QueryMapperUnitTests {
|
||||
assertThat(target).isEqualTo(org.bson.Document.parse("{\"$text\" : { \"$search\" : \"test\" }}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnEmbeddedObjectCorrectly() {
|
||||
|
||||
EmbeddableType embeddableType = new EmbeddableType();
|
||||
embeddableType.stringValue = "test";
|
||||
|
||||
Query source = query(Criteria.where("embeddableValue").is(embeddableType));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(target).isEqualTo(new org.bson.Document("stringValue", "test"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnEmbeddedCorrectly() {
|
||||
|
||||
Query source = query(Criteria.where("embeddableValue.stringValue").is("test"));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(target).isEqualTo(new org.bson.Document("stringValue", "test"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnPrefixedEmbeddedCorrectly() {
|
||||
|
||||
Query source = query(Criteria.where("embeddableValue.stringValue").is("test"));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WithPrefixedEmbedded.class));
|
||||
|
||||
assertThat(target).isEqualTo(new org.bson.Document("prefix-stringValue", "test"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnNestedEmbeddedObjectCorrectly() {
|
||||
|
||||
EmbeddableType embeddableType = new EmbeddableType();
|
||||
embeddableType.stringValue = "test";
|
||||
Query source = query(Criteria.where("withEmbedded.embeddableValue").is(embeddableType));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
assertThat(target).isEqualTo(new org.bson.Document("withEmbedded", new org.bson.Document("stringValue", "test")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnNestedPrefixedEmbeddedObjectCorrectly() {
|
||||
|
||||
EmbeddableType embeddableType = new EmbeddableType();
|
||||
embeddableType.stringValue = "test";
|
||||
Query source = query(Criteria.where("withPrefixedEmbedded.embeddableValue").is(embeddableType));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
assertThat(target)
|
||||
.isEqualTo(new org.bson.Document("withPrefixedEmbedded", new org.bson.Document("prefix-stringValue", "test")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnNestedEmbeddedCorrectly() {
|
||||
|
||||
Query source = query(Criteria.where("withEmbedded.embeddableValue.stringValue").is("test"));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
assertThat(target).isEqualTo(new org.bson.Document("withEmbedded.stringValue", "test"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void rendersQueryOnNestedPrefixedEmbeddedCorrectly() {
|
||||
|
||||
Query source = query(Criteria.where("withPrefixedEmbedded.embeddableValue.stringValue").is("test"));
|
||||
|
||||
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
assertThat(target).isEqualTo(new org.bson.Document("withPrefixedEmbedded.prefix-stringValue", "test"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void sortByEmbeddableIsEmpty() {
|
||||
|
||||
Query query = new Query().with(Sort.by("embeddableValue"));
|
||||
|
||||
org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(
|
||||
new org.bson.Document("stringValue", 1).append("listValue", 1).append("with-at-field-annotation", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void sortByEmbeddableValue() {
|
||||
|
||||
// atFieldAnnotatedValue
|
||||
Query query = new Query().with(Sort.by("embeddableValue.stringValue"));
|
||||
|
||||
org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("stringValue", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void sortByEmbeddableValueWithFieldAnnotation() {
|
||||
|
||||
Query query = new Query().with(Sort.by("embeddableValue.atFieldAnnotatedValue"));
|
||||
|
||||
org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("with-at-field-annotation", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void sortByPrefixedEmbeddableValueWithFieldAnnotation() {
|
||||
|
||||
Query query = new Query().with(Sort.by("embeddableValue.atFieldAnnotatedValue"));
|
||||
|
||||
org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
|
||||
context.getPersistentEntity(WithPrefixedEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("prefix-with-at-field-annotation", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void sortByNestedEmbeddableValueWithFieldAnnotation() {
|
||||
|
||||
Query query = new Query().with(Sort.by("withEmbedded.embeddableValue.atFieldAnnotatedValue"));
|
||||
|
||||
org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("withEmbedded.with-at-field-annotation", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void sortByNestedPrefixedEmbeddableValueWithFieldAnnotation() {
|
||||
|
||||
Query query = new Query().with(Sort.by("withPrefixedEmbedded.embeddableValue.atFieldAnnotatedValue"));
|
||||
|
||||
org.bson.Document document = mapper.getMappedSort(query.getSortObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("withPrefixedEmbedded.prefix-with-at-field-annotation", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void projectOnEmbeddableUsesFields() {
|
||||
|
||||
Query query = new Query();
|
||||
query.fields().include("embeddableValue");
|
||||
|
||||
org.bson.Document document = mapper.getMappedFields(query.getFieldsObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(
|
||||
new org.bson.Document("stringValue", 1).append("listValue", 1).append("with-at-field-annotation", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void projectOnEmbeddableValue() {
|
||||
|
||||
Query query = new Query();
|
||||
query.fields().include("embeddableValue.stringValue");
|
||||
|
||||
org.bson.Document document = mapper.getMappedFields(query.getFieldsObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("stringValue", 1));
|
||||
}
|
||||
|
||||
class WithDeepArrayNesting {
|
||||
|
||||
List<WithNestedArray> level0;
|
||||
@@ -1194,4 +1374,38 @@ public class QueryMapperUnitTests {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static class WrapperAroundWithEmbedded {
|
||||
|
||||
String someValue;
|
||||
WithEmbedded withEmbedded;
|
||||
WithPrefixedEmbedded withPrefixedEmbedded;
|
||||
}
|
||||
|
||||
static class WithEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
static class WithPrefixedEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
static class EmbeddableType {
|
||||
|
||||
String stringValue;
|
||||
List<String> listValue;
|
||||
|
||||
@Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
|
||||
@Transient //
|
||||
String transientValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -48,6 +48,7 @@ import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
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.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
@@ -1089,6 +1090,76 @@ class UpdateMapperUnitTests {
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("aliased.$[element].value", 10)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mappingShouldConsiderValueOfEmbeddedType() {
|
||||
|
||||
Update update = new Update().set("embeddableValue.stringValue", "updated");
|
||||
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("stringValue", "updated")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mappingShouldConsiderEmbeddedType() {
|
||||
|
||||
EmbeddableType embeddableType = new EmbeddableType();
|
||||
embeddableType.stringValue = "updated";
|
||||
embeddableType.listValue = Arrays.asList("val-1", "val-2");
|
||||
Update update = new Update().set("embeddableValue", embeddableType);
|
||||
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(WithEmbedded.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set",
|
||||
new Document("stringValue", "updated").append("listValue", Arrays.asList("val-1", "val-2"))));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mappingShouldConsiderValueOfPrefixedEmbeddedType() {
|
||||
|
||||
Update update = new Update().set("embeddableValue.stringValue", "updated");
|
||||
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(WithPrefixedEmbedded.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("prefix-stringValue", "updated")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mappingShouldConsiderPrefixedEmbeddedType() {
|
||||
|
||||
EmbeddableType embeddableType = new EmbeddableType();
|
||||
embeddableType.stringValue = "updated";
|
||||
embeddableType.listValue = Arrays.asList("val-1", "val-2");
|
||||
|
||||
Update update = new Update().set("embeddableValue", embeddableType);
|
||||
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(WithPrefixedEmbedded.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set",
|
||||
new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2"))));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void mappingShouldConsiderNestedPrefixedEmbeddedType() {
|
||||
|
||||
EmbeddableType embeddableType = new EmbeddableType();
|
||||
embeddableType.stringValue = "updated";
|
||||
embeddableType.listValue = Arrays.asList("val-1", "val-2");
|
||||
|
||||
Update update = new Update().set("withPrefixedEmbedded.embeddableValue", embeddableType);
|
||||
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
|
||||
|
||||
System.out.println("mappedUpdate.toJson(): " + mappedUpdate.toJson());
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("withPrefixedEmbedded",
|
||||
new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2")))));
|
||||
}
|
||||
|
||||
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
|
||||
ListModelWrapper concreteTypeWithListAttributeOfInterfaceType;
|
||||
}
|
||||
@@ -1418,4 +1489,37 @@ class UpdateMapperUnitTests {
|
||||
|
||||
}
|
||||
|
||||
static class WrapperAroundWithEmbedded {
|
||||
|
||||
String someValue;
|
||||
WithEmbedded withEmbedded;
|
||||
WithPrefixedEmbedded withPrefixedEmbedded;
|
||||
}
|
||||
|
||||
static class WithEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
static class WithPrefixedEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable("prefix-") EmbeddableType embeddableValue;
|
||||
}
|
||||
|
||||
static class EmbeddableType {
|
||||
|
||||
String stringValue;
|
||||
List<String> listValue;
|
||||
|
||||
@Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
|
||||
@Transient //
|
||||
String transientValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -30,6 +31,7 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.DocumentTestUtils;
|
||||
@@ -42,6 +44,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexRes
|
||||
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
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.Language;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
@@ -1282,6 +1285,44 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
public void resolvedIndexOnEmbeddedType() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(WithEmbedded.class,
|
||||
EmbeddableType.class);
|
||||
|
||||
assertThat(indexDefinitions).hasSize(2);
|
||||
assertThat(indexDefinitions.get(0)).satisfies(it -> {
|
||||
assertThat(it.getIndexKeys()).containsEntry("stringValue", 1);
|
||||
});
|
||||
assertThat(indexDefinitions.get(1)).satisfies(it -> {
|
||||
assertThat(it.getIndexKeys()).containsEntry("with-at-field-annotation", 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
public void resolvedIndexOnNestedEmbeddedType() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||
WrapperAroundWithEmbedded.class, WithEmbedded.class, EmbeddableType.class);
|
||||
|
||||
assertThat(indexDefinitions).hasSize(2);
|
||||
assertThat(indexDefinitions.get(0)).satisfies(it -> {
|
||||
assertThat(it.getIndexKeys()).containsEntry("withEmbedded.stringValue", 1);
|
||||
});
|
||||
assertThat(indexDefinitions.get(1)).satisfies(it -> {
|
||||
assertThat(it.getIndexKeys()).containsEntry("withEmbedded.with-at-field-annotation", 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
public void errorsOnIndexOnEmbedded() {
|
||||
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> prepareMappingContextAndResolveIndexForType(InvalidIndexOnEmbedded.class));
|
||||
|
||||
}
|
||||
|
||||
@Document
|
||||
class MixedIndexRoot {
|
||||
|
||||
@@ -1472,6 +1513,41 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
AlternatePathToNoCycleButIndenticallNamedPropertiesDeeplyNestedDocument path2;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class WrapperAroundWithEmbedded {
|
||||
|
||||
String id;
|
||||
WithEmbedded withEmbedded;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class WithEmbedded {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddableType embeddableType;
|
||||
}
|
||||
|
||||
@Document
|
||||
class InvalidIndexOnEmbedded {
|
||||
|
||||
@Indexed //
|
||||
@Embedded.Nullable //
|
||||
EmbeddableType embeddableType;
|
||||
|
||||
}
|
||||
|
||||
static class EmbeddableType {
|
||||
|
||||
@Indexed String stringValue;
|
||||
|
||||
List<String> listValue;
|
||||
|
||||
@Indexed //
|
||||
@Field("with-at-field-annotation") //
|
||||
String atFieldAnnotatedValue;
|
||||
}
|
||||
|
||||
static class AlternatePathToNoCycleButIndenticallNamedPropertiesDeeplyNestedDocument {
|
||||
NoCycleButIndenticallNamedPropertiesDeeplyNested propertyWithIndexedStructure;
|
||||
}
|
||||
@@ -1521,17 +1597,17 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
|
||||
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?>... types) {
|
||||
|
||||
MongoMappingContext mappingContext = prepareMappingContext(type);
|
||||
MongoMappingContext mappingContext = prepareMappingContext(types);
|
||||
MongoPersistentEntityIndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext);
|
||||
return resolver.resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type));
|
||||
return resolver.resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(types[0]));
|
||||
}
|
||||
|
||||
private static MongoMappingContext prepareMappingContext(Class<?> type) {
|
||||
private static MongoMappingContext prepareMappingContext(Class<?>... types) {
|
||||
|
||||
MongoMappingContext mappingContext = new MongoMappingContext();
|
||||
mappingContext.setInitialEntitySet(Collections.singleton(type));
|
||||
mappingContext.setInitialEntitySet(new LinkedHashSet<>(Arrays.asList(types)));
|
||||
mappingContext.initialize();
|
||||
|
||||
return mappingContext;
|
||||
|
||||
@@ -30,10 +30,11 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
@@ -63,8 +64,7 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
@Test
|
||||
void evaluatesSpELExpression() {
|
||||
|
||||
MongoPersistentEntity<Company> entity = new BasicMongoPersistentEntity<>(
|
||||
ClassTypeInformation.from(Company.class));
|
||||
MongoPersistentEntity<Company> entity = new BasicMongoPersistentEntity<>(ClassTypeInformation.from(Company.class));
|
||||
assertThat(entity.getCollection()).isEqualTo("35");
|
||||
}
|
||||
|
||||
@@ -364,16 +364,13 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
class WithDocumentCollation {}
|
||||
|
||||
@Sharded
|
||||
private
|
||||
class WithDefaultShardKey {}
|
||||
private class WithDefaultShardKey {}
|
||||
|
||||
@Sharded("country")
|
||||
private
|
||||
class WithSingleShardKey {}
|
||||
private class WithSingleShardKey {}
|
||||
|
||||
@Sharded({ "country", "userid" })
|
||||
private
|
||||
class WithMultiShardKey {}
|
||||
private class WithMultiShardKey {}
|
||||
|
||||
static class SampleExtension implements EvaluationContextExtension {
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ public class BasicMongoPersistentPropertyUnitTests {
|
||||
public void honorsFieldOrderWhenIteratingOverProperties() {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
BasicMongoPersistentEntity<?> entity = context.getPersistentEntity(Sample.class);
|
||||
MongoPersistentEntity<?> entity = context.getPersistentEntity(Sample.class);
|
||||
|
||||
List<String> properties = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ public class MongoMappingContextUnitTests {
|
||||
void mappingContextShouldAcceptClassWithImplicitIdProperty() {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
BasicMongoPersistentEntity<?> pe = context.getRequiredPersistentEntity(ClassWithImplicitId.class);
|
||||
MongoPersistentEntity<?> pe = context.getRequiredPersistentEntity(ClassWithImplicitId.class);
|
||||
|
||||
assertThat(pe).isNotNull();
|
||||
assertThat(pe.isIdProperty(pe.getRequiredPersistentProperty("id"))).isTrue();
|
||||
@@ -121,7 +121,7 @@ public class MongoMappingContextUnitTests {
|
||||
void mappingContextShouldAcceptClassWithExplicitIdProperty() {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
BasicMongoPersistentEntity<?> pe = context.getRequiredPersistentEntity(ClassWithExplicitId.class);
|
||||
MongoPersistentEntity<?> pe = context.getRequiredPersistentEntity(ClassWithExplicitId.class);
|
||||
|
||||
assertThat(pe).isNotNull();
|
||||
assertThat(pe.isIdProperty(pe.getRequiredPersistentProperty("myId"))).isTrue();
|
||||
@@ -131,7 +131,7 @@ public class MongoMappingContextUnitTests {
|
||||
void mappingContextShouldAcceptClassWithExplicitAndImplicitIdPropertyByGivingPrecedenceToExplicitIdProperty() {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
BasicMongoPersistentEntity<?> pe = context.getRequiredPersistentEntity(ClassWithExplicitIdAndImplicitId.class);
|
||||
MongoPersistentEntity<?> pe = context.getRequiredPersistentEntity(ClassWithExplicitIdAndImplicitId.class);
|
||||
assertThat(pe).isNotNull();
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ public class MongoMappingContextUnitTests {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
|
||||
BasicMongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(ClassWithChronoUnit.class);
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(ClassWithChronoUnit.class);
|
||||
|
||||
assertThat(entity.getPersistentProperty("unit").isEntity()).isFalse();
|
||||
assertThat(context.hasPersistentEntityFor(ChronoUnit.class)).isFalse();
|
||||
|
||||
@@ -1361,6 +1361,41 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
void spelExpressionArgumentsGetReevaluatedOnEveryInvocation() {
|
||||
|
||||
assertThat(repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Dave")).containsExactly(dave);
|
||||
assertThat(repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Carter")).containsExactly(carter);
|
||||
assertThat(repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Carter"))
|
||||
.containsExactly(carter);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void findByValueInsideEmbedded() {
|
||||
|
||||
Person bart = new Person("bart", "simpson");
|
||||
User user = new User();
|
||||
user.setUsername("bartman");
|
||||
user.setId("84r1m4n");
|
||||
bart.setEmbeddedUser(user);
|
||||
|
||||
operations.save(bart);
|
||||
|
||||
List<Person> result = repository.findByEmbeddedUserUsername(user.getUsername());
|
||||
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).getId().equals(bart.getId()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1902
|
||||
void findByEmbedded() {
|
||||
|
||||
Person bart = new Person("bart", "simpson");
|
||||
User user = new User();
|
||||
user.setUsername("bartman");
|
||||
user.setId("84r1m4n");
|
||||
bart.setEmbeddedUser(user);
|
||||
|
||||
operations.save(bart);
|
||||
|
||||
List<Person> result = repository.findByEmbeddedUser(user);
|
||||
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).getId().equals(bart.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Embedded;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
/**
|
||||
@@ -70,6 +71,9 @@ public class Person extends Contact {
|
||||
|
||||
Credentials credentials;
|
||||
|
||||
@Embedded.Nullable(prefix = "u") //
|
||||
User embeddedUser;
|
||||
|
||||
public Person() {
|
||||
|
||||
this(null, null);
|
||||
@@ -296,6 +300,14 @@ public class Person extends Contact {
|
||||
return skills;
|
||||
}
|
||||
|
||||
public User getEmbeddedUser() {
|
||||
return embeddedUser;
|
||||
}
|
||||
|
||||
public void setEmbeddedUser(User embeddedUser) {
|
||||
this.embeddedUser = embeddedUser;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -401,4 +401,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
Person findPersonByManyArguments(String firstname, String lastname, String email, Integer age, Sex sex,
|
||||
Date createdAt, List<String> skills, String street, String zipCode, //
|
||||
String city, UUID uniqueId, String username, String password);
|
||||
|
||||
List<Person> findByEmbeddedUserUsername(String username);
|
||||
|
||||
List<Person> findByEmbeddedUser(User user);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ public class RepositoryIndexCreationIntegrationTests {
|
||||
|
||||
assertHasIndexForField(indexInfo, "lastname");
|
||||
assertHasIndexForField(indexInfo, "firstname");
|
||||
assertHasIndexForField(indexInfo, "add");
|
||||
}
|
||||
|
||||
private static void assertHasIndexForField(List<IndexInfo> indexInfo, String... fields) {
|
||||
|
||||
@@ -66,7 +66,7 @@ class IndexEnsuringQueryCreationListenerUnitTests {
|
||||
|
||||
partTreeQuery = mock(PartTreeMongoQuery.class, Answers.RETURNS_MOCKS);
|
||||
when(partTreeQuery.getTree()).thenReturn(partTree);
|
||||
when(provider.indexOps(anyString())).thenReturn(indexOperations);
|
||||
when(provider.indexOps(anyString(), any())).thenReturn(indexOperations);
|
||||
when(queryMethod.getEntityInformation()).thenReturn(entityInformation);
|
||||
when(entityInformation.getCollectionName()).thenReturn("persons");
|
||||
}
|
||||
|
||||
370
src/main/asciidoc/reference/embedded-documents.adoc
Normal file
370
src/main/asciidoc/reference/embedded-documents.adoc
Normal file
@@ -0,0 +1,370 @@
|
||||
[[embedded-entities]]
|
||||
== Embedded Types
|
||||
|
||||
Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the MongoDB Document.
|
||||
|
||||
[[embedded-entities.mapping]]
|
||||
=== Embedded Types Mapping
|
||||
|
||||
In the example below you see, that `User.name` is annotated with `@Embedded`.
|
||||
The consequence of this is that all properties of `UserName` are folded into the `user` document.
|
||||
|
||||
.Sample Code of embedding objects
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
private String userId;
|
||||
|
||||
@Embedded(onEmpty = USE_NULL) <1>
|
||||
UserName name;
|
||||
}
|
||||
|
||||
public class UserName {
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"_id" : "1da2ba06-3ba7",
|
||||
"firstname" : "Emma",
|
||||
"lastname" : "Frost"
|
||||
}
|
||||
----
|
||||
<1> When loading the `name` property its value is set to `null` if both `firstname` and `lastname` are either `null` or not present.
|
||||
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)`.
|
||||
Using those annotations simultaneously set JSR-305 `@javax.annotation.Nonnull` accordingly.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is possible to use complex types within an embedded object.
|
||||
However those must not be, nor contain embedded fields themselves.
|
||||
====
|
||||
|
||||
[[embedded-entities.mapping.field-names]]
|
||||
=== Embedded Types field names
|
||||
|
||||
A value object can be embedded multiple times by using the optional `prefix` attribute of the `@Embedded` annotation.
|
||||
By dosing so the chosen prefix is prepended to each property or `@Field("...")` name in the embedded object.
|
||||
Please note that values will overwrite each other if multiple properties render to the same field name.
|
||||
|
||||
.Sample Code of embedded object with name prefix
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
private String userId;
|
||||
|
||||
@Embedded.Nullable(prefix = "u") <1>
|
||||
UserName name;
|
||||
}
|
||||
|
||||
public class UserName {
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"_id" : "a6a805bd-f95f",
|
||||
"ufirstname" : "Jean",
|
||||
"ulastname" : "Grey"
|
||||
}
|
||||
----
|
||||
<1> The prefix `u` is prepended to all properties of `UserName`.
|
||||
====
|
||||
|
||||
While combining the `@Field` annotation with `@Embedded` 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.
|
||||
|
||||
.Sample Code embedded object with `@Field` annotation
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
private String userId;
|
||||
|
||||
@Embedded.Nullable(prefix = "u-") <1>
|
||||
UserName name;
|
||||
}
|
||||
|
||||
public class UserName {
|
||||
|
||||
@Field("first-name") <2>
|
||||
private String firstname;
|
||||
|
||||
@Field("last-name")
|
||||
private String lastname;
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"_id" : "2647f7b9-89da",
|
||||
"u-first-name" : "Barbara", <2>
|
||||
"u-last-name" : "Gordon"
|
||||
}
|
||||
----
|
||||
<1> The prefix `u-` is prepended to all properties of `UserName`.
|
||||
<2> The field name is the result of the combination of the annotated field name an the chosen prefix.
|
||||
====
|
||||
|
||||
[[embedded-entities.queries]]
|
||||
=== Query on Embedded Objects
|
||||
|
||||
Defining queries on embedded properties is possible on type as well as field level as the provided `Critieria` is matched against the domain type.
|
||||
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.
|
||||
|
||||
.Query on embedded object
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
UserName userName = new UserName("Carol", "Danvers")
|
||||
Query findByUserName = query(where("name").is(userName));
|
||||
User user = template.findOne(findByUserName, User.class);
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.find({
|
||||
"firstname" : "Carol",
|
||||
"lastname" : "Danvers"
|
||||
})
|
||||
----
|
||||
====
|
||||
|
||||
It is also possible to address any field of the embedded object directly via its property name as shown in the snippet below.
|
||||
|
||||
.Query on field of embedded object
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
|
||||
List<User> users = template.findAll(findByUserFirstName, User.class);
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.find({
|
||||
"firstname" : "Shuri"
|
||||
})
|
||||
----
|
||||
====
|
||||
|
||||
[[embedded-entities.queries.sort]]
|
||||
==== Sort by embedded field.
|
||||
|
||||
Fields of embedded objects can be used for sorting via their property path as shown in the sample below.
|
||||
|
||||
.Sort on embedded field
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Query findByUserLastName = query(where("name.lastname").is("Romanoff"));
|
||||
List<User> user = template.findAll(findByUserName.withSort(Sort.by("name.firstname")), User.class);
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.find({
|
||||
"lastname" : "Romanoff"
|
||||
}).sort({ "firstname" : 1 })
|
||||
----
|
||||
====
|
||||
|
||||
[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.
|
||||
====
|
||||
|
||||
[[embedded-entities.queries.project]]
|
||||
==== Project on embedded object
|
||||
|
||||
Fields of embedded objects can be subject for projection either as a whole or via single fields as shown in the samples below.
|
||||
|
||||
.Project on embedded object.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Query findByUserLastName = query(where("name.firstname").is("Gamora"));
|
||||
findByUserLastName.fields().include("name"); <1>
|
||||
List<User> user = template.findAll(findByUserName, User.class);
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.find({
|
||||
"lastname" : "Gamora"
|
||||
},
|
||||
{
|
||||
"firstname" : 1,
|
||||
"lastname" : 1
|
||||
})
|
||||
----
|
||||
<1> A field projection on an embedded object includes all of its properties.
|
||||
====
|
||||
|
||||
.Project on a field of an embedded object.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Query findByUserLastName = query(where("name.lastname").is("Smoak"));
|
||||
findByUserLastName.fields().include("name.firstname"); <1>
|
||||
List<User> user = template.findAll(findByUserName, User.class);
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.find({
|
||||
"lastname" : "Smoak"
|
||||
},
|
||||
{
|
||||
"firstname" : 1
|
||||
})
|
||||
----
|
||||
<1> A field projection on an embedded object includes all of its properties.
|
||||
====
|
||||
|
||||
[[embedded-entities.queries.by-example]]
|
||||
==== Query By Example on embedded object.
|
||||
|
||||
Embedded 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.
|
||||
|
||||
[[embedded-entities.queries.repository]]
|
||||
==== Repository Queries on embedded objects.
|
||||
|
||||
The `Repository` abstraction allows deriving queries on fields of embedded objects as well as the entire object.
|
||||
|
||||
.Repository queries on embedded objects.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface UserRepository extends CrudRepository<User, String> {
|
||||
|
||||
List<User> findByName(UserName username); <1>
|
||||
|
||||
List<User> findByNameFirstname(String firstname); <1>
|
||||
}
|
||||
----
|
||||
<1> Matches against all fields of the embedded object.
|
||||
<2> Matches against the `firstname`.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Index creation for embedded objects is suspended even if the repository `create-query-indexes` namespace attribute is set to `true`.
|
||||
====
|
||||
|
||||
[[embedded-entities.update]]
|
||||
=== Update on Embedded Objects
|
||||
|
||||
Embedded 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.
|
||||
It is possible to update single attributes of the embedded object as well as the entire value as shown in the examples below.
|
||||
|
||||
.Update a single field of an embedded object.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Update update = new Update().set("name.firstname", "Janet");
|
||||
template.update(User.class).matching(where("id").is("Wasp"))
|
||||
.apply(update).first()
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.update({
|
||||
"_id" : "Wasp"
|
||||
},
|
||||
{
|
||||
"$set" { "firstname" : "Janet" }
|
||||
},
|
||||
{ ... }
|
||||
)
|
||||
----
|
||||
====
|
||||
|
||||
.Update an embedded object.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Update update = new Update().set("name", new Name("Janet", "van Dyne"));
|
||||
template.update(User.class).matching(where("id").is("Wasp"))
|
||||
.apply(update).first()
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
db.collection.update({
|
||||
"_id" : "Wasp"
|
||||
},
|
||||
{
|
||||
"$set" {
|
||||
"firstname" : "Janet",
|
||||
"lastname" : "van Dyne",
|
||||
}
|
||||
},
|
||||
{ ... }
|
||||
)
|
||||
----
|
||||
====
|
||||
|
||||
[[embedded-entities.aggregations]]
|
||||
=== Aggregations on Embedded Objects
|
||||
|
||||
The <<mongo.aggregation,Aggregation Framework>> will attempt to map embedded values of typed aggregations.
|
||||
Please make sure to work with the properties path including the embedded wrapper object when referencing one of it's values.
|
||||
Other than that no special action is required.
|
||||
|
||||
[[embedded-entities.indexes]]
|
||||
=== Index on Embedded Objects
|
||||
|
||||
It is possible to attach the `@Indexed` annotation to properties of an embedded type just as it is done with regular objects.
|
||||
However it is not possible to use `@Indexed` along with the `@Embedded` annotation on the very same property of an object.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
private String userId;
|
||||
|
||||
@Embedded(onEmpty = USE_NULL)
|
||||
UserName name; <1>
|
||||
|
||||
@Indexed <2> // Invalid -> InvalidDataAccessApiUsageException
|
||||
@Embedded(onEmpty = USE_Empty)
|
||||
Address address;
|
||||
}
|
||||
|
||||
public class UserName {
|
||||
|
||||
private String firstname;
|
||||
|
||||
@Indexed
|
||||
private String lastname; <1>
|
||||
}
|
||||
----
|
||||
<1> Index created for `lastname` in `users` collection.
|
||||
<2> Invalid `@Indexed` usage along with `@Embedded`
|
||||
====
|
||||
|
||||
|
||||
@@ -833,4 +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.
|
||||
|
||||
include::embedded-documents.adoc[]
|
||||
|
||||
include::mongo-custom-conversions.adoc[]
|
||||
|
||||
Reference in New Issue
Block a user