Browse Source

DATAJDBC-374 - Add onEmpty attribute to Embedded annotation.

The onEmpty attribute allows to define if an embedded entity should be set to null or a default instance if all properties backing the entity are actually null.

    @Embedded(onEmpty = USE_NULL)
    EmbeddedEntity embeddedEntity;

Original pull request: #154.
pull/156/head
Christoph Strobl 7 years ago committed by Jens Schauder
parent
commit
5830b83cf4
  1. 7
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java
  2. 7
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java
  3. 46
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java
  4. 11
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java
  5. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java
  6. 7
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java
  7. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java
  8. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java
  9. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java
  10. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java
  11. 31
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java
  12. 3
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java
  13. 5
      spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java
  14. 10
      src/main/asciidoc/jdbc.adoc

7
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java

@ -37,6 +37,8 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
import org.springframework.data.relational.core.conversion.RelationalConverter; 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.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@ -375,6 +377,11 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
private Object readEmbeddedEntityFrom(@Nullable Object idValue, RelationalPersistentProperty property) { private Object readEmbeddedEntityFrom(@Nullable Object idValue, RelationalPersistentProperty property) {
ReadingContext<?> newContext = extendBy(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; return newContext.hasInstanceValues(idValue) ? newContext.createInstanceInternal(idValue) : null;
} }

7
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java

@ -23,6 +23,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.Embedded; 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.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@ -217,7 +218,7 @@ public class PersistentPropertyPathExtensionUnitTests {
static class DummyEntity { static class DummyEntity {
@Id Long entityId; @Id Long entityId;
Second second; Second second;
@Embedded("sec") Second second2; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "sec") Second second2;
List<Second> secondList; List<Second> secondList;
WithId withId; WithId withId;
} }
@ -225,7 +226,7 @@ public class PersistentPropertyPathExtensionUnitTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
static class Second { static class Second {
Third third; Third third;
@Embedded("thrd") Third third2; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "thrd") Third third2;
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -237,7 +238,7 @@ public class PersistentPropertyPathExtensionUnitTests {
static class WithId { static class WithId {
@Id Long withIdId; @Id Long withIdId;
Second second; Second second;
@Embedded("sec") Second second2; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "sec") Second second2;
} }
} }

46
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java

