From 97433d0f57bfb206a50b98759298dd7f7a3365b8 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 30 Nov 2023 10:16:04 +0100 Subject: [PATCH] Document list/map/set initialization on read. Update the reference documentation about collection initialization on read, add the required tests to make sure it behaves as expected and simplify BeanUtils value presence check. Closes #4571 Original pull request: #4574 --- .../data/mongodb/util/BsonUtils.java | 2 +- .../MappingMongoConverterUnitTests.java | 78 +++++++++++++++++++ .../ROOT/pages/mongodb/mapping/mapping.adoc | 11 +++ 3 files changed, 90 insertions(+), 1 deletion(-) 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 eebd948a1..331cbb2e8 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 class BsonUtils { 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 47c7a0027..9815ce23b 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.LocalDate; 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.Disabled; 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 @@ class MappingMongoConverterUnitTests { 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 @@ class MappingMongoConverterUnitTests { 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 @@ class MappingMongoConverterUnitTests { 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 @@ class MappingMongoConverterUnitTests { 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 4d4ddb62f..462b2f5b2 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