diff --git a/pom.xml b/pom.xml index 4016f901e1..e412d6f301 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jdbc - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-266-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. diff --git a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index dbaa053ef3..2fb226ec7a 100644 --- a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -21,7 +21,6 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.mapping.MappingException; -import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.relational.core.conversion.RelationalConverter; @@ -118,21 +117,17 @@ private T populateProperties(T result, ResultSet resultSet) { @Nullable private Object readFrom(ResultSet resultSet, RelationalPersistentProperty property, String prefix) { - try { - - if (property.isEntity()) { - return readEntityFrom(resultSet, property); - } + if (property.isEntity()) { + return readEntityFrom(resultSet, property); + } - return converter.readValue(resultSet.getObject(prefix + property.getColumnName()), property.getTypeInformation()); + Object value = getObjectFromResultSet(resultSet, prefix + property.getColumnName()); + return converter.readValue(value, property.getTypeInformation()); - } catch (SQLException o_O) { - throw new MappingException(String.format("Could not read property %s from result set!", property), o_O); - } } @Nullable - private S readEntityFrom(ResultSet rs, PersistentProperty property) { + private S readEntityFrom(ResultSet rs, RelationalPersistentProperty property) { String prefix = property.getName() + "_"; @@ -140,7 +135,12 @@ private S readEntityFrom(ResultSet rs, PersistentProperty property) { RelationalPersistentEntity entity = (RelationalPersistentEntity) context .getRequiredPersistentEntity(property.getActualType()); - if (readFrom(rs, entity.getRequiredIdProperty(), prefix) == null) { + RelationalPersistentProperty idProperty = entity.getIdProperty(); + + if ((idProperty != null // + ? readFrom(rs, idProperty, prefix) // + : getObjectFromResultSet(rs, prefix + property.getReverseColumnName()) // + ) == null) { return null; } @@ -155,6 +155,16 @@ private S readEntityFrom(ResultSet rs, PersistentProperty property) { return instance; } + @Nullable + private Object getObjectFromResultSet(ResultSet rs, String backreferenceName) { + + try { + return rs.getObject(backreferenceName); + } catch (SQLException o_O) { + throw new MappingException(String.format("Could not read value %s from result set!", backreferenceName), o_O); + } + } + private S createInstance(RelationalPersistentEntity entity, ResultSet rs, String prefix) { return converter.createInstance(entity, parameter -> { diff --git a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 83ce833e38..a92d3f1ecd 100644 --- a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -201,6 +201,17 @@ private void addColumnsAndJoinsForOneToOneReferences(SelectBuilder builder) { .as(joinAlias + "_" + refProperty.getColumnName()) // ); } + + // if the referenced property doesn't have an id, include the back reference in the select list. + // this enables determining if the referenced entity is present or null. + if (!refEntity.hasIdProperty()) { + + builder.column( // + cb -> cb.tableAlias(joinAlias) // + .column(property.getReverseColumnName()) // + .as(joinAlias + "_" + property.getReverseColumnName()) // + ); + } } } diff --git a/src/test/java/org/springframework/data/jdbc/core/AggregateTemplateIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/core/AggregateTemplateIntegrationTests.java index 22d46b1fdc..bf25e4fa5d 100644 --- a/src/test/java/org/springframework/data/jdbc/core/AggregateTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/AggregateTemplateIntegrationTests.java @@ -210,6 +210,52 @@ public void changeReferencedEntity() { assertThat(reloadedLegoSet.manual.content).isEqualTo("new content"); } + @Test // DATAJDBC-266 + public void oneToOneChildWithoutId() { + + OneToOneParent parent = new OneToOneParent(); + + parent.content = "parent content"; + parent.child = new OneToOneChildNoId(); + parent.child.content = "child content"; + + template.save(parent); + + OneToOneParent reloaded = template.findById(parent.id, OneToOneParent.class); + + assertThat(reloaded.child.content).isEqualTo("child content"); + } + + @Test // DATAJDBC-266 + public void oneToOneNullChildWithoutId() { + + OneToOneParent parent = new OneToOneParent(); + + parent.content = "parent content"; + parent.child = null; + + template.save(parent); + + OneToOneParent reloaded = template.findById(parent.id, OneToOneParent.class); + + assertThat(reloaded.child).isNull(); + } + + @Test // DATAJDBC-266 + public void oneToOneNullAttributes() { + + OneToOneParent parent = new OneToOneParent(); + + parent.content = "parent content"; + parent.child = new OneToOneChildNoId(); + + template.save(parent); + + OneToOneParent reloaded = template.findById(parent.id, OneToOneParent.class); + + assertThat(reloaded.child).isNotNull(); + } + private static LegoSet createLegoSet() { LegoSet entity = new LegoSet(); @@ -241,6 +287,18 @@ static class Manual { } + static class OneToOneParent { + + @Id private Long id; + private String content; + + private OneToOneChildNoId child; + } + + static class OneToOneChildNoId { + private String content; + } + @Configuration @Import(TestConfiguration.class) static class Config { diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index 984e195aca..6be1baa15c 100644 --- a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -186,6 +186,19 @@ public void getInsertForEmptyColumnList() { assertThat(insert).endsWith("()"); } + @Test // DATAJDBC-266 + public void joinForOneToOneWithoutIdIncludesTheBackReferenceOfTheOuterJoin() { + + SqlGenerator sqlGenerator = createSqlGenerator(ParentOfNoIdChild.class); + + String findAll = sqlGenerator.getFindAll(); + + assertThat(findAll).containsSequence( + "SELECT", + "child.parent_of_no_id_child AS child_parent_of_no_id_child", + "FROM"); + } + private PersistentPropertyPath getPath(String path, Class base) { return PersistentPropertyPathTestUtils.getPath(context, path, base); } @@ -220,6 +233,15 @@ static class Element { String content; } + static class ParentOfNoIdChild { + @Id Long id; + NoIdChild child; + } + + static class NoIdChild { + + } + private static class PrefixingNamingStrategy implements NamingStrategy { @Override diff --git a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-hsql.sql index 20a0a290df..6b7c1a5aff 100644 --- a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-hsql.sql +++ b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-hsql.sql @@ -3,3 +3,6 @@ CREATE TABLE MANUAL ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) P ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET(id); + +CREATE TABLE ONE_TO_ONE_PARENT ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE One_To_One_Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); diff --git a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mariadb.sql b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mariadb.sql index 0ac78e637f..766bf60618 100644 --- a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mariadb.sql +++ b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mariadb.sql @@ -3,3 +3,6 @@ CREATE TABLE MANUAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, LEGO_SET BIGINT, CON ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET(id); + +CREATE TABLE ONE_TO_ONE_PARENT ( id BIGINT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE One_To_One_Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); diff --git a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mysql.sql b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mysql.sql index 0ac78e637f..766bf60618 100644 --- a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mysql.sql +++ b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-mysql.sql @@ -3,3 +3,6 @@ CREATE TABLE MANUAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, LEGO_SET BIGINT, CON ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET(id); + +CREATE TABLE ONE_TO_ONE_PARENT ( id BIGINT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE One_To_One_Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); diff --git a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-postgres.sql b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-postgres.sql index e36e560791..432d687be7 100644 --- a/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-postgres.sql +++ b/src/test/resources/org.springframework.data.jdbc.core/AggregateTemplateIntegrationTests-postgres.sql @@ -6,3 +6,6 @@ CREATE TABLE MANUAL ( id SERIAL PRIMARY KEY, LEGO_SET BIGINT, CONTENT VARCHAR(20 ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET(id); + +CREATE TABLE ONE_TO_ONE_PARENT ( id SERIAL PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE One_To_One_Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30));