Polishing.

Reorder API methods, remove unused MongoPersistentProperty.isNullable method, reduce visibility where possible. Add Javadoc and tweak documentation wording.

Introduce DotPath utility to abstract dot path concatenation.

See #2803.
Original pull request: #896.
This commit is contained in:
Mark Paluch
2021-02-16 11:10:06 +01:00
parent bd985a6589
commit f3d6f405c9
20 changed files with 320 additions and 137 deletions

View File

@@ -67,8 +67,17 @@ class DocumentAccessor {
return this.document;
}
public void putAll(MongoPersistentProperty prop, Document value) {
value.entrySet().forEach(entry -> BsonUtils.asMap(document).put(entry.getKey(), entry.getValue()));
/**
* Copies all of the mappings from the given {@link Document} to the underlying target {@link Document}. These
* mappings will replace any mappings that the target document had for any of the keys currently in the specified map.
*
* @param source
*/
public void putAll(Document source) {
Map<String, Object> target = BsonUtils.asMap(document);
target.putAll(source);
}
/**

View File

@@ -672,7 +672,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Document target = new Document();
writeInternal(obj, target, mappingContext.getPersistentEntity(prop));
accessor.putAll(prop, target);
accessor.putAll(target);
return;
}

View File

@@ -40,6 +40,7 @@ import org.springframework.data.mongodb.core.query.MongoRegexCreator;
import org.springframework.data.mongodb.core.query.MongoRegexCreator.MatchMode;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
import org.springframework.data.mongodb.util.DotPath;
import org.springframework.data.support.ExampleMatcherAccessor;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
@@ -134,7 +135,7 @@ public class MongoExampleMapper {
while (iter.hasNext()) {
Map.Entry<String, Object> entry = iter.next();
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey();
String propertyPath = DotPath.from(path).append(entry.getKey()).toString();
String mappedPropertyPath = getMappedPropertyPath(propertyPath, probeType);
if (isEmptyIdProperty(entry)) {

View File

@@ -24,6 +24,7 @@ import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Example;
@@ -32,7 +33,6 @@ import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
@@ -43,6 +43,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.mongodb.util.DotPath;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -255,17 +256,18 @@ public class QueryMapper {
PropertyPath path = PropertyPath.from(field.getKey(), entity.getTypeInformation());
PersistentPropertyPath<MongoPersistentProperty> persistentPropertyPath = mappingContext
.getPersistentPropertyPath(path);
MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getLeafProperty();
MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getRequiredLeafProperty();
if (property.isEmbedded() && property.isEntity()) {
mappingContext.getPersistentEntity(property)
.doWithProperties((PropertyHandler<MongoPersistentProperty>) embedded -> {
MongoPersistentEntity<?> embeddedEntity = mappingContext.getRequiredPersistentEntity(property);
for (MongoPersistentProperty embedded : embeddedEntity) {
DotPath dotPath = DotPath.from(persistentPropertyPath.toDotPath()).append(embedded.getName());
target.put(dotPath.toString(), field.getValue());
}
String dotPath = persistentPropertyPath.toDotPath();
dotPath = dotPath + (StringUtils.hasText(dotPath) ? "." : "") + embedded.getName();
target.put(dotPath, field.getValue());
});
} else {
target.put(field.getKey(), field.getValue());
}

View File

@@ -26,7 +26,6 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.EmbeddedMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Query;
@@ -164,10 +163,6 @@ public class UpdateMapper extends QueryMapper {
return getMappedUpdateModifier(field, rawValue);
}
if(field.getProperty() != null && field.getProperty().isEmbedded()) {
System.out.println("here we are: ");
}
return super.getMappedObjectForField(field, rawValue);
}

View File

@@ -28,17 +28,7 @@ import org.springframework.lang.Nullable;
public interface IndexOperationsProvider {
/**
* Returns the operations that can be performed on indexes
*
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
* @param type the type used for field mapping. Can be {@literal null}.
* @return index operations on the named collection
* @since 2.5
*/
IndexOperations indexOps(String collectionName, @Nullable Class<?> type);
/**
* Returns the operations that can be performed on indexes
* Returns the operations that can be performed on indexes.
*
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
* @return index operations on the named collection
@@ -46,4 +36,14 @@ public interface IndexOperationsProvider {
default IndexOperations indexOps(String collectionName) {
return indexOps(collectionName, null);
}
/**
* Returns the operations that can be performed on indexes.
*
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
* @param type the type used for field mapping. Can be {@literal null}.
* @return index operations on the named collection
* @since 3.2
*/
IndexOperations indexOps(String collectionName, @Nullable Class<?> type);
}

