From 4154c4288203d7401dabb28f945a7e8f933e5ce8 Mon Sep 17 00:00:00 2001 From: divya_jnu08 Date: Sun, 9 May 2021 16:16:43 +0530 Subject: [PATCH 1/2] Issue - 3407 Add an option to @Field annoatation to omit null values on write --- .../mongodb/core/convert/MappingMongoConverter.java | 7 +++++-- .../core/mapping/BasicMongoPersistentProperty.java | 11 +++++++++++ .../data/mongodb/core/mapping/Field.java | 10 ++++++++++ .../core/mapping/MongoPersistentProperty.java | 9 +++++++++ .../mapping/UnwrappedMongoPersistentProperty.java | 5 +++++ .../BasicMongoPersistentPropertyUnitTests.java | 13 +++++++++++++ 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 4a2a7fc152..528f3cd304 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -654,10 +654,13 @@ private void writeProperties(Bson bson, MongoPersistentEntity entity, Persist Object value = accessor.getProperty(prop); - if (value == null) { + if(value==null) { + if(!prop.isOmitNullProperty()) { + writeSimpleInternal(value, bson , prop); + } continue; } - + if (!conversions.isSimpleType(value.getClass())) { writePropertyInternal(value, dbObjectAccessor, prop); } else { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java index 0b47c79d04..2b5e02ead7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java @@ -266,4 +266,15 @@ public boolean isExplicitLanguageProperty() { public boolean isTextScoreProperty() { return isAnnotationPresent(TextScore.class); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isOmitNullProperty() + */ + public boolean isOmitNullProperty() { + org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation( + org.springframework.data.mongodb.core.mapping.Field.class); + + return annotation != null ? annotation.omitNull() : true; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java index 45e8b815fb..4efadc0541 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java @@ -65,4 +65,14 @@ * @since 2.2 */ FieldType targetType() default FieldType.IMPLICIT; + + /** + * If set to {@literal true} {@literal null} values will be omitted. + * Setting the value to {@literal false} explicitly adds an entry for the given field + * holding {@literal null} as a value {@code 'fieldName' : null }. + *

+ * NOTE Setting the value to {@literal false} may lead to increased document size. + * @return {@literal true} by default. + */ + boolean omitNull() default true; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java index 7c347229b6..33ecd95eb3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java @@ -96,6 +96,15 @@ public interface MongoPersistentProperty extends PersistentProperty + * It's annotated with {@link omitNull}. + * + * @return + * @since 1.6 + */ + boolean isOmitNullProperty(); + /** * Returns the {@link DBRef} if the property is a reference. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java index 7dfafc1202..4dcd4fc23c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java @@ -87,6 +87,11 @@ public boolean isExplicitLanguageProperty() { public boolean isTextScoreProperty() { return delegate.isTextScoreProperty(); } + + @Override + public boolean isOmitNullProperty() { + return delegate.isOmitNullProperty(); + } @Override @Nullable diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index 3fb4f59084..16c64d7d13 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -145,6 +145,13 @@ public void shouldDetectTextScorePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithTextScoreProperty.class, "score"); assertThat(property.isTextScoreProperty()).isTrue(); } + + @Test // DATAMONGO-2551 + public void shouldDetectOmitNullPropertyCorrectly() { + + MongoPersistentProperty property = getPropertyFor(DocumentWithOmitNullProperty.class, "omitNull"); + assertThat(property.isOmitNullProperty()).isTrue(); + } @Test // DATAMONGO-976 public void shouldDetectTextScoreAsReadOnlyProperty() { @@ -297,6 +304,12 @@ static class DocumentWithTextScoreProperty { @TextScore Float score; } + static class DocumentWithOmitNullProperty { + + @org.springframework.data.mongodb.core.mapping.Field("omitNull") boolean omitNull; + + } + static class DocumentWithExplicitlyRenamedIdProperty { @org.springframework.data.mongodb.core.mapping.Field("id") String id; From 7b5c8ec01be3044cd17cf1ff1be7d9013a8d7b74 Mon Sep 17 00:00:00 2001 From: divya srivastava Date: Sat, 12 Jun 2021 16:52:56 +0530 Subject: [PATCH 2/2] review comments incorporated. --- .../core/convert/MappingMongoConverter.java | 3 +++ .../mapping/BasicMongoPersistentProperty.java | 14 +++++++++++ .../data/mongodb/core/mapping/Field.java | 25 +++++++++++++++++++ .../core/mapping/MongoPersistentProperty.java | 9 +++++++ .../UnwrappedMongoPersistentProperty.java | 5 ++++ ...BasicMongoPersistentPropertyUnitTests.java | 13 ++++++++++ 6 files changed, 69 insertions(+) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index bcb9575b92..86be47c558 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -744,6 +744,9 @@ private void writeProperties(Bson bson, MongoPersistentEntity entity, Persist Object value = accessor.getProperty(prop); if (value == null) { + if(!prop.isPropertyOmittableOnNull()) { + writeSimpleInternal(value, bson , prop); + } continue; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java index 53af00fc54..5182ba66e1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java @@ -285,4 +285,18 @@ public boolean isExplicitLanguageProperty() { public boolean isTextScoreProperty() { return isAnnotationPresent(TextScore.class); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isPropertyOmittableOnNull() + */ + public boolean isPropertyOmittableOnNull() { + org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation( + org.springframework.data.mongodb.core.mapping.Field.class); + + if ( annotation != null && annotation.write().equals(Field.Write.ALWAYS) ) { + return false; + } + return true; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java index 45e8b815fb..fdca4b718c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Field.java @@ -33,6 +33,21 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface Field { + + /** + * Enumeration of write strategies for a field with null value.It decides whether a field with null value has to be + * written to the resulting document to be saved to the database. + */ + enum Write{ + /* + * The field will always be written to the database irrespective of null value. + */ + ALWAYS, + /* + * The field will only be written to the database if it has a non null value. + */ + NON_NULL + } /** * The key to be used to store the field inside the document. Alias for {@link #name()}. @@ -65,4 +80,14 @@ * @since 2.2 */ FieldType targetType() default FieldType.IMPLICIT; + + /** + * If set to {@link Write#NON_NULL} {@literal null} values will be omitted. + * Setting the value to {@link Write#ALWAYS} explicitly adds an entry for the given field + * holding {@literal null} as a value {@code 'fieldName' : null }. + *

+ * NOTE Setting the value to {@link Write#ALWAYS} may lead to increased document size. + * @return {@link Write#NON_NULL} by default. + */ + Write write() default Write.NON_NULL; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java index c753f3856d..ae382d1d66 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java @@ -104,6 +104,15 @@ public interface MongoPersistentProperty extends PersistentProperty + * It's annotated with {@link Field.Write}. + * + * @return + * @since 1.6 + */ + boolean isPropertyOmittableOnNull(); /** * Returns the {@link DBRef} if the property is a reference. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java index f8218171c5..5344762c03 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/UnwrappedMongoPersistentProperty.java @@ -315,4 +315,9 @@ public Class getAssociationTargetType() { public PersistentPropertyAccessor getAccessorForOwner(T owner) { return delegate.getAccessorForOwner(owner); } + + @Override + public boolean isPropertyOmittableOnNull() { + return delegate.isPropertyOmittableOnNull(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index 3fb4f59084..7c78781e70 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -145,6 +145,13 @@ public void shouldDetectTextScorePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithTextScoreProperty.class, "score"); assertThat(property.isTextScoreProperty()).isTrue(); } + + @Test // DATAMONGO-2551 + public void shouldDetectOmittableOnNullPropertyCorrectly() { + + MongoPersistentProperty property = getPropertyFor(DocumentWithOmittableOnNullProperty.class, "write"); + assertThat(property.isPropertyOmittableOnNull()).isTrue(); + } @Test // DATAMONGO-976 public void shouldDetectTextScoreAsReadOnlyProperty() { @@ -296,6 +303,12 @@ static class DocumentWithImplicitLanguageProperty { static class DocumentWithTextScoreProperty { @TextScore Float score; } + + static class DocumentWithOmittableOnNullProperty { + + @org.springframework.data.mongodb.core.mapping.Field("write") org.springframework.data.mongodb.core.mapping.Field.Write write; + + } static class DocumentWithExplicitlyRenamedIdProperty {