@ -54,6 +54,7 @@ import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.Embedded; 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.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
@ -295,13 +296,13 @@ public class EntityRowMapperUnitTests {
} }
@Test // DATAJDBC-370 @Test // DATAJDBC-370
public void simpleImmutableEmbeddedGetsProperlyExtracted() throws SQLException { public void simpleNullableImmutableEmbeddedGetsProperlyExtracted() throws SQLException {
ResultSet rs = mockResultSet(asList("id", "value"), // ResultSet rs = mockResultSet(asList("id", "value"), //
ID_FOR_ENTITY_NOT_REFERENCING_MAP, "ru'Ha'"); ID_FOR_ENTITY_NOT_REFERENCING_MAP, "ru'Ha'");
rs.next(); rs.next();
WithImmutableValue extracted = createRowMapper(WithImmutableValue.class).mapRow(rs, 1); WithNullableEmbeddedImmutableValue extracted = createRowMapper(WithNullableEmbeddedImmutableValue.class).mapRow(rs, 1);
assertThat(extracted) // assertThat(extracted) //
.isNotNull() // .isNotNull() //
@ -309,6 +310,21 @@ public class EntityRowMapperUnitTests {
.containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, new ImmutableValue("ru'Ha'")); .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 @Test // DATAJDBC-370
@SneakyThrows @SneakyThrows
public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() { public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() {
@ -317,7 +333,7 @@ public class EntityRowMapperUnitTests {
ID_FOR_ENTITY_NOT_REFERENCING_MAP, 24); ID_FOR_ENTITY_NOT_REFERENCING_MAP, 24);
rs.next(); rs.next();
WithPrimitiveImmutableValue extracted = createRowMapper(WithPrimitiveImmutableValue.class).mapRow(rs, 1); WithEmbeddedPrimitiveImmutableValue extracted = createRowMapper(WithEmbeddedPrimitiveImmutableValue.class).mapRow(rs, 1);
assertThat(extracted) // assertThat(extracted) //
.isNotNull() // .isNotNull() //
@ -332,7 +348,7 @@ public class EntityRowMapperUnitTests {
ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); ID_FOR_ENTITY_NOT_REFERENCING_MAP, null);
rs.next(); rs.next();
WithImmutableValue extracted = createRowMapper(WithImmutableValue.class).mapRow(rs, 1); WithNullableEmbeddedImmutableValue extracted = createRowMapper(WithNullableEmbeddedImmutableValue.class).mapRow(rs, 1);
assertThat(extracted) // assertThat(extracted) //
.isNotNull() // .isNotNull() //
@ -380,7 +396,7 @@ public class EntityRowMapperUnitTests {
ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); ID_FOR_ENTITY_NOT_REFERENCING_MAP, null);
rs.next(); rs.next();
WithPrimitiveImmutableValue extracted = createRowMapper(WithPrimitiveImmutableValue.class).mapRow(rs, 1); WithEmbeddedPrimitiveImmutableValue extracted = createRowMapper(WithEmbeddedPrimitiveImmutableValue.class).mapRow(rs, 1);
assertThat(extracted) // assertThat(extracted) //
.isNotNull() // .isNotNull() //
@ -465,7 +481,7 @@ public class EntityRowMapperUnitTests {
@Id Long id; @Id Long id;
String name; String name;
@Embedded("prefix_") Trivial children; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Trivial children;
} }
private static class DontUseSetter { private static class DontUseSetter {
@ -533,16 +549,22 @@ public class EntityRowMapperUnitTests {
NoIdChain3 chain3; NoIdChain3 chain3;
} }
static class WithImmutableValue { static class WithNullableEmbeddedImmutableValue {
@Id Long id;
@Embedded(onEmpty = OnEmpty.USE_NULL) ImmutableValue embeddedImmutableValue;
}
static class WithEmptyEmbeddedImmutableValue {
@Id Long id; @Id Long id;
@Embedded ImmutableValue embeddedImmutableValue; @Embedded(onEmpty = OnEmpty.USE_EMPTY) ImmutableValue embeddedImmutableValue;
} }
static class WithPrimitiveImmutableValue { static class WithEmbeddedPrimitiveImmutableValue {
@Id Long id; @Id Long id;
@Embedded ImmutablePrimitiveValue embeddedImmutablePrimitiveValue; @Embedded(onEmpty = OnEmpty.USE_NULL) ImmutablePrimitiveValue embeddedImmutablePrimitiveValue;
} }
@Value @Value
@ -559,13 +581,13 @@ public class EntityRowMapperUnitTests {
@Id Long id; @Id Long id;
String level0; String level0;
@Embedded("level1_") EmbeddedWithEmbedded level1; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "level1_") EmbeddedWithEmbedded level1;
} }
static class EmbeddedWithEmbedded { static class EmbeddedWithEmbedded {
Object value; Object value;
@Embedded("level2_") ImmutableValue level2; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "level2_") ImmutableValue level2;
} }
// Infrastructure for assertions and constructing mocks // Infrastructure for assertions and constructing mocks

11
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java