View File

@@ -47,6 +47,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.mongodb.util.DotPath;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.EvaluationContext;
@@ -161,16 +162,16 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
* types. Will never be {@code null}.
*/
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String dotPath,
final Path path, final String collection, final CycleGuard guard) {
private List<IndexDefinitionHolder> resolveIndexForClass( TypeInformation<?> type, String dotPath,
Path path, String collection, CycleGuard guard) {
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) {
private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, String dotPath,
Path path, String collection, CycleGuard guard) {
final List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity));
entity.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
@@ -184,10 +185,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private void guardAndPotentiallyAddIndexForProperty(MongoPersistentProperty persistentProperty, String dotPath,
Path path, String collection, List<IndexDefinitionHolder> indexes, CycleGuard guard) {
String propertyDotPath = dotPath;
DotPath propertyDotPath = DotPath.from(dotPath);
if (!persistentProperty.isEmbedded()) {
propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + persistentProperty.getFieldName();
propertyDotPath = propertyDotPath.append(persistentProperty.getFieldName());
}
Path propertyPath = path.append(persistentProperty);
@@ -195,14 +196,14 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
if (persistentProperty.isEntity()) {
try {
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), propertyDotPath,
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty), propertyDotPath.toString(),
propertyPath, collection, guard));
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
}
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath, collection,
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(), collection,
persistentProperty);
if (!indexDefinitions.isEmpty()) {
@@ -270,7 +271,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
try {
appendTextIndexInformation("", Path.empty(), indexDefinitionBuilder, root,
appendTextIndexInformation(DotPath.empty(), Path.empty(), indexDefinitionBuilder, root,
new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard());
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
@@ -291,9 +292,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
private void appendTextIndexInformation(final String dotPath, final Path path,
final TextIndexDefinitionBuilder indexDefinitionBuilder, final MongoPersistentEntity<?> entity,
final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
private void appendTextIndexInformation(DotPath dotPath, Path path,
TextIndexDefinitionBuilder indexDefinitionBuilder, MongoPersistentEntity<?> entity,
TextIndexIncludeOptions includeOptions, CycleGuard guard) {
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@@ -302,7 +303,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
guard.protect(persistentProperty, path);
if (persistentProperty.isExplicitLanguageProperty() && !StringUtils.hasText(dotPath)) {
if (persistentProperty.isExplicitLanguageProperty() && dotPath.isEmpty()) {
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
}
@@ -310,8 +311,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
+ persistentProperty.getFieldName();
DotPath propertyDotPath = dotPath
.append(persistentProperty.getFieldName());
Path propertyPath = path.append(persistentProperty);
@@ -324,7 +325,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
TextIndexIncludeOptions optionsForNestedType = includeOptions;
if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) {
optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE,
new TextIndexedFieldSpec(propertyDotPath, weight));
new TextIndexedFieldSpec(propertyDotPath.toString(), weight));
}
try {
@@ -337,7 +338,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
entity.getName()), e);
}
} else if (includeOptions.isForce() || indexed != null) {
indexDefinitionBuilder.onField(propertyDotPath, weight);
indexDefinitionBuilder.onField(propertyDotPath.toString(), weight);
}
}
@@ -648,7 +649,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
MongoPersistentProperty property = association.getInverse();
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + property.getFieldName();
DotPath propertyDotPath = DotPath.from(path).append(property.getFieldName());
if (property.isAnnotationPresent(GeoSpatialIndexed.class) || property.isAnnotationPresent(TextIndexed.class)) {
throw new MappingException(
@@ -656,7 +657,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
propertyDotPath));
}
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath, collection,
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(), collection,
property);
if (!indexDefinitions.isEmpty()) {

View File

@@ -19,7 +19,7 @@ package org.springframework.data.mongodb.core.mapping;
* @author Christoph Strobl
* @since 3.2
*/
public class EmbeddedEntityContext {
class EmbeddedEntityContext {
private final MongoPersistentProperty property;

View File

@@ -32,13 +32,16 @@ import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
/**
* Embedded variant of {@link MongoPersistentEntity}.
*
* @author Christoph Strobl
* @since 2020/12
* @since 3.2
* @see Embedded
*/
public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T> {
private EmbeddedEntityContext context;
private MongoPersistentEntity<T> delegate;
private final EmbeddedEntityContext context;
private final MongoPersistentEntity<T> delegate;
public EmbeddedMongoPersistentEntity(MongoPersistentEntity<T> delegate, EmbeddedEntityContext context) {
@@ -46,84 +49,103 @@ public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T
this.delegate = delegate;
}
@Override
public String getCollection() {
return delegate.getCollection();
}
@Override
public String getLanguage() {
return delegate.getLanguage();
}
@Override
@Nullable
public MongoPersistentProperty getTextScoreProperty() {
return delegate.getTextScoreProperty();
}
@Override
public boolean hasTextScoreProperty() {
return delegate.hasTextScoreProperty();
}
@Override
@Nullable
public Collation getCollation() {
return delegate.getCollation();
}
@Override
public boolean hasCollation() {
return delegate.hasCollation();
}
@Override
public ShardKey getShardKey() {
return delegate.getShardKey();
}
@Override
public boolean isSharded() {
return delegate.isSharded();
}
@Override
public String getName() {
return delegate.getName();
}
@Override
@Nullable
public PreferredConstructor<T, MongoPersistentProperty> getPersistenceConstructor() {
return delegate.getPersistenceConstructor();
}
@Override
public boolean isConstructorArgument(PersistentProperty<?> property) {
return delegate.isConstructorArgument(property);
}
@Override
public boolean isIdProperty(PersistentProperty<?> property) {
return delegate.isIdProperty(property);
}
@Override
public boolean isVersionProperty(PersistentProperty<?> property) {
return delegate.isVersionProperty(property);
}
@Override
@Nullable
public MongoPersistentProperty getIdProperty() {
return delegate.getIdProperty();
}
@Override
public MongoPersistentProperty getRequiredIdProperty() {
return delegate.getRequiredIdProperty();
}
@Override
@Nullable
public MongoPersistentProperty getVersionProperty() {
return delegate.getVersionProperty();
}
@Override
public MongoPersistentProperty getRequiredVersionProperty() {
return delegate.getRequiredVersionProperty();
}
@Override
@Nullable
public MongoPersistentProperty getPersistentProperty(String name) {
return wrap(delegate.getPersistentProperty(name));
}
@Override
public MongoPersistentProperty getRequiredPersistentProperty(String name) {
MongoPersistentProperty persistentProperty = getPersistentProperty(name);
@@ -134,36 +156,44 @@ public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T
throw new RuntimeException(":kladjnf");
}
@Override
@Nullable
public MongoPersistentProperty getPersistentProperty(Class<? extends Annotation> annotationType) {
return wrap(delegate.getPersistentProperty(annotationType));
}
@Override
public Iterable<MongoPersistentProperty> getPersistentProperties(Class<? extends Annotation> annotationType) {
return Streamable.of(delegate.getPersistentProperties(annotationType)).stream().map(this::wrap)
.collect(Collectors.toList());
}
@Override
public boolean hasIdProperty() {
return delegate.hasIdProperty();
}
@Override
public boolean hasVersionProperty() {
return delegate.hasVersionProperty();
}
@Override
public Class<T> getType() {
return delegate.getType();
}
@Override
public Alias getTypeAlias() {
return delegate.getTypeAlias();
}
@Override
public TypeInformation<T> getTypeInformation() {
return delegate.getTypeInformation();
}
@Override
public void doWithProperties(PropertyHandler<MongoPersistentProperty> handler) {
delegate.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> {
@@ -171,6 +201,7 @@ public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T
});
}
@Override
public void doWithProperties(SimplePropertyHandler handler) {
delegate.doWithProperties((SimplePropertyHandler) property -> {
@@ -182,51 +213,63 @@ public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T
});
}
@Override
public void doWithAssociations(AssociationHandler<MongoPersistentProperty> handler) {
delegate.doWithAssociations(handler);
}
@Override
public void doWithAssociations(SimpleAssociationHandler handler) {
delegate.doWithAssociations(handler);
}
@Override
@Nullable
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
return delegate.findAnnotation(annotationType);
}
@Override
public <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType) throws IllegalStateException {
return delegate.getRequiredAnnotation(annotationType);
}
@Override
public <A extends Annotation> boolean isAnnotationPresent(Class<A> annotationType) {
return delegate.isAnnotationPresent(annotationType);
}
@Override
public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
return delegate.getPropertyAccessor(bean);
}
@Override
public <B> PersistentPropertyPathAccessor<B> getPropertyPathAccessor(B bean) {
return delegate.getPropertyPathAccessor(bean);
}
@Override
public IdentifierAccessor getIdentifierAccessor(Object bean) {
return delegate.getIdentifierAccessor(bean);
}
@Override
public boolean isNew(Object bean) {
return delegate.isNew(bean);
}
@Override
public boolean isImmutable() {
return delegate.isImmutable();
}
@Override
public boolean requiresPropertyPopulation() {
return delegate.requiresPropertyPopulation();
}
@Override
public Iterator<MongoPersistentProperty> iterator() {
List<MongoPersistentProperty> target = new ArrayList<>();
@@ -234,10 +277,12 @@ public class EmbeddedMongoPersistentEntity<T> implements MongoPersistentEntity<T
return target.iterator();
}
@Override
public void forEach(Consumer<? super MongoPersistentProperty> action) {
delegate.forEach(it -> action.accept(wrap(it)));
}
@Override
public Spliterator<MongoPersistentProperty> spliterator() {
return delegate.spliterator();
}

