diff --git a/pom.xml b/pom.xml index aff1afc489..ec0d667da1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 4.3.0-SNAPSHOT + 4.3.x-4571-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index 34d95eb205..76732159c0 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 4.3.0-SNAPSHOT + 4.3.x-4571-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 124a6bf5ad..5c21192bcd 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -15,7 +15,7 @@ org.springframework.data spring-data-mongodb-parent - 4.3.0-SNAPSHOT + 4.3.x-4571-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 39fc1a1de9..651bf35a1c 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 4.3.0-SNAPSHOT + 4.3.x-4571-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java index eebd948a14..331cbb2e86 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java @@ -593,7 +593,7 @@ public static boolean hasValue(Bson bson, FieldName fieldName) { Map source = asMap(bson); if (fieldName.isKey()) { - return source.get(fieldName.name()) != null; + return source.containsKey(fieldName.name()); } String[] parts = fieldName.parts(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 47c7a0027b..9815ce23b9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -27,6 +27,8 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; import org.bson.BsonUndefined; import org.bson.types.Binary; @@ -38,6 +40,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.Mockito; @@ -673,6 +677,15 @@ void readsListOfMapsCorrectly() { assertThat(wrapper.listOfMaps.get(0).get("Foo")).isEqualTo(Locale.ENGLISH); } + @ParameterizedTest // GH-4571 + @MethodSource("listMapSetReadingSource") + void initializesListMapSetPropertiesIfRequiredOnRead(org.bson.Document source, Class type, + Function valueFunction, Object expectedValue) { + + T target = converter.read(type, source); + assertThat(target).extracting(valueFunction).isEqualTo(expectedValue); + } + @Test // DATAMONGO-259 void writesPlainMapOfCollectionsCorrectly() { @@ -2986,6 +2999,49 @@ org.bson.Document write(Object source) { return target; } + private static Stream listMapSetReadingSource() { + + Function contacts = CollectionWrapper::getContacts; + Function contactsSet = CollectionWrapper::getContactsSet; + Function autoInitList = CollectionWrapper::getAutoInitList; + Function map = ClassWithMapProperty::getMap; + Function autoInitMap = ClassWithMapProperty::getAutoInitMap; + + return Stream.of( // + + // List + Arguments.of(new org.bson.Document("contacts", Collections.emptyList()), CollectionWrapper.class, contacts, + Collections.emptyList()), + Arguments.of(new org.bson.Document("contacts", null), CollectionWrapper.class, contacts, null), + Arguments.of(new org.bson.Document(), CollectionWrapper.class, contacts, null), + + // ctor initialized List + Arguments.of(new org.bson.Document("autoInitList", Collections.emptyList()), CollectionWrapper.class, + autoInitList, Collections.emptyList()), + Arguments.of(new org.bson.Document("autoInitList", null), CollectionWrapper.class, autoInitList, null), + Arguments.of(new org.bson.Document(), CollectionWrapper.class, autoInitList, + Collections.singletonList("spring")), + + // Set + Arguments.of(new org.bson.Document("contactsSet", Collections.emptyList()), CollectionWrapper.class, + contactsSet, Collections.emptySet()), + Arguments.of(new org.bson.Document("contactsSet", null), CollectionWrapper.class, contactsSet, null), + Arguments.of(new org.bson.Document(), CollectionWrapper.class, contactsSet, null), + + // Map + Arguments.of(new org.bson.Document("map", new org.bson.Document()), ClassWithMapProperty.class, map, + Collections.emptyMap()), + Arguments.of(new org.bson.Document("map", null), ClassWithMapProperty.class, map, null), + Arguments.of(new org.bson.Document(), ClassWithMapProperty.class, map, null), + + // ctor initialized Map + Arguments.of(new org.bson.Document("autoInitMap", new org.bson.Document()), ClassWithMapProperty.class, + autoInitMap, Collections.emptyMap()), + Arguments.of(new org.bson.Document("autoInitMap", null), ClassWithMapProperty.class, autoInitMap, null), + Arguments.of(new org.bson.Document(), ClassWithMapProperty.class, autoInitMap, + Collections.singletonMap("spring", "data"))); + } + static class GenericType { T content; } @@ -3142,11 +3198,20 @@ static class ClassWithSortedMap { static class ClassWithMapProperty { Map map; + Map autoInitMap = Collections.singletonMap("spring", "data"); Map> mapOfLists; Map mapOfObjects; Map mapOfStrings; Map mapOfPersons; TreeMap treeMapOfPersons; + + public Map getMap() { + return map; + } + + public Map getAutoInitMap() { + return this.autoInitMap; + } } static class ClassWithNestedMaps { @@ -3168,6 +3233,19 @@ static class CollectionWrapper { List> strings; List> listOfMaps; Set contactsSet; + List autoInitList = Collections.singletonList("spring"); + + public List getContacts() { + return contacts; + } + + public Set getContactsSet() { + return contactsSet; + } + + public List getAutoInitList() { + return autoInitList; + } } static class LocaleWrapper { diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc index 9283ad9be8..5b553fd91b 100644 --- a/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc +++ b/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc @@ -274,6 +274,17 @@ calling `get()` before the actual conversion |=== ==== +.Collection Handling +[NOTE] +==== +Collection handing depends on the actual values retrieved from the MongoDB. + +* If a document does **not** contain the field mapped to a collection, the mapping will not touch the property. +Which means the value will remain `null`, a java default or any value set during object creation. +* If the document contains the field to be mapped, but the field holds a `null` value (like: `{ 'list' : null }`), the property value is set to `null` overriding any default value set during object creation. +* If the document contains the field to be mapped to a collection which is **not** `null` (like: `{ 'list' : [ ... ] }`), the collection is populated with the mapped values overriding any default value set during object creation. +==== + [[mapping-configuration]] == Mapping Configuration