diff --git a/pom.xml b/pom.xml index 680dddf736..973266e739 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-374-SNAPSHOT pom Spring Data Relational Parent @@ -34,6 +34,7 @@ 42.0.0 2.2.3 1.9.1 + 3.0.2 2017 diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index f6d4373844..f7e15bb6be 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-374-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index cfb1e9443b..09c9fc97e6 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-jdbc - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-374-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-374-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 70289e008f..d455b35522 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -36,6 +36,8 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -352,6 +354,11 @@ private Object readFrom(RelationalPersistentProperty property) { private Object readEmbeddedEntityFrom(@Nullable Object idValue, RelationalPersistentProperty property) { ReadingContext newContext = extendBy(property); + + if(OnEmpty.USE_EMPTY.equals(property.findAnnotation(Embedded.class).onEmpty())) { + return newContext.createInstanceInternal(idValue); + } + return newContext.hasInstanceValues(idValue) ? newContext.createInstanceInternal(idValue) : null; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java index 24170e350d..1c60da9862 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java @@ -23,6 +23,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -217,7 +218,7 @@ PersistentPropertyPath createSimplePath(String pat static class DummyEntity { @Id Long entityId; Second second; - @Embedded("sec") Second second2; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "sec") Second second2; List secondList; WithId withId; } @@ -225,7 +226,7 @@ static class DummyEntity { @SuppressWarnings("unused") static class Second { Third third; - @Embedded("thrd") Third third2; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "thrd") Third third2; } @SuppressWarnings("unused") @@ -237,7 +238,7 @@ static class Third { static class WithId { @Id Long withIdId; Second second; - @Embedded("sec") Second second2; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "sec") Second second2; } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java index 2004a3357c..6be6a99e73 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java @@ -51,6 +51,7 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -291,13 +292,13 @@ public void chainedEntitiesWithoutId() throws SQLException { } @Test // DATAJDBC-370 - public void simpleImmutableEmbeddedGetsProperlyExtracted() throws SQLException { + public void simpleNullableImmutableEmbeddedGetsProperlyExtracted() throws SQLException { ResultSet rs = mockResultSet(asList("id", "value"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "ru'Ha'"); rs.next(); - WithImmutableValue extracted = createRowMapper(WithImmutableValue.class).mapRow(rs, 1); + WithNullableEmbeddedImmutableValue extracted = createRowMapper(WithNullableEmbeddedImmutableValue.class).mapRow(rs, 1); assertThat(extracted) // .isNotNull() // @@ -305,6 +306,21 @@ public void simpleImmutableEmbeddedGetsProperlyExtracted() throws SQLException { .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, new ImmutableValue("ru'Ha'")); } + @Test // DATAJDBC-374 + public void simpleEmptyImmutableEmbeddedGetsProperlyExtracted() throws SQLException { + + ResultSet rs = mockResultSet(asList("id", "value"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); + rs.next(); + + WithEmptyEmbeddedImmutableValue extracted = createRowMapper(WithEmptyEmbeddedImmutableValue.class).mapRow(rs, 1); + + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.embeddedImmutableValue) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, new ImmutableValue(null)); + } + @Test // DATAJDBC-370 @SneakyThrows public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() { @@ -313,7 +329,7 @@ public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() { ID_FOR_ENTITY_NOT_REFERENCING_MAP, 24); rs.next(); - WithPrimitiveImmutableValue extracted = createRowMapper(WithPrimitiveImmutableValue.class).mapRow(rs, 1); + WithEmbeddedPrimitiveImmutableValue extracted = createRowMapper(WithEmbeddedPrimitiveImmutableValue.class).mapRow(rs, 1); assertThat(extracted) // .isNotNull() // @@ -328,7 +344,7 @@ public void simpleImmutableEmbeddedShouldBeNullIfAllOfTheEmbeddableAreNull() thr ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); rs.next(); - WithImmutableValue extracted = createRowMapper(WithImmutableValue.class).mapRow(rs, 1); + WithNullableEmbeddedImmutableValue extracted = createRowMapper(WithNullableEmbeddedImmutableValue.class).mapRow(rs, 1); assertThat(extracted) // .isNotNull() // @@ -376,7 +392,7 @@ public void primitiveEmbeddedShouldBeNullWhenNoValuePresent() { ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); rs.next(); - WithPrimitiveImmutableValue extracted = createRowMapper(WithPrimitiveImmutableValue.class).mapRow(rs, 1); + WithEmbeddedPrimitiveImmutableValue extracted = createRowMapper(WithEmbeddedPrimitiveImmutableValue.class).mapRow(rs, 1); assertThat(extracted) // .isNotNull() // @@ -460,7 +476,7 @@ static class EmbeddedEntity { @Id Long id; String name; - @Embedded("prefix_") Trivial children; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Trivial children; } private static class DontUseSetter { @@ -528,16 +544,22 @@ static class NoIdChain4 { NoIdChain3 chain3; } - static class WithImmutableValue { + static class WithNullableEmbeddedImmutableValue { + + @Id Long id; + @Embedded(onEmpty = OnEmpty.USE_NULL) ImmutableValue embeddedImmutableValue; + } + + static class WithEmptyEmbeddedImmutableValue { @Id Long id; - @Embedded ImmutableValue embeddedImmutableValue; + @Embedded.Empty ImmutableValue embeddedImmutableValue; } - static class WithPrimitiveImmutableValue { + static class WithEmbeddedPrimitiveImmutableValue { @Id Long id; - @Embedded ImmutablePrimitiveValue embeddedImmutablePrimitiveValue; + @Embedded.Nullable ImmutablePrimitiveValue embeddedImmutablePrimitiveValue; } @Value @@ -554,13 +576,13 @@ static class WithDeepNestedEmbeddable { @Id Long id; String level0; - @Embedded("level1_") EmbeddedWithEmbedded level1; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "level1_") EmbeddedWithEmbedded level1; } static class EmbeddedWithEmbedded { Object value; - @Embedded("level2_") ImmutableValue level2; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "level2_") ImmutableValue level2; } // Infrastructure for assertions and constructing mocks diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java index 33e4f379fb..fb5481ab40 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java @@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -245,16 +246,16 @@ static class DummyEntity { @Column("id1") @Id Long id; - @Embedded("prefix_") CascadedEmbedded prefixedEmbeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") CascadedEmbedded prefixedEmbeddable; - @Embedded CascadedEmbedded embeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL) CascadedEmbedded embeddable; } @SuppressWarnings("unused") static class CascadedEmbedded { String test; - @Embedded("prefix2_") Embeddable prefixedEmbeddable; - @Embedded Embeddable embeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix2_") Embeddable prefixedEmbeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL) Embeddable embeddable; } @SuppressWarnings("unused") @@ -268,7 +269,7 @@ static class DummyEntity2 { @Id Long id; - @Embedded("prefix_") EmbeddedWithReference embedded; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") EmbeddedWithReference embedded; } static class EmbeddedWithReference { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java index 96f4d82057..028fcfd4ec 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java @@ -33,6 +33,7 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -99,7 +100,7 @@ static class DummyEntity { @Id Long id; - @Embedded("prefix_") Embeddable prefixedEmbeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Embeddable prefixedEmbeddable; } @Value diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java index 924c728972..703b08fee9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java @@ -31,6 +31,7 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -250,16 +251,16 @@ static class DummyEntity { @Id Long id; - @Embedded("prefix_") CascadedEmbeddable prefixedEmbeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") CascadedEmbeddable prefixedEmbeddable; - @Embedded CascadedEmbeddable embeddable; + @Embedded(onEmpty = OnEmpty.USE_NULL) CascadedEmbeddable embeddable; } @Data static class CascadedEmbeddable { String test; - @Embedded("prefix2_") + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix2_") Embeddable embeddable; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java index d726a80279..d4fd91a9be 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -246,7 +247,7 @@ static class DummyEntity2 { String test; - @Embedded("prefix_") + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Embeddable embeddable; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java index 3a3d81f28d..3561e0129e 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.MappedCollection; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; @@ -249,7 +250,7 @@ private static class DummyEntity { String test; - @Embedded("prefix_") + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Embeddable embeddable; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java index e37dbeae6b..c219759faa 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -236,7 +237,7 @@ private static class DummyEntity { String test; - @Embedded("prefix_") + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Embeddable embeddable; } diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index ba0ffea143..50a121510d 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-relational - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-374-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -13,7 +13,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-374-SNAPSHOT @@ -48,6 +48,13 @@ spring-core + + com.google.code.findbugs + jsr305 + ${jsr305.version} + true + + org.assertj assertj-core diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java index fd2638bdf9..47a1f94be3 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java @@ -84,7 +84,7 @@ public BasicRelationalPersistentProperty(Property property, PersistentEntity Optional.ofNullable(findAnnotation(Embedded.class)).isPresent()); this.embeddedPrefix = Lazy.of(() -> Optional.ofNullable(findAnnotation(Embedded.class)) // - .map(Embedded::value) // + .map(Embedded::prefix) // .orElse("")); this.columnName = Lazy.of(() -> Optional.ofNullable(findAnnotation(Column.class)) // diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java index b7acab31b9..4e83767a50 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java @@ -21,17 +21,126 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.annotation.meta.When; + +import org.springframework.core.annotation.AliasFor; + /** * The annotation to configure a value object as embedded in the current table. + *

+ * Depending on the {@link OnEmpty value} of {@link #onEmpty()} the property is set to {@literal null} or an empty + * instance in the case all embedded values are {@literal null} when reading from the result set. * * @author Bastian Wilhelm + * @author Christoph Strobl + * @since 1.1 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Documented public @interface Embedded { - /** - * @return prefix for columns in the embedded value object. Default is an empty String - */ - String value() default ""; + + /** + * Set the load strategy for the embedded object if all contained fields yield {@literal null} values. + *

+ * {@link Nullable @Embedded.Nullable} and {@link Empty @Embedded.Empty} offer shortcuts for this. + * + * @return never {@link} null. + */ + OnEmpty onEmpty(); + + /** + * @return prefix for columns in the embedded value object. An empty {@link String} by default. + */ + String prefix() default ""; + + /** + * Load strategy to be used {@link Embedded#onEmpty()}. + * + * @author Christoph Strobl + * @since 1.1 + */ + enum OnEmpty { + USE_NULL, USE_EMPTY + } + + /** + * Shortcut for a nullable embedded property. + * + *

+	 * 
+	 * @Embedded.Nullable
+	 * private Address address;
+	 * 
+	 * 
+ * + * as alternative to the more verbose + * + *
+	 * 
+	 *
+	 * @Embedded(onEmpty = USE_NULL)
+	 * @javax.annotation.Nonnull(when = When.MAYBE)
+	 * private Address address;
+	 *
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.1 + * @see Embedded#onEmpty() + */ + @Embedded(onEmpty = OnEmpty.USE_NULL) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD, ElementType.METHOD }) + @javax.annotation.Nonnull(when = When.MAYBE) + @interface Nullable { + + /** + * @return prefix for columns in the embedded value object. An empty {@link String} by default. + */ + @AliasFor(annotation = Embedded.class, attribute = "prefix") + String prefix() default ""; + } + + /** + * Shortcut for an empty embedded property. + * + *
+	 * 
+	 * @Embedded.Empty
+	 * private Address address;
+	 * 
+	 * 
+ * + * as alternative to the more verbose + * + *
+	 * 
+	 *
+	 * @Embedded(onEmpty = USE_EMPTY)
+	 * @javax.annotation.Nonnull(when = When.NEVER)
+	 * private Address address;
+	 *
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.1 + * @see Embedded#onEmpty() + */ + @Embedded(onEmpty = OnEmpty.USE_EMPTY) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD, ElementType.METHOD }) + @javax.annotation.Nonnull(when = When.NEVER) + @interface Empty { + + /** + * @return prefix for columns in the embedded value object. An empty {@link String} by default. + */ + @AliasFor(annotation = Embedded.class, attribute = "prefix") + String prefix() default ""; + } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java index c196d65942..8bb3039047 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java @@ -38,6 +38,7 @@ import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot; import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -464,7 +465,7 @@ static class SingleReferenceEntity { static class EmbeddedReferenceEntity { @Id final Long id; - @Embedded("prefix_") Element other; + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Element other; } @RequiredArgsConstructor diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java index 7ab36ef8e3..4b7954ac8c 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; /** * Unit tests for the {@link BasicRelationalPersistentProperty}. @@ -188,10 +189,10 @@ private static class DummyEntity { private @Column("dummy_name") String name; // DATAJDBC-111 - private @Embedded EmbeddableEntity embeddableEntity; + private @Embedded(onEmpty = OnEmpty.USE_NULL) EmbeddableEntity embeddableEntity; // DATAJDBC-111 - private @Embedded("prefix") EmbeddableEntity prefixedEmbeddableEntity; + private @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix") EmbeddableEntity prefixedEmbeddableEntity; @Column("dummy_last_updated_at") public LocalDateTime getLocalDateTime() { diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index 7d6e0f7643..77d9761084 100644 --- a/src/main/asciidoc/jdbc.adoc +++ b/src/main/asciidoc/jdbc.adoc @@ -288,14 +288,19 @@ Embedded entities are used to have value objects in your java data model, even i In the following example you see, that `MyEntity` is mapped with the `@Embedded` annotation. The consequence of this is, that in the database a table `my_entity` with the two columns `id` and `name` (from the `EmbeddedEntity` class) is expected. +However, if the `name` column is actually `null` within the result set, the entire property `embeddedEntity` will be set to null according to the `onEmpty` of `@Embedded`, which ``null``s objects when all nested properties are `null`. + +Opposite to this behavior `USE_EMPTY` tries to create a new instance using either a default constructor or one that accepts nullable parameter values from the result set. + +.Sample Code of embedding objects ==== [source, java] ---- public class MyEntity { + @Id Integer id; - @Embedded + @Embedded(onEmpty = USE_NULL) <1> EmbeddedEntity embeddedEntity; } @@ -303,11 +308,30 @@ public class EmbeddedEntity { String name; } ---- +<1> ``Null``s `embeddedEntity` if `name` in `null`. Use `USE_EMPTY` to instanciate `embeddedEntity` with a potential `null` value for the `name` property. ==== -If you need a value object multiple times in an entity, this can be achieved with the optional `value` element of the `@Embedded` annotation. +If you need a value object multiple times in an entity, this can be achieved with the optional `prefix` element of the `@Embedded` annotation. This element represents a prefix and is prepend for each column name in the embedded object. +[TIP] +==== +Make use of the shortcuts `@Embedded.Nullable` & `@Embedded.Empty` for `@Embedded(onEmpty = USE_NULL)` and `@Embedded(onEmpty = USE_EMPTY)` to reduce verbositility and simultaneously set JSR-305 `@javax.annotation.Nonnull` accordingly. + +[source, java] +---- +public class MyEntity { + + @Id + Integer id; + + @Embedded.Nullable <1> + EmbeddedEntity embeddedEntity; +} +---- +<1> Shortcut for `@Embedded(onEmpty = USE_NULL)`. +==== + [[jdbc.entity-persistence.state-detection-strategies]] === Entity State Detection Strategies