View File

@@ -26,10 +26,13 @@ import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
/**
* Embedded variant of {@link MongoPersistentProperty}.
*
* @author Christoph Strobl
* @since 2020/12
* @since 3.2
* @see Embedded
*/
public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty {
class EmbeddedMongoPersistentProperty implements MongoPersistentProperty {
private final MongoPersistentProperty delegate;
private final EmbeddedEntityContext context;
@@ -40,6 +43,7 @@ public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty
this.context = context;
}
@Override
public String getFieldName() {
if (!context.getProperty().isEmbedded()) {
@@ -49,210 +53,254 @@ public class EmbeddedMongoPersistentProperty implements MongoPersistentProperty
return context.getProperty().findAnnotation(Embedded.class).prefix() + delegate.getFieldName();
}
@Override
public Class<?> getFieldType() {
return delegate.getFieldType();
}
@Override
public int getFieldOrder() {
return delegate.getFieldOrder();
}
@Override
public boolean isDbReference() {
return delegate.isDbReference();
}
@Override
public boolean isExplicitIdProperty() {
return delegate.isExplicitIdProperty();
}
@Override
public boolean isLanguageProperty() {
return delegate.isLanguageProperty();
}
@Override
public boolean isExplicitLanguageProperty() {
return delegate.isExplicitLanguageProperty();
}
@Override
public boolean isTextScoreProperty() {
return delegate.isTextScoreProperty();
}
@Override
@Nullable
public DBRef getDBRef() {
return delegate.getDBRef();
}
@Override
public boolean usePropertyAccess() {
return delegate.usePropertyAccess();
}
@Override
public boolean hasExplicitWriteTarget() {
return delegate.hasExplicitWriteTarget();
}
@Override
public PersistentEntity<?, MongoPersistentProperty> getOwner() {
return delegate.getOwner();
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public Class<?> getType() {
return delegate.getType();
}
@Override
public TypeInformation<?> getTypeInformation() {
return delegate.getTypeInformation();
}
@Override
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
return delegate.getPersistentEntityTypes();
}
@Override
@Nullable
public Method getGetter() {
return delegate.getGetter();
}
@Override
public Method getRequiredGetter() {
return delegate.getRequiredGetter();
}
@Override
@Nullable
public Method getSetter() {
return delegate.getSetter();
}
@Override
public Method getRequiredSetter() {
return delegate.getRequiredSetter();
}
@Override
@Nullable
public Method getWither() {
return delegate.getWither();
}
@Override
public Method getRequiredWither() {
return delegate.getRequiredWither();
}
@Override
@Nullable
public Field getField() {
return delegate.getField();
}
@Override
public Field getRequiredField() {
return delegate.getRequiredField();
}
@Override
@Nullable
public String getSpelExpression() {
return delegate.getSpelExpression();
}
@Override
@Nullable
public Association<MongoPersistentProperty> getAssociation() {
return delegate.getAssociation();
}
@Override
public Association<MongoPersistentProperty> getRequiredAssociation() {
return delegate.getRequiredAssociation();
}
@Override
public boolean isEntity() {
return delegate.isEntity();
}
@Override
public boolean isIdProperty() {
return delegate.isIdProperty();
}
@Override
public boolean isVersionProperty() {
return delegate.isVersionProperty();
}
@Override
public boolean isCollectionLike() {
return delegate.isCollectionLike();
}
@Override
public boolean isMap() {
return delegate.isMap();
}
@Override
public boolean isArray() {
return delegate.isArray();
}
@Override
public boolean isTransient() {
return delegate.isTransient();
}
@Override
public boolean isWritable() {
return delegate.isWritable();
}
@Override
public boolean isImmutable() {
return delegate.isImmutable();
}
@Override
public boolean isAssociation() {
return delegate.isAssociation();
}
@Override
public boolean isEmbedded() {
return delegate.isEmbedded();
}
public boolean isNullable() {
return delegate.isNullable();
}
@Override
@Nullable
public Class<?> getComponentType() {
return delegate.getComponentType();
}
@Override
public Class<?> getRawType() {
return delegate.getRawType();
}
@Override
@Nullable
public Class<?> getMapValueType() {
return delegate.getMapValueType();
}
@Override
public Class<?> getActualType() {
return delegate.getActualType();
}
@Override
@Nullable
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
return delegate.findAnnotation(annotationType);
}
@Override
public <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType) throws IllegalStateException {
return delegate.getRequiredAnnotation(annotationType);
}
@Override
@Nullable
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
return delegate.findPropertyOrOwnerAnnotation(annotationType);
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
return delegate.isAnnotationPresent(annotationType);
}
@Override
public boolean hasActualTypeAnnotation(Class<? extends Annotation> annotationType) {
return delegate.hasActualTypeAnnotation(annotationType);
}
@Override
@Nullable
public Class<?> getAssociationTargetType() {
return delegate.getAssociationTargetType();
}
@Override
public <T> PersistentPropertyAccessor<T> getAccessorForOwner(T owner) {
return delegate.getAccessorForOwner(owner);
}

