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,
this.isEmbedded = Lazy.of(() -> 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