diff --git a/pom.xml b/pom.xml index 680dddf736..d147ba7d53 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-370-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index f6d4373844..8945528d0d 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-370-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index cfb1e9443b..9ed7427d84 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-370-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-370-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 7b9a48f33f..70289e008f 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 @@ -30,7 +30,6 @@ import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.MappingException; -import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.context.MappingContext; @@ -53,6 +52,7 @@ * * @author Mark Paluch * @author Jens Schauder + * @author Christoph Strobl * @see MappingContext * @see SimpleTypeHolder * @see CustomConversions @@ -285,30 +285,22 @@ public ReadingContext(RelationalPersistentEntity entity, DataAccessStrategy a } private ReadingContext extendBy(RelationalPersistentProperty property) { - return new ReadingContext<>(entity, accessStrategy, resultSet, path.extendBy(property)); + return new ReadingContext(getMappingContext().getRequiredPersistentEntity(property.getActualType()), + accessStrategy, resultSet, path.extendBy(property)); } T mapRow() { RelationalPersistentProperty idProperty = entity.getIdProperty(); - Object idValue = null; - if (idProperty != null) { - idValue = readFrom(idProperty); - } + Object idValue = idProperty == null ? null : readFrom(idProperty); - T result = createInstanceInternal(entity, idValue); - - return entity.requiresPropertyPopulation() // - ? populateProperties(result) // - : result; + return createInstanceInternal(idValue); } - private T populateProperties(T result) { - - PersistentPropertyAccessor propertyAccessor = getPropertyAccessor(entity, result); + private T populateProperties(T instance, @Nullable Object idValue) { - Object id = idProperty == null ? null : readFrom(idProperty); + PersistentPropertyAccessor propertyAccessor = getPropertyAccessor(entity, instance); PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); @@ -318,7 +310,7 @@ private T populateProperties(T result) { continue; } - propertyAccessor.setProperty(property, readOrLoadProperty(id, property)); + propertyAccessor.setProperty(property, readOrLoadProperty(idValue, property)); } return propertyAccessor.getBean(); @@ -354,31 +346,38 @@ private Object readFrom(RelationalPersistentProperty property) { Object value = getObjectFromResultSet(path.extendBy(property).getColumnAlias()); return readValue(value, property.getTypeInformation()); - } - @SuppressWarnings("unchecked") - private Object readEmbeddedEntityFrom(@Nullable Object id, RelationalPersistentProperty property) { + @Nullable + private Object readEmbeddedEntityFrom(@Nullable Object idValue, RelationalPersistentProperty property) { - ReadingContext newContext = extendBy(property); + ReadingContext newContext = extendBy(property); + return newContext.hasInstanceValues(idValue) ? newContext.createInstanceInternal(idValue) : null; + } - RelationalPersistentEntity entity = getMappingContext().getRequiredPersistentEntity(property.getActualType()); + private boolean hasInstanceValues(Object idValue) { - Object instance = newContext.createInstanceInternal(entity, null); + RelationalPersistentEntity persistentEntity = path.getLeafEntity(); - PersistentPropertyAccessor accessor = getPropertyAccessor((PersistentEntity) entity, instance); + for (RelationalPersistentProperty embeddedProperty : persistentEntity) { - for (RelationalPersistentProperty p : entity) { - accessor.setProperty(p, newContext.readOrLoadProperty(id, p)); + // if the embedded contains Lists, Sets or Maps we consider it non-empty + if (embeddedProperty.isQualified() || embeddedProperty.isReference()) { + return true; + } + + if (readOrLoadProperty(idValue, embeddedProperty) != null) { + return true; + } } - return instance; + return false; } @Nullable private S readEntityFrom(RelationalPersistentProperty property, PersistentPropertyPathExtension path) { - ReadingContext newContext = extendBy(property); + ReadingContext newContext = (ReadingContext) extendBy(property); RelationalPersistentEntity entity = (RelationalPersistentEntity) getMappingContext() .getRequiredPersistentEntity(property.getActualType()); @@ -398,15 +397,7 @@ private S readEntityFrom(RelationalPersistentProperty property, PersistentPr return null; } - S instance = newContext.createInstanceInternal(entity, idValue); - - PersistentPropertyAccessor accessor = getPropertyAccessor(entity, instance); - - for (RelationalPersistentProperty p : entity) { - accessor.setProperty(p, newContext.readOrLoadProperty(idValue, p)); - } - - return instance; + return newContext.createInstanceInternal(idValue); } @Nullable @@ -419,9 +410,9 @@ private Object getObjectFromResultSet(String backreferenceName) { } } - private S createInstanceInternal(RelationalPersistentEntity entity, @Nullable Object idValue) { + private T createInstanceInternal(@Nullable Object idValue) { - return createInstance(entity, parameter -> { + T instance = createInstance(entity, parameter -> { String parameterName = parameter.getName(); @@ -431,6 +422,7 @@ private S createInstanceInternal(RelationalPersistentEntity entity, @Null return readOrLoadProperty(idValue, property); }); + return populateProperties(instance, idValue); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java index 685f5f80a8..4b66d647ad 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java @@ -54,6 +54,7 @@ * @author Mark Paluch * @author Thomas Lang * @author Bastian Wilhelm + * @author Christoph Strobl * @since 1.1 */ public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -313,7 +314,8 @@ private MapSqlParameterSource getParameterSource(S instance, RelationalPe MapSqlParameterSource parameters = new MapSqlParameterSource(); - PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(instance); + PersistentPropertyAccessor propertyAccessor = instance != null ? persistentEntity.getPropertyAccessor(instance) + : NoValuePropertyAccessor.instance(); persistentEntity.doWithProperties((PropertyHandler) property -> { @@ -480,4 +482,33 @@ static Predicate includeAll() { return it -> false; } } + + /** + * A {@link PersistentPropertyAccessor} implementation always returning null + * + * @param + */ + static class NoValuePropertyAccessor implements PersistentPropertyAccessor { + + private static final NoValuePropertyAccessor INSTANCE = new NoValuePropertyAccessor(); + + static NoValuePropertyAccessor instance() { + return INSTANCE; + } + + @Override + public void setProperty(PersistentProperty property, Object value) { + throw new UnsupportedOperationException("Cannot set value on 'null' target object."); + } + + @Override + public Object getProperty(PersistentProperty property) { + return null; + } + + @Override + public T getBean() { + return null; + } + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index f2f910fa34..ffe40093c9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -436,7 +436,7 @@ public void saveAndLoadAnEntityWithByteArray() { assertThat(reloaded.binaryData).isEqualTo(new byte[] { 1, 23, 42 }); } - @Test + @Test // DATAJDBC-340 public void saveAndLoadLongChain() { Chain4 chain4 = new Chain4(); 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 2f3a62377b..2004a3357c 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 @@ -18,11 +18,16 @@ import static java.util.Arrays.*; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.Value; import lombok.experimental.Wither; import java.sql.ResultSet; @@ -60,6 +65,7 @@ * @author Mark Paluch * @author Maciej Walkowiak * @author Bastian Wilhelm + * @author Christoph Strobl */ public class EntityRowMapperUnitTests { @@ -284,148 +290,129 @@ public void chainedEntitiesWithoutId() throws SQLException { fixture.assertOn(extracted); } - private FixtureBuilder buildFixture() { - return new FixtureBuilder<>(); - } - - private EntityRowMapper createRowMapper(Class type) { - return createRowMapper(type, NamingStrategy.INSTANCE); - } - - private EntityRowMapper createRowMapper(Class type, NamingStrategy namingStrategy) { + @Test // DATAJDBC-370 + public void simpleImmutableEmbeddedGetsProperlyExtracted() throws SQLException { - RelationalMappingContext context = new JdbcMappingContext(namingStrategy); - - DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class); - - // the ID of the entity is used to determine what kind of ResultSet is needed for subsequent selects. - doReturn(new HashSet<>(asList(new Trivial(), new Trivial()))).when(accessStrategy) - .findAllByProperty(eq(ID_FOR_ENTITY_NOT_REFERENCING_MAP), any(RelationalPersistentProperty.class)); - - doReturn(new HashSet<>(asList( // - new SimpleEntry<>("one", new Trivial()), // - new SimpleEntry<>("two", new Trivial()) // - ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_MAP), - any(RelationalPersistentProperty.class)); - - doReturn(new HashSet<>(asList( // - new SimpleEntry<>(1, new Trivial()), // - new SimpleEntry<>(2, new Trivial()) // - ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST), - any(RelationalPersistentProperty.class)); + ResultSet rs = mockResultSet(asList("id", "value"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, "ru'Ha'"); + rs.next(); - JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions()); + WithImmutableValue extracted = createRowMapper(WithImmutableValue.class).mapRow(rs, 1); - return new EntityRowMapper<>( // - (RelationalPersistentEntity) context.getRequiredPersistentEntity(type), // - // - converter, // - accessStrategy // - ); + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.embeddedImmutableValue) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, new ImmutableValue("ru'Ha'")); } - private static ResultSet mockResultSet(List columns, Object... values) { + @Test // DATAJDBC-370 + @SneakyThrows + public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() { - Assert.isTrue( // - values.length % columns.size() == 0, // - String // - .format( // - "Number of values [%d] must be a multiple of the number of columns [%d]", // - values.length, // - columns.size() // - ) // - ); + ResultSet rs = mockResultSet(asList("id", "value"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, 24); + rs.next(); - List> result = convertValues(columns, values); + WithPrimitiveImmutableValue extracted = createRowMapper(WithPrimitiveImmutableValue.class).mapRow(rs, 1); - return mock(ResultSet.class, new ResultSetAnswer(result)); + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.embeddedImmutablePrimitiveValue) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, new ImmutablePrimitiveValue(24)); } - private static List> convertValues(List columns, Object[] values) { + @Test // DATAJDBC-370 + public void simpleImmutableEmbeddedShouldBeNullIfAllOfTheEmbeddableAreNull() throws SQLException { - List> result = new ArrayList<>(); + ResultSet rs = mockResultSet(asList("id", "value"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); + rs.next(); - int index = 0; - while (index < values.length) { + WithImmutableValue extracted = createRowMapper(WithImmutableValue.class).mapRow(rs, 1); - Map row = new HashMap<>(); - result.add(row); - for (String column : columns) { - - row.put(column, values[index]); - index++; - } - } - return result; + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.embeddedImmutableValue) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); } - private static class ResultSetAnswer implements Answer { + @Test // DATAJDBC-370 + @SneakyThrows + public void embeddedShouldBeNullWhenFieldsAreNull() { - private final List> values; - private int index = -1; + ResultSet rs = mockResultSet(asList("id", "name", "prefix_id", "prefix_name"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", null, null); + rs.next(); - public ResultSetAnswer(List> values) { + EmbeddedEntity extracted = createRowMapper(EmbeddedEntity.class).mapRow(rs, 1); - this.values = values; - } + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.name, e -> e.children) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", null); + } - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { + @Test // DATAJDBC-370 + @SneakyThrows + public void embeddedShouldNotBeNullWhenAtLeastOneFieldIsNotNull() { - switch (invocation.getMethod().getName()) { - case "next": - return next(); - case "getObject": - return getObject(invocation.getArgument(0)); - case "isAfterLast": - return isAfterLast(); - case "isBeforeFirst": - return isBeforeFirst(); - case "getRow": - return isAfterLast() || isBeforeFirst() ? 0 : index + 1; - case "toString": - return this.toString(); - default: - throw new OperationNotSupportedException(invocation.getMethod().getName()); + ResultSet rs = mockResultSet(asList("id", "name", "prefix_id", "prefix_name"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24, null); + rs.next(); - } + EmbeddedEntity extracted = createRowMapper(EmbeddedEntity.class).mapRow(rs, 1); - } + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.name, e -> e.children) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", new Trivial(24L, null)); + } - private boolean isAfterLast() { - return index >= values.size() && !values.isEmpty(); - } + @Test // DATAJDBC-370 + @SneakyThrows + public void primitiveEmbeddedShouldBeNullWhenNoValuePresent() { - private boolean isBeforeFirst() { - return index < 0 && !values.isEmpty(); - } + ResultSet rs = mockResultSet(asList("id", "value"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); + rs.next(); - private Object getObject(String column) throws SQLException { + WithPrimitiveImmutableValue extracted = createRowMapper(WithPrimitiveImmutableValue.class).mapRow(rs, 1); - Map rowMap = values.get(index); + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.embeddedImmutablePrimitiveValue) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); + } - if (!rowMap.containsKey(column)) { - throw new SQLException(String.format("Trying to access a column (%s) that does not exist", column)); - } + @Test // DATAJDBC-370 + @SneakyThrows + public void deepNestedEmbeddable() { - return rowMap.get(column); - } + ResultSet rs = mockResultSet(asList("id", "level0", "level1_value", "level1_level2_value"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, "0", "1", "2"); + rs.next(); - private boolean next() { + WithDeepNestedEmbeddable extracted = createRowMapper(WithDeepNestedEmbeddable.class).mapRow(rs, 1); - index++; - return index < values.size(); - } + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> extracted.level0, e -> e.level1.value, e -> e.level1.level2.value) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "0", "1", "2"); } - @RequiredArgsConstructor + // Model classes to be used in tests + @Wither + @RequiredArgsConstructor static class TrivialImmutable { @Id private final Long id; private final String name; } + @EqualsAndHashCode + @NoArgsConstructor + @AllArgsConstructor static class Trivial { @Id Long id; @@ -439,8 +426,8 @@ static class OneToOne { Trivial child; } - @RequiredArgsConstructor @Wither + @RequiredArgsConstructor static class OneToOneImmutable { private final @Id Long id; @@ -541,6 +528,178 @@ static class NoIdChain4 { NoIdChain3 chain3; } + static class WithImmutableValue { + + @Id Long id; + @Embedded ImmutableValue embeddedImmutableValue; + } + + static class WithPrimitiveImmutableValue { + + @Id Long id; + @Embedded ImmutablePrimitiveValue embeddedImmutablePrimitiveValue; + } + + @Value + static class ImmutableValue { + Object value; + } + + @Value + static class ImmutablePrimitiveValue { + int value; + } + + static class WithDeepNestedEmbeddable { + + @Id Long id; + String level0; + @Embedded("level1_") EmbeddedWithEmbedded level1; + } + + static class EmbeddedWithEmbedded { + + Object value; + @Embedded("level2_") ImmutableValue level2; + } + + // Infrastructure for assertions and constructing mocks + + private FixtureBuilder buildFixture() { + return new FixtureBuilder<>(); + } + + private EntityRowMapper createRowMapper(Class type) { + return createRowMapper(type, NamingStrategy.INSTANCE); + } + + private EntityRowMapper createRowMapper(Class type, NamingStrategy namingStrategy) { + + RelationalMappingContext context = new JdbcMappingContext(namingStrategy); + + DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class); + + // the ID of the entity is used to determine what kind of ResultSet is needed for subsequent selects. + doReturn(new HashSet<>(asList(new Trivial(1L, "one"), new Trivial(2L, "two")))).when(accessStrategy) + .findAllByProperty(eq(ID_FOR_ENTITY_NOT_REFERENCING_MAP), any(RelationalPersistentProperty.class)); + + doReturn(new HashSet<>(asList( // + new SimpleEntry<>("one", new Trivial(1L, "one")), // + new SimpleEntry<>("two", new Trivial(2L, "two")) // + ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_MAP), + any(RelationalPersistentProperty.class)); + + doReturn(new HashSet<>(asList( // + new SimpleEntry<>(1, new Trivial(1L, "one")), // + new SimpleEntry<>(2, new Trivial(2L, "tow")) // + ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST), + any(RelationalPersistentProperty.class)); + + JdbcConverter converter = new BasicJdbcConverter(context, new JdbcCustomConversions(), + JdbcTypeFactory.unsupported()); + + return new EntityRowMapper<>( // + (RelationalPersistentEntity) context.getRequiredPersistentEntity(type), // + // + converter, // + accessStrategy // + ); + } + + private static ResultSet mockResultSet(List columns, Object... values) { + + Assert.isTrue( // + values.length % columns.size() == 0, // + String // + .format( // + "Number of values [%d] must be a multiple of the number of columns [%d]", // + values.length, // + columns.size() // + ) // + ); + + List> result = convertValues(columns, values); + + return mock(ResultSet.class, new ResultSetAnswer(result)); + } + + private static List> convertValues(List columns, Object[] values) { + + List> result = new ArrayList<>(); + + int index = 0; + while (index < values.length) { + + Map row = new HashMap<>(); + result.add(row); + for (String column : columns) { + + row.put(column, values[index]); + index++; + } + } + return result; + } + + private static class ResultSetAnswer implements Answer { + + private final List> values; + private int index = -1; + + public ResultSetAnswer(List> values) { + + this.values = values; + } + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + + switch (invocation.getMethod().getName()) { + case "next": + return next(); + case "getObject": + return getObject(invocation.getArgument(0)); + case "isAfterLast": + return isAfterLast(); + case "isBeforeFirst": + return isBeforeFirst(); + case "getRow": + return isAfterLast() || isBeforeFirst() ? 0 : index + 1; + case "toString": + return this.toString(); + default: + throw new OperationNotSupportedException(invocation.getMethod().getName()); + + } + + } + + private boolean isAfterLast() { + return index >= values.size() && !values.isEmpty(); + } + + private boolean isBeforeFirst() { + return index < 0 && !values.isEmpty(); + } + + private Object getObject(String column) throws SQLException { + + Map rowMap = values.get(index); + + if (!rowMap.containsKey(column)) { + throw new SQLException(String.format("Trying to access a column (%s) that does not exist", column)); + } + + return rowMap.get(column); + } + + private boolean next() { + + index++; + return index < values.size(); + } + } + private interface SetValue { SetColumns value(Object value); @@ -609,6 +768,7 @@ public SetValue endUpIn(Function extractor) { @AllArgsConstructor private static class Fixture { + final ResultSet resultSet; final List> expectations; 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 5d0ac7609f..924c728972 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 @@ -44,6 +44,7 @@ * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. * * @author Bastian Wilhelm + * @author Christoph Strobl */ @ContextConfiguration @Transactional @@ -209,6 +210,14 @@ public void deleteAll() { assertThat(repository.findAll()).isEmpty(); } + @Test // DATAJDBC-370 + public void saveWithNullValueEmbeddable() { + + DummyEntity entity = repository.save(new DummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + } private static DummyEntity createDummyEntity() { DummyEntity entity = new DummyEntity(); @@ -222,7 +231,6 @@ private static DummyEntity createDummyEntity() { entity.setPrefixedEmbeddable(prefixedCascadedEmbeddable); - final CascadedEmbeddable cascadedEmbeddable = new CascadedEmbeddable(); cascadedEmbeddable.setTest("c2"); diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index ba0ffea143..ea6fbff70b 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-370-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-370-SNAPSHOT