View File

@@ -35,6 +35,7 @@ import org.springframework.lang.Nullable;
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class MongoMappingContext extends AbstractMappingContext<MongoPersistentEntity<?>, MongoPersistentProperty>
implements ApplicationContextAware {
@@ -81,18 +82,13 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
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)
*/
@Override
protected <T> BasicMongoPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
return new BasicMongoPersistentEntity<T>(typeInformation);
return new BasicMongoPersistentEntity<>(typeInformation);
}
/*
@@ -101,7 +97,6 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
super.setApplicationContext(applicationContext);
}
@@ -132,16 +127,16 @@ public class MongoMappingContext extends AbstractMappingContext<MongoPersistentE
this.autoIndexCreation = autoCreateIndexes;
}
@Nullable
@Override
public MongoPersistentEntity<?> getPersistentEntity(MongoPersistentProperty persistentProperty) {
MongoPersistentEntity entity = super.getPersistentEntity(persistentProperty);
MongoPersistentEntity<?> entity = super.getPersistentEntity(persistentProperty);
if(entity == null || !persistentProperty.isEmbedded()) {
return entity;
}
return new EmbeddedMongoPersistentEntity(entity, new EmbeddedEntityContext(persistentProperty));
return new EmbeddedMongoPersistentEntity<>(entity, new EmbeddedEntityContext(persistentProperty));
}
}

View File

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

View File

@@ -15,14 +15,10 @@
*/
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;
/**
@@ -132,17 +128,7 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
* @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);
return isEntity() && isAnnotationPresent(Embedded.class);
}
/**

View File

@@ -0,0 +1,89 @@
/*
* 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.util;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Value object representing a dot path.
*
* @author Mark Paluch
* @since 3.2
*/
public class DotPath {
private static final DotPath EMPTY = new DotPath("");
private final String path;
private DotPath(String path) {
this.path = path;
}
/**
* Creates a new {@link DotPath} from {@code dotPath}.
*
* @param dotPath the dot path, can be empty or {@literal null}.
* @return the {@link DotPath} representing {@code dotPath}.
*/
public static DotPath from(@Nullable String dotPath) {
if (StringUtils.hasLength(dotPath)) {
return new DotPath(dotPath);
}
return EMPTY;
}
/**
* Returns an empty dotpath.
*
* @return an empty dotpath.
*/
public static DotPath empty() {
return EMPTY;
}
/**
* Append a segment to the dotpath. If the dotpath is not empty, then segments are separated with a dot.
*
* @param segment the segment to append.
* @return
*/
public DotPath append(String segment) {
if (isEmpty()) {
return new DotPath(segment);
}
return new DotPath(path + "." + segment);
}
/**
* Returns whether this dotpath is empty.
*
* @return whether this dotpath is empty.
*/
public boolean isEmpty() {
return !StringUtils.hasLength(path);
}
@Override
public String toString() {
return path;
}
}

