Skip to content

Commit bd985a6

Browse files
christophstroblmp911de
authored andcommitted
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.
1 parent c1417c4 commit bd985a6

File tree

40 files changed

+2390
-61
lines changed

40 files changed

+2390
-61
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider {
4747
* @see org.springframework.data.mongodb.core.index.IndexOperationsProvider#reactiveIndexOps(java.lang.String)
4848
*/
4949
@Override
50-
public IndexOperations indexOps(String collectionName) {
51-
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
50+
public IndexOperations indexOps(String collectionName, Class<?> type) {
51+
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, type);
5252
}
5353
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -715,20 +715,26 @@ public void dropCollection(String collectionName) {
715715
});
716716
}
717717

718+
719+
@Override
720+
public IndexOperations indexOps(String collectionName) {
721+
return indexOps(collectionName, null);
722+
}
723+
718724
/*
719725
* (non-Javadoc)
720726
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.String)
721727
*/
722-
public IndexOperations indexOps(String collectionName) {
723-
return new DefaultIndexOperations(this, collectionName, null);
728+
public IndexOperations indexOps(String collectionName, @Nullable Class<?> type) {
729+
return new DefaultIndexOperations(this, collectionName, type);
724730
}
725731

726732
/*
727733
* (non-Javadoc)
728734
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class)
729735
*/
730736
public IndexOperations indexOps(Class<?> entityClass) {
731-
return new DefaultIndexOperations(this, getCollectionName(entityClass), entityClass);
737+
return indexOps(getCollectionName(entityClass), entityClass);
732738
}
733739

734740
/*

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

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ Bson getDocument() {
6767
return this.document;
6868
}
6969

70+
public void putAll(MongoPersistentProperty prop, Document value) {
71+
value.entrySet().forEach(entry -> BsonUtils.asMap(document).put(entry.getKey(), entry.getValue()));
72+
}
73+
7074
/**
7175
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
7276
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist

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

+42
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
6363
import org.springframework.data.mongodb.CodecRegistryProvider;
6464
import org.springframework.data.mongodb.MongoDatabaseFactory;
65+
import org.springframework.data.mongodb.core.mapping.Embedded;
66+
import org.springframework.data.mongodb.core.mapping.Embedded.OnEmpty;
6567
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
6668
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
6769
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
@@ -427,6 +429,13 @@ private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyA
427429
continue;
428430
}
429431

432+
if (prop.isEmbedded()) {
433+
434+
accessor.setProperty(prop,
435+
readEmbedded(documentAccessor, currentPath, prop, mappingContext.getPersistentEntity(prop)));
436+
continue;
437+
}
438+
430439
// We skip the id property since it was already set
431440

432441
if (entity.isIdProperty(prop)) {
@@ -472,6 +481,22 @@ private void readAssociation(Association<MongoPersistentProperty> association, P
472481
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
473482
}
474483

484+
@Nullable
485+
private Object readEmbedded(DocumentAccessor documentAccessor, ObjectPath currentPath, MongoPersistentProperty prop,
486+
MongoPersistentEntity<?> embeddedEntity) {
487+
488+
if (prop.findAnnotation(Embedded.class).onEmpty().equals(OnEmpty.USE_EMPTY)) {
489+
return read(embeddedEntity, (Document) documentAccessor.getDocument(), currentPath);
490+
}
491+
492+
for (MongoPersistentProperty persistentProperty : embeddedEntity) {
493+
if (documentAccessor.hasValue(persistentProperty)) {
494+
return read(embeddedEntity, (Document) documentAccessor.getDocument(), currentPath);
495+
}
496+
}
497+
return null;
498+
}
499+
475500
/*
476501
* (non-Javadoc)
477502
* @see org.springframework.data.mongodb.core.convert.MongoWriter#toDBRef(java.lang.Object, org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
@@ -642,6 +667,15 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce
642667
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
643668
TypeInformation<?> type = prop.getTypeInformation();
644669

670+
if (prop.isEmbedded()) {
671+
672+
Document target = new Document();
673+
writeInternal(obj, target, mappingContext.getPersistentEntity(prop));
674+
675+
accessor.putAll(prop, target);
676+
return;
677+
}
678+
645679
if (valueType.isCollectionLike()) {
646680
List<Object> collectionInternal = createCollection(asCollection(obj), prop);
647681
accessor.put(prop, collectionInternal);
@@ -1352,6 +1386,14 @@ public Object convertToMongoType(@Nullable Object obj, TypeInformation<?> typeIn
13521386
return !obj.getClass().equals(typeInformation.getType()) ? newDocument : removeTypeInfo(newDocument, true);
13531387
}
13541388

1389+
@Nullable
1390+
@Override
1391+
public Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity entity) {
1392+
Document newDocument = new Document();
1393+
writeInternal(obj, newDocument, entity);
1394+
return newDocument;
1395+
}
1396+
13551397
public List<Object> maybeConvertList(Iterable<?> source, TypeInformation<?> typeInformation) {
13561398

13571399
List<Object> newDbl = new ArrayList<>();

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
2727
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
2828
import org.springframework.data.mongodb.util.BsonUtils;
29+
import org.springframework.data.util.TypeInformation;
2930
import org.springframework.lang.Nullable;
3031
import org.springframework.util.Assert;
3132
import org.springframework.util.ClassUtils;
@@ -144,9 +145,9 @@ default Object convertId(@Nullable Object id, Class<?> targetType) {
144145
try {
145146
return getConversionService().canConvert(id.getClass(), targetType)
146147
? getConversionService().convert(id, targetType)
147-
: convertToMongoType(id, null);
148+
: convertToMongoType(id, (TypeInformation<?>) null);
148149
} catch (ConversionException o_O) {
149-
return convertToMongoType(id, null);
150+
return convertToMongoType(id,(TypeInformation<?>) null);
150151
}
151152
}
152153
}

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.bson.conversions.Bson;
1919
import org.springframework.data.convert.EntityWriter;
20+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
2021
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
2122
import org.springframework.data.util.TypeInformation;
2223
import org.springframework.lang.Nullable;
@@ -43,7 +44,7 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
4344
*/
4445
@Nullable
4546
default Object convertToMongoType(@Nullable Object obj) {
46-
return convertToMongoType(obj, null);
47+
return convertToMongoType(obj, (TypeInformation<?>) null);
4748
}
4849

4950
/**
@@ -57,6 +58,9 @@ default Object convertToMongoType(@Nullable Object obj) {
5758
@Nullable
5859
Object convertToMongoType(@Nullable Object obj, @Nullable TypeInformation<?> typeInformation);
5960

61+
default Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity<?> entity) {
62+
return convertToMongoType(obj, entity.getTypeInformation());
63+
}
6064
/**
6165
* Creates a {@link DBRef} to refer to the given object.
6266
*

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

+69-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.data.mapping.PersistentEntity;
3333
import org.springframework.data.mapping.PersistentProperty;
3434
import org.springframework.data.mapping.PersistentPropertyPath;
35+
import org.springframework.data.mapping.PropertyHandler;
3536
import org.springframework.data.mapping.PropertyPath;
3637
import org.springframework.data.mapping.PropertyReferenceException;
3738
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
@@ -140,9 +141,23 @@ public Document getMappedObject(Bson query, @Nullable MongoPersistentEntity<?> e
140141
try {
141142

142143
Field field = createPropertyField(entity, key, mappingContext);
143-
Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key));
144144

145-
result.put(entry.getKey(), entry.getValue());
145+
// TODO: move to dedicated method
146+
if (field.getProperty() != null && field.getProperty().isEmbedded()) {
147+
148+
Object theNestedObject = BsonUtils.get(query, key);
149+
Document mappedValue = (Document) getMappedValue(field, theNestedObject);
150+
if (!StringUtils.hasText(field.getMappedKey())) {
151+
result.putAll(mappedValue);
152+
} else {
153+
result.put(field.getMappedKey(), mappedValue);
154+
}
155+
} else {
156+
157+
Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key));
158+
159+
result.put(entry.getKey(), entry.getValue());
160+
}
146161
} catch (InvalidPersistentPropertyPath invalidPathException) {
147162

148163
// in case the object has not already been mapped
@@ -173,10 +188,16 @@ public Document getMappedSort(Document sortObject, @Nullable MongoPersistentEnti
173188
return new Document();
174189
}
175190

191+
sortObject = filterEmbeddedObjects(sortObject, entity);
192+
176193
Document mappedSort = new Document();
177194
for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
178195

179196
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
197+
if (field.getProperty() != null && field.getProperty().isEmbedded()) {
198+
continue;
199+
}
200+
180201
mappedSort.put(field.getMappedKey(), entry.getValue());
181202
}
182203

@@ -197,7 +218,9 @@ public Document getMappedFields(Document fieldsObject, @Nullable MongoPersistent
197218

198219
Assert.notNull(fieldsObject, "FieldsObject must not be null!");
199220

200-
Document mappedFields = fieldsObject.isEmpty() ? new Document() : getMappedObject(fieldsObject, entity);
221+
fieldsObject = filterEmbeddedObjects(fieldsObject, entity);
222+
223+
Document mappedFields = getMappedObject(fieldsObject, entity);
201224
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
202225
return mappedFields;
203226
}
@@ -217,6 +240,43 @@ private void mapMetaAttributes(Document source, @Nullable MongoPersistentEntity<
217240
}
218241
}
219242

243+
private Document filterEmbeddedObjects(Document fieldsObject, @Nullable MongoPersistentEntity<?> entity) {
244+
245+
if (fieldsObject.isEmpty() || entity == null) {
246+
return fieldsObject;
247+
}
248+
249+
Document target = new Document();
250+
251+
for (Entry<String, Object> field : fieldsObject.entrySet()) {
252+
253+
try {
254+
255+
PropertyPath path = PropertyPath.from(field.getKey(), entity.getTypeInformation());
256+
PersistentPropertyPath<MongoPersistentProperty> persistentPropertyPath = mappingContext
257+
.getPersistentPropertyPath(path);
258+
MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getLeafProperty();
259+
260+
if (property.isEmbedded() && property.isEntity()) {
261+
262+
mappingContext.getPersistentEntity(property)
263+
.doWithProperties((PropertyHandler<MongoPersistentProperty>) embedded -> {
264+
265+
String dotPath = persistentPropertyPath.toDotPath();
266+
dotPath = dotPath + (StringUtils.hasText(dotPath) ? "." : "") + embedded.getName();
267+
target.put(dotPath, field.getValue());
268+
});
269+
} else {
270+
target.put(field.getKey(), field.getValue());
271+
}
272+
} catch (RuntimeException e) {
273+
target.put(field.getKey(), field.getValue());
274+
}
275+
276+
}
277+
return target;
278+
}
279+
220280
private Document getMappedTextScoreField(MongoPersistentProperty property) {
221281
return new Document(property.getFieldName(), META_TEXT_SCORE);
222282
}
@@ -497,6 +557,11 @@ protected Object convertSimpleOrDocument(Object source, @Nullable MongoPersisten
497557
*/
498558
@Nullable
499559
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
560+
561+
if (entity != null && entity.isEmbedded()) {
562+
return converter.convertToMongoType(source, entity);
563+
}
564+
500565
return converter.convertToMongoType(source, entity == null ? null : entity.getTypeInformation());
501566
}
502567

