diff --git a/pom.xml b/pom.xml index f0b2a42b23f..932085a1aef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 4626db43643..3529d127bf8 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 - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 266af71b96c..2ee475d1020 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java index 3810efd12bf..fc31e8af197 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java @@ -159,10 +159,13 @@ public Class getColumnType(RelationalPersistentProperty property) { private Class doGetColumnType(RelationalPersistentProperty property) { + // here we get the column type and it seems we ignore custom conversions + if (property.isAssociation()) { return getReferenceColumnType(property); } + // why the f**** is this an entity?? if (property.isEntity()) { Class columnType = getEntityColumnType(property.getTypeInformation().getActualType()); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java index d20ea64d66d..d1044886516 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.core.convert; +import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.*; import static org.mockito.Mockito.*; @@ -40,6 +41,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; +import org.springframework.data.convert.WritingConverter; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcValue; @@ -59,9 +61,9 @@ class MappingJdbcConverterUnitTests { private static final UUID UUID = java.util.UUID.fromString("87a48aa8-a071-705e-54a9-e52fe3a012f1"); - private static final byte[] BYTES_REPRESENTING_UUID = { -121, -92, -118, -88, -96, 113, 112, 94, 84, -87, -27, 47, + private static final byte[] BYTES_REPRESENTING_UUID = {-121, -92, -118, -88, -96, 113, 112, 94, 84, -87, -27, 47, -29, - -96, 18, -15 }; + -96, 18, -15}; private JdbcMappingContext context = new JdbcMappingContext(); private StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory(); @@ -70,32 +72,39 @@ class MappingJdbcConverterUnitTests { (identifier, path) -> { throw new UnsupportedOperationException(); }, // - new JdbcCustomConversions(), // + new JdbcCustomConversions(List.of(CustomIdToLong.INSTANCE)), // typeFactory // ); + { + context.setSimpleTypeHolder(converter.getConversions().getSimpleTypeHolder()); + } - @Test // DATAJDBC-104, DATAJDBC-1384 + @Test + // DATAJDBC-104, DATAJDBC-1384 void testTargetTypesForPropertyType() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); - SoftAssertions softly = new SoftAssertions(); - - checkTargetType(softly, entity, "someEnum", String.class); - checkTargetType(softly, entity, "localDateTime", LocalDateTime.class); - checkTargetType(softly, entity, "localDate", Timestamp.class); - checkTargetType(softly, entity, "localTime", Timestamp.class); - checkTargetType(softly, entity, "zonedDateTime", String.class); - checkTargetType(softly, entity, "offsetDateTime", OffsetDateTime.class); - checkTargetType(softly, entity, "instant", Timestamp.class); - checkTargetType(softly, entity, "date", Date.class); - checkTargetType(softly, entity, "timestamp", Timestamp.class); - checkTargetType(softly, entity, "uuid", UUID.class); - - softly.assertAll(); + SoftAssertions.assertSoftly(softly -> { + + checkTargetType(softly, entity, "someEnum", String.class); + checkTargetType(softly, entity, "localDateTime", LocalDateTime.class); + checkTargetType(softly, entity, "localDate", Timestamp.class); + checkTargetType(softly, entity, "localTime", Timestamp.class); + checkTargetType(softly, entity, "zonedDateTime", String.class); + checkTargetType(softly, entity, "offsetDateTime", OffsetDateTime.class); + checkTargetType(softly, entity, "instant", Timestamp.class); + checkTargetType(softly, entity, "date", Date.class); + checkTargetType(softly, entity, "timestamp", Timestamp.class); + checkTargetType(softly, entity, "uuid", UUID.class); + checkTargetType(softly, entity, "reference", Long.class); + checkTargetType(softly, entity, "enumIdReference", String.class); + checkTargetType(softly, entity, "customIdReference", Long.class); + }); } - @Test // DATAJDBC-259 + @Test + // DATAJDBC-259 void classificationOfCollectionLikeProperties() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); @@ -111,22 +120,24 @@ void classificationOfCollectionLikeProperties() { softly.assertAll(); } - @Test // DATAJDBC-221 + @Test + // DATAJDBC-221 void referencesAreNotEntitiesAndGetStoredAsTheirId() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); - SoftAssertions softly = new SoftAssertions(); RelationalPersistentProperty reference = entity.getRequiredPersistentProperty("reference"); - softly.assertThat(reference.isEntity()).isFalse(); - softly.assertThat(converter.getColumnType(reference)).isEqualTo(Long.class); + SoftAssertions.assertSoftly(softly -> { - softly.assertAll(); + softly.assertThat(reference.isEntity()).isFalse(); + softly.assertThat(converter.getColumnType(reference)).isEqualTo(Long.class); + }); } - @Test // DATAJDBC-637 + @Test + // DATAJDBC-637 void conversionOfDateLikeValueAndBackYieldsOriginalValue() { RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); @@ -142,17 +153,19 @@ void conversionOfDateLikeValueAndBackYieldsOriginalValue() { } - @Test // GH-945 + @Test + // GH-945 void conversionOfPrimitiveArrays() { - int[] ints = { 1, 2, 3, 4, 5 }; + int[] ints = {1, 2, 3, 4, 5}; JdbcValue converted = converter.writeJdbcValue(ints, ints.getClass(), JdbcUtil.targetSqlTypeFor(ints.getClass())); assertThat(converted.getValue()).isInstanceOf(Array.class); assertThat(typeFactory.arraySource).containsExactly(1, 2, 3, 4, 5); } - @Test // GH-1684 + @Test + // GH-1684 void accessesCorrectValuesForOneToOneRelationshipWithIdenticallyNamedIdProperties() { RowDocument rowdocument = new RowDocument(Map.of("ID", "one", "REFERENCED_ID", 23)); @@ -162,7 +175,8 @@ void accessesCorrectValuesForOneToOneRelationshipWithIdenticallyNamedIdPropertie assertThat(result).isEqualTo(new WithOneToOne("one", new Referenced(23L))); } - @Test // GH-1750 + @Test + // GH-1750 void readByteArrayToNestedUuidWithCustomConverter() { JdbcMappingContext context = new JdbcMappingContext(); @@ -186,7 +200,7 @@ void readByteArrayToNestedUuidWithCustomConverter() { } private static void checkReadConversion(SoftAssertions softly, MappingJdbcConverter converter, String propertyName, - Object expected) { + Object expected) { RelationalPersistentProperty property = converter.getMappingContext().getRequiredPersistentEntity(DummyEntity.class) .getRequiredPersistentProperty(propertyName); @@ -197,7 +211,7 @@ private static void checkReadConversion(SoftAssertions softly, MappingJdbcConver } private void checkConversionToTimestampAndBack(SoftAssertions softly, RelationalPersistentEntity persistentEntity, - String propertyName, Object value) { + String propertyName, Object value) { RelationalPersistentProperty property = persistentEntity.getRequiredPersistentProperty(propertyName); @@ -208,7 +222,7 @@ private void checkConversionToTimestampAndBack(SoftAssertions softly, Relational } private void checkTargetType(SoftAssertions softly, RelationalPersistentEntity persistentEntity, - String propertyName, Class expected) { + String propertyName, Class expected) { RelationalPersistentProperty property = persistentEntity.getRequiredPersistentProperty(propertyName); @@ -216,117 +230,30 @@ private void checkTargetType(SoftAssertions softly, RelationalPersistentEntity reference; - private final UUID uuid; - private final AggregateReference uuidRef; - private final Optional optionalUuid; - - // DATAJDBC-259 - private final List listOfString; - private final String[] arrayOfString; - private final List listOfEntity; - private final OtherEntity[] arrayOfEntity; - - private DummyEntity(Long id, SomeEnum someEnum, LocalDateTime localDateTime, LocalDate localDate, - LocalTime localTime, ZonedDateTime zonedDateTime, OffsetDateTime offsetDateTime, Instant instant, Date date, - Timestamp timestamp, AggregateReference reference, UUID uuid, - AggregateReference uuidRef, Optional optionalUUID, List listOfString, String[] arrayOfString, - List listOfEntity, OtherEntity[] arrayOfEntity) { - this.id = id; - this.someEnum = someEnum; - this.localDateTime = localDateTime; - this.localDate = localDate; - this.localTime = localTime; - this.zonedDateTime = zonedDateTime; - this.offsetDateTime = offsetDateTime; - this.instant = instant; - this.date = date; - this.timestamp = timestamp; - this.reference = reference; - this.uuid = uuid; - this.uuidRef = uuidRef; - this.optionalUuid = optionalUUID; - this.listOfString = listOfString; - this.arrayOfString = arrayOfString; - this.listOfEntity = listOfEntity; - this.arrayOfEntity = arrayOfEntity; - } - - public Long getId() { - return this.id; - } - - public SomeEnum getSomeEnum() { - return this.someEnum; - } - - public LocalDateTime getLocalDateTime() { - return this.localDateTime; - } - - public LocalDate getLocalDate() { - return this.localDate; - } - - public LocalTime getLocalTime() { - return this.localTime; - } - - public ZonedDateTime getZonedDateTime() { - return this.zonedDateTime; - } - - public OffsetDateTime getOffsetDateTime() { - return this.offsetDateTime; - } - - public Instant getInstant() { - return this.instant; - } - - public Date getDate() { - return this.date; - } - - public Timestamp getTimestamp() { - return this.timestamp; - } - - public AggregateReference getReference() { - return this.reference; - } - - public UUID getUuid() { - return this.uuid; - } - - public List getListOfString() { - return this.listOfString; - } - - public String[] getArrayOfString() { - return this.arrayOfString; - } - - public List getListOfEntity() { - return this.listOfEntity; - } - - public OtherEntity[] getArrayOfEntity() { - return this.arrayOfEntity; - } + private record DummyEntity( + @Id Long id, + SomeEnum someEnum, + LocalDateTime localDateTime, + LocalDate localDate, + LocalTime localTime, + ZonedDateTime zonedDateTime, + OffsetDateTime offsetDateTime, + Instant instant, + Date date, + Timestamp timestamp, + AggregateReference reference, + AggregateReference enumIdReference, + AggregateReference customIdReference, + UUID uuid, + AggregateReference uuidRef, + Optional optionalUuid, + + // DATAJDBC-259 + List listOfString, + String[] arrayOfString, + List listOfEntity, + OtherEntity[] arrayOfEntity + ) { } @SuppressWarnings("unused") @@ -335,7 +262,20 @@ private enum SomeEnum { } @SuppressWarnings("unused") - private static class OtherEntity {} + private static class OtherEntity { + } + + private static class EnumIdEntity { + @Id SomeEnum id; + } + + private static class CustomIdEntity { + @Id CustomId id; + } + + private record CustomId(Long id) { + + } private static class StubbedJdbcTypeFactory implements JdbcTypeFactory { Object[] arraySource; @@ -366,4 +306,14 @@ public UUID convert(byte[] source) { return new UUID(high, low); } } + + @WritingConverter + private enum CustomIdToLong implements Converter { + INSTANCE; + + @Override + public Long convert(CustomId source) { + return source.id; + } + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdOnlyAggregateReferenceTest.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdOnlyAggregateReferenceUnitTests.java similarity index 94% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdOnlyAggregateReferenceTest.java rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdOnlyAggregateReferenceUnitTests.java index 5c224edfb8b..7469d942e4a 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdOnlyAggregateReferenceTest.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdOnlyAggregateReferenceUnitTests.java @@ -9,8 +9,9 @@ * Unit tests for the {@link IdOnlyAggregateReference}. * * @author Myeonghyeon Lee + * @author Jens Schauder */ -public class IdOnlyAggregateReferenceTest { +public class IdOnlyAggregateReferenceUnitTests { @Test // DATAJDBC-427 public void equals() { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests.java index 8fcc7c132e4..6e740f52509 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests.java @@ -15,26 +15,38 @@ */ package org.springframework.data.jdbc.repository; +import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.Import; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.AggregateReference; +import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.DatabaseType; import org.springframework.data.jdbc.testing.EnabledOnDatabase; import org.springframework.data.jdbc.testing.IntegrationTest; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.jdbc.JdbcTestUtils; +import java.util.Collections; + /** * Very simple use cases for creation and usage of JdbcRepositories. * @@ -52,13 +64,18 @@ public class JdbcRepositoryCrossAggregateHsqlIntegrationTests { @Configuration @Import(TestConfiguration.class) @EnableJdbcRepositories(considerNestedRepositories = true, - includeFilters = @ComponentScan.Filter(value = Ones.class, type = FilterType.ASSIGNABLE_TYPE)) + includeFilters = @ComponentScan.Filter(value = {Ones.class, ReferencingAggregateRepository.class}, type = FilterType.ASSIGNABLE_TYPE)) static class Config { + @Bean + JdbcCustomConversions jdbcCustomConversions() { + return new JdbcCustomConversions(asList( AggregateIdToLong.INSTANCE, LongToAggregateId.INSTANCE )); + } } @Autowired NamedParameterJdbcTemplate template; @Autowired Ones ones; + @Autowired ReferencingAggregateRepository referencingAggregates; @Autowired RelationalMappingContext context; @SuppressWarnings("ConstantConditions") @@ -95,6 +112,18 @@ public void savesAndUpdate() { ).isEqualTo(1); } + @Test // DATAJDBC-221 + public void savesAndReadWithConvertableId() { + + + AggregateReference idReference = AggregateReference.to(new AggregateId(TWO_ID)); + ReferencingAggregate reference = referencingAggregates.save(new ReferencingAggregate(null, "Reference", idReference)); + + + ReferencingAggregate reloaded = referencingAggregates.findById(reference.id).get(); + assertThat(reloaded.id()).isEqualTo(idReference); + } + interface Ones extends CrudRepository {} static class AggregateOne { @@ -109,4 +138,40 @@ static class AggregateTwo { @Id Long id; String name; } + + interface ReferencingAggregateRepository extends CrudRepository { + + } + + record AggregateWithConvertableId(@Id AggregateId id, String name) { + + } + + record AggregateId(Long value) { + + } + + record ReferencingAggregate(@Id Long id, String name, + AggregateReference ref) { + } + + @WritingConverter + enum AggregateIdToLong implements Converter { + INSTANCE; + + @Override + public Long convert(AggregateId source) { + return source.value; + } + } + + @ReadingConverter + enum LongToAggregateId implements Converter< Long,AggregateId> { + INSTANCE; + + @Override + public AggregateId convert(Long source) { + return new AggregateId(source); + } + } } diff --git a/spring-data-jdbc/src/test/resources/logback.xml b/spring-data-jdbc/src/test/resources/logback.xml index 67cda4afc63..fc4fae5ab81 100644 --- a/spring-data-jdbc/src/test/resources/logback.xml +++ b/spring-data-jdbc/src/test/resources/logback.xml @@ -9,7 +9,7 @@ - + diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests-hsql.sql index f03df7b7ea5..ea1da7181fc 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests-hsql.sql @@ -1 +1,13 @@ -CREATE TABLE aggregate_one ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100), two INTEGER); +CREATE TABLE AGGREGATE_ONE +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + NAME VARCHAR(100), + TWO INTEGER +); + +CREATE TABLE REFERENCING_AGGREGATE +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + NAME VARCHAR(100), + REF INTEGER +); diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index 3168f9d4f6a..4bf7406885e 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 0287ece7439..257f1bb0fb6 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1828-aggregate-ref-with-convertable-SNAPSHOT diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java index e97b3d7331e..215d255d458 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java @@ -703,7 +703,7 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { Optional> customWriteTarget = getConversions().getCustomWriteTarget(type.getType()); if (customWriteTarget.isPresent()) { - return getConversionService().convert(value, customWriteTarget.get()); + return getPotentiallyConvertedSimpleWrite(getConversionService().convert(value, customWriteTarget.get())); } if (TypeInformation.OBJECT != type) { @@ -741,9 +741,7 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { return writeValue(id, type); } - return - - getConversionService().convert(value, type.getType()); + return getConversionService().convert(value, type.getType()); } private Object writeArray(Object value, TypeInformation type) {