View File

@@ -24,6 +24,7 @@ import lombok.EqualsAndHashCode;
import java.util.Arrays;
import java.util.List;
import lombok.ToString;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.mongodb.core.mapping.Embedded;
@@ -33,6 +34,8 @@ import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
import org.springframework.data.mongodb.test.util.Template;
/**
* Integration tests for {@link Embedded}.
*
* @author Christoph Strobl
*/
@ExtendWith(MongoTemplateExtension.class)
@@ -105,6 +108,7 @@ class MongoTemplateEmbeddedTests {
}
@EqualsAndHashCode
@ToString
static class WithEmbedded {
String id;
@@ -113,6 +117,7 @@ class MongoTemplateEmbeddedTests {
}
@EqualsAndHashCode
@ToString
static class WithPrefixedEmbedded {
String id;
@@ -121,6 +126,7 @@ class MongoTemplateEmbeddedTests {
}
@EqualsAndHashCode
@ToString
static class EmbeddableType {
String stringValue;

View File

@@ -83,7 +83,6 @@ class UnionWithOperationUnitTests {
UnionWithOperation.unionWith("coll-1").pipeline(Aggregation.project().and("name").as("name")));
List<Document> pipeline = agg.toPipeline(contextFor(Supplier.class));
System.out.println("pipeline: " + pipeline);
assertThat(pipeline).containsExactly(new Document("$project", new Document("supplier", 1)), //
new Document("$unionWith", new Document("coll", "coll-1").append("pipeline",
Arrays.asList(new Document("$project", new Document("name", 1))))));

View File

@@ -1155,7 +1155,6 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WrapperAroundWithEmbedded.class));
System.out.println("mappedUpdate.toJson(): " + mappedUpdate.toJson());
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("withPrefixedEmbedded",
new Document("prefix-stringValue", "updated").append("prefix-listValue", Arrays.asList("val-1", "val-2")))));
}