@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Embedded; 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.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
@ -245,16 +246,16 @@ public class SqlGeneratorEmbeddedUnitTests {
@Column("id1") @Id Long id; @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") @SuppressWarnings("unused")
static class CascadedEmbedded { static class CascadedEmbedded {
String test; String test;
@Embedded("prefix2_") Embeddable prefixedEmbeddable; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix2_") Embeddable prefixedEmbeddable;
@Embedded Embeddable embeddable; @Embedded(onEmpty = OnEmpty.USE_NULL) Embeddable embeddable;
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -268,7 +269,7 @@ public class SqlGeneratorEmbeddedUnitTests {
@Id Long id; @Id Long id;
@Embedded("prefix_") EmbeddedWithReference embedded; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") EmbeddedWithReference embedded;
} }
static class EmbeddedWithReference { static class EmbeddedWithReference {

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java

@ -33,6 +33,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.relational.core.mapping.Embedded; 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.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@ -99,7 +100,7 @@ public class JdbcRepositoryEmbeddedImmutableIntegrationTests {
@Id Long id; @Id Long id;
@Embedded("prefix_") Embeddable prefixedEmbeddable; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Embeddable prefixedEmbeddable;
} }
@Value @Value

7
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java

@ -31,6 +31,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.relational.core.mapping.Embedded; 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.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@ -250,16 +251,16 @@ public class JdbcRepositoryEmbeddedIntegrationTests {
@Id Long id; @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 @Data
static class CascadedEmbeddable { static class CascadedEmbeddable {
String test; String test;
@Embedded("prefix2_") @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix2_")
Embeddable embeddable; Embeddable embeddable;
} }

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java

@ -32,6 +32,7 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Embedded; 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.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@ -246,7 +247,7 @@ public class JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests {
String test; String test;
@Embedded("prefix_") @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_")
Embeddable embeddable; Embeddable embeddable;
} }

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java

@ -32,6 +32,7 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Embedded; 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.relational.core.mapping.MappedCollection;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@ -249,7 +250,7 @@ public class JdbcRepositoryEmbeddedWithCollectionIntegrationTests {
String test; String test;
@Embedded("prefix_") @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_")
Embeddable embeddable; Embeddable embeddable;
} }

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java

@ -32,6 +32,7 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Embedded; 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.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@ -236,7 +237,7 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests {
String test; String test;
@Embedded("prefix_") @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_")
Embeddable embeddable; Embeddable embeddable;
} }

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java

@ -84,7 +84,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
this.isEmbedded = Lazy.of(() -> Optional.ofNullable(findAnnotation(Embedded.class)).isPresent()); this.isEmbedded = Lazy.of(() -> Optional.ofNullable(findAnnotation(Embedded.class)).isPresent());
this.embeddedPrefix = Lazy.of(() -> Optional.ofNullable(findAnnotation(Embedded.class)) // this.embeddedPrefix = Lazy.of(() -> Optional.ofNullable(findAnnotation(Embedded.class)) //
.map(Embedded::value) // .map(Embedded::prefix) //
.orElse("")); .orElse(""));
this.columnName = Lazy.of(() -> Optional.ofNullable(findAnnotation(Column.class)) // this.columnName = Lazy.of(() -> Optional.ofNullable(findAnnotation(Column.class)) //

31
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java

@ -23,15 +23,38 @@ import java.lang.annotation.Target;
/** /**
* The annotation to configure a value object as embedded in the current table. * The annotation to configure a value object as embedded in the current table.
* <p />
* 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 Bastian Wilhelm
* @author Christoph Strobl
* @since 1.1
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented @Documented
public @interface Embedded { public @interface Embedded {
/**
* @return prefix for columns in the embedded value object. Default is an empty String /**
*/ * Set the load strategy for the embedded object if all contained fields yield {@literal null} values.
String value() default ""; *
* @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
}
} }

3
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java

@ -39,6 +39,7 @@ import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot; 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;
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -588,7 +589,7 @@ public class RelationalEntityWriterUnitTests {
static class EmbeddedReferenceEntity { static class EmbeddedReferenceEntity {
@Id final Long id; @Id final Long id;
@Embedded("prefix_") Element other; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") Element other;
} }
@RequiredArgsConstructor @RequiredArgsConstructor

5
spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java

@ -30,6 +30,7 @@ import org.assertj.core.api.SoftAssertions;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
/** /**
* Unit tests for the {@link BasicRelationalPersistentProperty}. * Unit tests for the {@link BasicRelationalPersistentProperty}.
@ -188,10 +189,10 @@ public class BasicRelationalPersistentPropertyUnitTests {
private @Column("dummy_name") String name; private @Column("dummy_name") String name;
// DATAJDBC-111 // DATAJDBC-111
private @Embedded EmbeddableEntity embeddableEntity; private @Embedded(onEmpty = OnEmpty.USE_NULL) EmbeddableEntity embeddableEntity;
// DATAJDBC-111 // DATAJDBC-111
private @Embedded("prefix") EmbeddableEntity prefixedEmbeddableEntity; private @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix") EmbeddableEntity prefixedEmbeddableEntity;
@Column("dummy_last_updated_at") @Column("dummy_last_updated_at")
public LocalDateTime getLocalDateTime() { public LocalDateTime getLocalDateTime() {

10
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. 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. 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] [source, java]
---- ----
public class MyEntity { public class MyEntity {
@Id @Id
Integer id; Integer id;
@Embedded @Embedded(onEmpty = USE_NULL) <1>
EmbeddedEntity embeddedEntity; EmbeddedEntity embeddedEntity;
} }
@ -303,9 +308,10 @@ public class EmbeddedEntity {
String name; 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. This element represents a prefix and is prepend for each column name in the embedded object.
[[jdbc.entity-persistence.state-detection-strategies]] [[jdbc.entity-persistence.state-detection-strategies]]

Loading…
Cancel
Save