@@ -912,6 +977,7 @@ public boolean isMap() {
912977
public TypeInformation<?> getTypeHint() {
913978
return ClassTypeInformation.OBJECT;
914979
}
980+
915981
}
916982

917983
/**

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

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.data.domain.Sort.Order;
2727
import org.springframework.data.mapping.Association;
2828
import org.springframework.data.mapping.context.MappingContext;
29+
import org.springframework.data.mongodb.core.mapping.EmbeddedMongoPersistentEntity;
2930
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3031
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3132
import org.springframework.data.mongodb.core.query.Query;
@@ -132,6 +133,11 @@ public static boolean isUpdateObject(@Nullable Document updateObj) {
132133
*/
133134
@Override
134135
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
136+
137+
if(entity != null && entity.isEmbedded()) {
138+
return converter.convertToMongoType(source, entity);
139+
}
140+
135141
return converter.convertToMongoType(source,
136142
entity == null ? ClassTypeInformation.OBJECT : getTypeHintForEntity(source, entity));
137143
}
@@ -158,6 +164,10 @@ protected Entry<String, Object> getMappedObjectForField(Field field, Object rawV
158164
return getMappedUpdateModifier(field, rawValue);
159165
}
160166

167+
if(field.getProperty() != null && field.getProperty().isEmbedded()) {
168+
System.out.println("here we are: ");
169+
}
170+
161171
return super.getMappedObjectForField(field, rawValue);
162172
}
163173

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.mongodb.core.index;
1717

18+
import org.springframework.lang.Nullable;
19+
1820
/**
1921
* Provider interface to obtain {@link IndexOperations} by MongoDB collection name.
2022
*
@@ -25,11 +27,23 @@
2527
@FunctionalInterface
2628
public interface IndexOperationsProvider {
2729

30+
/**
31+
* Returns the operations that can be performed on indexes
32+
*
33+
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
34+
* @param type the type used for field mapping. Can be {@literal null}.
35+
* @return index operations on the named collection
36+
* @since 2.5
37+
*/
38+
IndexOperations indexOps(String collectionName, @Nullable Class<?> type);
39+
2840
/**
2941
* Returns the operations that can be performed on indexes
3042
*
3143
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
3244
* @return index operations on the named collection
3345
*/
34-
IndexOperations indexOps(String collectionName);
46+
default IndexOperations indexOps(String collectionName) {
47+
return indexOps(collectionName, null);
48+
}
3549
}

0 commit comments

Comments
 (0)