View File

@@ -79,19 +79,6 @@ public class MapReduceTests {
template.getMongoDbFactory().getMongoDatabase("jmr1-out-db").drop();
}
@Test // DATADOC-7
@Ignore
public void testForDocs() {
createMapReduceData();
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
ValueObject.class);
for (ValueObject valueObject : results) {
System.out.println(valueObject);
}
}
@Test // DATAMONGO-260
public void testIssue260() {

View File

@@ -1,6 +1,11 @@
[[new-features]]
= New & Noteworthy
[[new-features.3.2]]
== What's New in Spring Data MongoDB 3.2
* Support for <<embedded-entities,Embedded Types>> to unwrap nested objects into the parent `Document`.
[[new-features.3.1]]
== What's New in Spring Data MongoDB 3.1

View File

@@ -1,30 +1,33 @@
[[embedded-entities]]
== Embedded Types
Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the MongoDB Document.
Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the parent's MongoDB Document.
[[embedded-entities.mapping]]
=== Embedded Types Mapping
In the example below you see, that `User.name` is annotated with `@Embedded`.
The consequence of this is that all properties of `UserName` are folded into the `user` document.
Consider the following domain model where `User.name` is annotated with `@Embedded`.
The `@Embedded` annotation signals that all properties of `UserName` should be unwrapped into the `user` document that owns the `name` property.
.Sample Code of embedding objects
====
[source,java]
----
public class User {
class User {
@Id
private String userId;
@Id
String userId;
@Embedded(onEmpty = USE_NULL) <1>
UserName name;
}
public class UserName {
private String firstname;
private String lastname;
class UserName {
String firstname;
String lastname;
}
----
@@ -41,37 +44,42 @@ By using `onEmpty=USE_EMPTY` an empty `UserName`, with potential `null` value fo
====
For less verbose embeddable type declarations use `@Embedded.Nullable` and `@Embedded.Empty` instead `@Embedded(onEmpty = USE_NULL)` and `@Embedded(onEmpty = USE_EMPTY)`.
Using those annotations simultaneously set JSR-305 `@javax.annotation.Nonnull` accordingly.
Both annotations are meta-annotated with JSR-305 `@javax.annotation.Nonnull` to aid with nullability inspections.
[WARNING]
====
It is possible to use complex types within an embedded object.
However those must not be, nor contain embedded fields themselves.
However, those must not be, nor contain embedded fields themselves.
====
[[embedded-entities.mapping.field-names]]
=== Embedded Types field names
A value object can be embedded multiple times by using the optional `prefix` attribute of the `@Embedded` annotation.
By dosing so the chosen prefix is prepended to each property or `@Field("...")` name in the embedded object.
By dosing so the chosen prefix is prepended to each property or `@Field("")` name in the embedded object.
Please note that values will overwrite each other if multiple properties render to the same field name.
.Sample Code of embedded object with name prefix
====
[source,java]
----
public class User {
class User {
@Id
private String userId;
@Id
String userId;
@Embedded.Nullable(prefix = "u") <1>
@Embedded.Nullable(prefix = "u_") <1>
UserName name;
@Embedded.Nullable(prefix = "a_") <2>
UserName name;
}
public class UserName {
private String firstname;
private String lastname;
class UserName {
String firstname;
String lastname;
}
----
@@ -79,11 +87,14 @@ public class UserName {
----
{
"_id" : "a6a805bd-f95f",
"ufirstname" : "Jean",
"ulastname" : "Grey"
"u_firstname" : "Jean", <1>
"u_lastname" : "Grey",
"a_firstname" : "Something", <2>
"a_lastname" : "Else"
}
----
<1> The prefix `u` is prepended to all properties of `UserName`.
<1> All properties of `UserName` are prefixed with `u_`.
<2> All properties of `UserName` are prefixed with `a_`.
====
While combining the `@Field` annotation with `@Embedded` on the very same property does not make sense and therefore leads to an error.
@@ -104,7 +115,7 @@ public class User {
public class UserName {
@Field("first-name") <2>
@Field("first-name") <2>
private String firstname;
@Field("last-name")
@@ -116,18 +127,18 @@ public class UserName {
----
{
"_id" : "2647f7b9-89da",
"u-first-name" : "Barbara", <2>
"u-first-name" : "Barbara", <2>
"u-last-name" : "Gordon"
}
----
<1> The prefix `u-` is prepended to all properties of `UserName`.
<2> The field name is the result of the combination of the annotated field name an the chosen prefix.
<1> All properties of `UserName` are prefixed with `u-`.
<2> Final field names are a result of concatenating `@Embedded(prefix)` and `@Field(name)`.
====
[[embedded-entities.queries]]
=== Query on Embedded Objects
Defining queries on embedded properties is possible on type as well as field level as the provided `Critieria` is matched against the domain type.
Defining queries on embedded properties is possible on type- as well as field-level as the provided `Criteria` is matched against the domain type.
Prefixes and potential custom field names will be considered when rendering the actual query.
Use the property name of the embedded object to match against all contained fields as shown in the sample below.
@@ -149,7 +160,7 @@ db.collection.find({
----
====
It is also possible to address any field of the embedded object directly via its property name as shown in the snippet below.
It is also possible to address any field of the embedded object directly using its property name as shown in the snippet below.
.Query on field of embedded object
====
@@ -194,7 +205,7 @@ Though possible, using the embedded object itself as sort criteria includes all
====
[[embedded-entities.queries.project]]
==== Project on embedded object
==== Field projection on embedded objects
Fields of embedded objects can be subject for projection either as a whole or via single fields as shown in the samples below.
@@ -203,7 +214,7 @@ Fields of embedded objects can be subject for projection either as a whole or vi
[source,java]
----
Query findByUserLastName = query(where("name.firstname").is("Gamora"));
findByUserLastName.fields().include("name"); <1>
findByUserLastName.fields().include("name"); <1>
List<User> user = template.findAll(findByUserName, User.class);
----
@@ -225,7 +236,7 @@ db.collection.find({
[source,java]
----
Query findByUserLastName = query(where("name.lastname").is("Smoak"));
findByUserLastName.fields().include("name.firstname"); <1>
findByUserLastName.fields().include("name.firstname"); <1>
List<User> user = template.findAll(findByUserName, User.class);
----
@@ -258,9 +269,9 @@ The `Repository` abstraction allows deriving queries on fields of embedded objec
----
interface UserRepository extends CrudRepository<User, String> {
List<User> findByName(UserName username); <1>
List<User> findByName(UserName username); <1>
List<User> findByNameFirstname(String firstname); <1>
List<User> findByNameFirstname(String firstname); <2>
}
----
<1> Matches against all fields of the embedded object.
@@ -330,14 +341,14 @@ db.collection.update({
=== Aggregations on Embedded Objects
The <<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.
Please make sure to work with the property path including the embedded wrapper object when referencing one of its values.
Other than that no special action is required.
[[embedded-entities.indexes]]
=== Index on Embedded Objects
It is possible to attach the `@Indexed` annotation to properties of an embedded type just as it is done with regular objects.
However it is not possible to use `@Indexed` along with the `@Embedded` annotation on the very same property of an object.
It is not possible to use `@Indexed` along with the `@Embedded` annotation on the owning property.
====
[source,java]
@@ -348,9 +359,10 @@ public class User {
private String userId;
@Embedded(onEmpty = USE_NULL)
UserName name; <1>
UserName name; <1>
@Indexed <2> // Invalid -> InvalidDataAccessApiUsageException
// Invalid -> InvalidDataAccessApiUsageException
@Indexed <2>
@Embedded(onEmpty = USE_Empty)
Address address;
}
@@ -360,7 +372,7 @@ public class UserName {
private String firstname;
@Indexed
private String lastname; <1>
private String lastname; <1>
}
----
<1> Index created for `lastname` in `users` collection.