diff --git a/pom.xml b/pom.xml index ebd3103251..1458884755 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 4.0.0-SNAPSHOT + 4.0.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 b3c39e64c3..b6d1148cbe 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 - 4.0.0-SNAPSHOT + 4.0.0-1828-aggregate-ref-with-convertable-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index e61fd64020..0eecd3490a 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 4.0.0-SNAPSHOT + 4.0.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 - 4.0.0-SNAPSHOT + 4.0.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 7460931dab..b1d57bb38f 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 @@ -27,9 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContextAware; -import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcValue; @@ -80,7 +78,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements * {@link #MappingJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory)} * (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types. * - * @param context must not be {@literal null}. + * @param context must not be {@literal null}. * @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. */ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) { @@ -91,19 +89,17 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r this.typeFactory = JdbcTypeFactory.unsupported(); this.relationResolver = relationResolver; - - registerAggregateReferenceConverters(); } /** * Creates a new {@link MappingJdbcConverter} given {@link MappingContext}. * - * @param context must not be {@literal null}. + * @param context must not be {@literal null}. * @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. - * @param typeFactory must not be {@literal null} + * @param typeFactory must not be {@literal null} */ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, - CustomConversions conversions, JdbcTypeFactory typeFactory) { + CustomConversions conversions, JdbcTypeFactory typeFactory) { super(context, conversions); @@ -112,14 +108,6 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r this.typeFactory = typeFactory; this.relationResolver = relationResolver; - - registerAggregateReferenceConverters(); - } - - private void registerAggregateReferenceConverters() { - - ConverterRegistry registry = (ConverterRegistry) getConversionService(); - AggregateReferenceConverters.getConvertersToRegister(getConversionService()).forEach(registry::addConverter); } @Nullable @@ -185,33 +173,48 @@ private Class doGetColumnType(RelationalPersistentProperty property) { } @Override - @Nullable - public Object readValue(@Nullable Object value, TypeInformation type) { - - if (value == null) { - return value; - } + protected Object readTechnologyType(Object value) { if (value instanceof Array array) { try { - return super.readValue(array.getArray(), type); - } catch (SQLException | ConverterNotFoundException e) { + return array.getArray(); + } catch (SQLException e) { LOG.info("Failed to extract a value of type %s from an Array; Attempting to use standard conversions", e); + } } - return super.readValue(value, type); + return value; + } + + @Override + protected TypeInformation determineModuleReadTarget(TypeInformation ultimateTargetType) { + + if (AggregateReference.class.isAssignableFrom(ultimateTargetType.getType())) { + // the id type of a AggregateReference + return ultimateTargetType.getTypeArguments().get(1); + } + return ultimateTargetType; } @Override + protected Object readModuleType(Object value, TypeInformation targetType) { + + if (AggregateReference.class.isAssignableFrom(targetType.getType())) { + return AggregateReference.to(value); + } + return value; + } + @Nullable - public Object writeValue(@Nullable Object value, TypeInformation type) { + @Override + protected Object getPotentiallyConvertedSimpleWrite(Object value, TypeInformation type) { - if (value == null) { - return null; + if (value instanceof AggregateReference aggregateReference) { + return writeValue(aggregateReference.getId(), type); } - return super.writeValue(value, type); + return super.getPotentiallyConvertedSimpleWrite(value, type); } private boolean canWriteAsJdbcValue(@Nullable Object value) { @@ -285,7 +288,7 @@ public R readAndResolve(TypeInformation type, RowDocument source, Identif @Override protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor, - ValueExpressionEvaluator evaluator, ConversionContext context) { + ValueExpressionEvaluator evaluator, ConversionContext context) { if (context instanceof ResolvingConversionContext rcc) { @@ -314,7 +317,7 @@ class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValu private final Identifier identifier; private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor, - ResolvingConversionContext context, Identifier identifier) { + ResolvingConversionContext context, Identifier identifier) { AggregatePath path = context.aggregatePath(); @@ -323,7 +326,7 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele this.context = context; this.identifier = path.isEntity() ? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), - property -> delegate.getValue(path.append(property))) + property -> delegate.getValue(path.append(property))) : identifier; } @@ -331,7 +334,7 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele * Conditionally append the identifier if the entity has an identifier property. */ static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity entity, - Function getter) { + Function getter) { if (entity.hasIdProperty()) { @@ -460,7 +463,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) { return context == this.context ? this : new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor, - (ResolvingConversionContext) context, identifier); + (ResolvingConversionContext) context, identifier); } } @@ -472,7 +475,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) { * @param identifier */ private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath, - Identifier identifier) implements ConversionContext { + Identifier identifier) implements ConversionContext { @Override public S convert(Object source, TypeInformation typeHint) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterAggregateReferenceUnitTests.java similarity index 93% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterAggregateReferenceUnitTests.java index e2b25f5087..616710f2a8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterAggregateReferenceUnitTests.java @@ -32,7 +32,7 @@ * * @author Jens Schauder */ -public class BasicRelationalConverterAggregateReferenceUnitTests { +class MappingJdbcConverterAggregateReferenceUnitTests { JdbcMappingContext context = new JdbcMappingContext(); JdbcConverter converter = new MappingJdbcConverter(context, mock(RelationResolver.class)); @@ -40,7 +40,7 @@ public class BasicRelationalConverterAggregateReferenceUnitTests { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); @Test // DATAJDBC-221 - public void convertsToAggregateReference() { + void convertsToAggregateReference() { final RelationalPersistentProperty property = entity.getRequiredPersistentProperty("reference"); @@ -51,7 +51,7 @@ public void convertsToAggregateReference() { } @Test // DATAJDBC-221 - public void convertsFromAggregateReference() { + void convertsFromAggregateReference() { final RelationalPersistentProperty property = entity.getRequiredPersistentProperty("reference"); 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 b623b6ef94..dfecac2fb5 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 @@ -40,6 +40,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; @@ -60,8 +61,7 @@ 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, - -29, - -96, 18, -15 }; + -29, -96, 18, -15 }; private JdbcMappingContext context = new JdbcMappingContext(); private StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory(); @@ -70,7 +70,7 @@ class MappingJdbcConverterUnitTests { (identifier, path) -> { throw new UnsupportedOperationException(); }, // - new JdbcCustomConversions(), // + new JdbcCustomConversions(List.of(CustomIdToLong.INSTANCE)), // typeFactory // ); @@ -91,6 +91,7 @@ void testTargetTypesForPropertyType() { checkTargetType(softly, entity, "date", Date.class); checkTargetType(softly, entity, "timestamp", Timestamp.class); checkTargetType(softly, entity, "uuid", UUID.class); + checkTargetType(softly, entity, "reference", Long.class); softly.assertAll(); } @@ -216,117 +217,26 @@ 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, // + UUID uuid, // + AggregateReference uuidRef, // + Optional optionalUuid, // + List listOfString, // + String[] arrayOfString, // + List listOfEntity, // + OtherEntity[] arrayOfEntity // + ) { } @SuppressWarnings("unused") @@ -337,6 +247,18 @@ private enum SomeEnum { @SuppressWarnings("unused") 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 +288,14 @@ public UUID convert(byte[] source) { return new UUID(high, low); } } + + @WritingConverter + 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/repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCrossAggregateHsqlIntegrationTests.java index 1c52709796..e975fa9465 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,15 +15,22 @@ */ package org.springframework.data.jdbc.repository; +import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Disabled; 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.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.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.DatabaseType; @@ -52,13 +59,19 @@ 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 +108,19 @@ public void savesAndUpdate() { ).isEqualTo(1); } + @Test // GH-1828 + @Disabled + 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 +135,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 { + INSTANCE; + + @Override + public AggregateId convert(Long source) { + return new AggregateId(source); + } + } } 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 f03df7b7ea..74944aeca6 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 3ee76fd3c1..4bf3670065 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 4.0.0-SNAPSHOT + 4.0.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 - 4.0.0-SNAPSHOT + 4.0.0-1828-aggregate-ref-with-convertable-SNAPSHOT diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTests.java index 4389bb91c4..19edbae919 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTests.java @@ -21,8 +21,8 @@ import java.util.Arrays; import java.util.UUID; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; @@ -50,8 +50,11 @@ public void shouldConvertParameter() { UUID value = UUID.randomUUID(); - assertThat(strategy.getBindValue(Parameter.from(value))).isEqualTo(Parameter.from(value.toString())); - assertThat(strategy.getBindValue(Parameter.from(Condition.New))).isEqualTo(Parameter.from("New")); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(strategy.getBindValue(Parameter.from(value))).isEqualTo(Parameter.from(value.toString())); + softly.assertThat(strategy.getBindValue(Parameter.from(Condition.New))).isEqualTo(Parameter.from("New")); + }); } @Test // gh-305 diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 8fd6d7a6f0..8928842b1b 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 4.0.0-SNAPSHOT + 4.0.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 - 4.0.0-SNAPSHOT + 4.0.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 395c64e677..8251ef454c 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 @@ -559,7 +559,9 @@ private void readProperties(ConversionContext context, RelationalPersistentEntit continue; } - accessor.setProperty(property, valueProviderToUse.getPropertyValue(property)); + Object propertyValue = valueProviderToUse.getPropertyValue(property); + propertyValue = readValue(propertyValue, property.getTypeInformation()); + accessor.setProperty(property, propertyValue); } } @@ -606,34 +608,65 @@ private boolean shouldReadEmbeddable(ConversionContext context, RelationalPersis return false; } + /** + * Read and convert a single value that is comming from a database to the {@literal targetType} expected by the domain + * model. + * + * @param value a value as it is returned by the driver accessing the persistence store. May be {@code null}. + * @param targetType {@link TypeInformation} into which the value is to be converted. Must not be {@code null}. + * @return + */ @Override @Nullable - public Object readValue(@Nullable Object value, TypeInformation type) { + public Object readValue(@Nullable Object value, TypeInformation targetType) { if (null == value) { return null; } - return getPotentiallyConvertedSimpleRead(value, type); + TypeInformation originalTargetType = targetType; + value = readTechnologyType(value); + targetType = determineModuleReadTarget(targetType); + + return readModuleType(getPotentiallyConvertedSimpleRead(value, targetType), originalTargetType); } /** - * Checks whether we have a custom conversion registered for the given value into an arbitrary simple JDBC type. - * Returns the converted value if so. If not, we perform special enum handling or simply return the value as is. - * - * @param value to be converted. Must not be {@code null}. - * @return the converted value if a conversion applies or the original value. Might return {@code null}. + * Convert a read value using module dependent special conversions. Spring Data JDBC for example uses this to + * implement the conversion of AggregateReferences. There is no guarantee that the value is converted to the exact + * required TypeInformation, nor that it is converted at all. + * + * @param value the value read from the database. Must not be {@literal null}. + * @param targetType the type to which the value should get converted if possible. Must not be {@literal null}. + * @return a potentially converted value. */ - @Nullable - private Object getPotentiallyConvertedSimpleWrite(Object value) { - - Optional> customTarget = getConversions().getCustomWriteTarget(value.getClass()); + protected Object readModuleType(Object value, TypeInformation targetType) { + return value; + } - if (customTarget.isPresent()) { - return getConversionService().convert(value, customTarget.get()); - } + /** + * Read technology specific values into objects that then can be fed in the normal conversion process for reading. An + * example are the conversion of JDBCs {@literal Array} type to normal java arrays. + * + * @param value a value read from the database + * @return a preprocessed value suitable for technology-agnostic further processing. + */ + protected Object readTechnologyType(Object value) { + return value; + } - return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; + /** + * When type is a type that has special support, this returns the type a value should be converted to before the + * conversion to the special type happens. For example if type is AggregateReference this method returns the second + * parameter type of AggregateReference, in order to allow conversions to handle that type. + * + * @param ultimateTargetType ultimate target type to be returned by the conversion process. Must not be + * {@literal null}. + * @return a type that can be converted to the ultimate target type by module specific handling. Must not be + * {@literal null}. + */ + protected TypeInformation determineModuleReadTarget(TypeInformation ultimateTargetType) { + return ultimateTargetType; } /** @@ -683,33 +716,28 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { return null; } - if (getConversions().isSimpleType(value.getClass())) { + // custom conversion + Optional> customWriteTarget = determinCustomWriteTarget(value, type); - Optional> customWriteTarget = getConversions().hasCustomWriteTarget(value.getClass(), type.getType()) - ? getConversions().getCustomWriteTarget(value.getClass(), type.getType()) - : getConversions().getCustomWriteTarget(type.getType()); - - if (customWriteTarget.isPresent()) { - return getConversionService().convert(value, customWriteTarget.get()); - } + if (customWriteTarget.isPresent()) { + return getConversionService().convert(value, customWriteTarget.get()); + } - if (TypeInformation.OBJECT != type) { + return getPotentiallyConvertedSimpleWrite(value, type); + } - if (type.getType().isAssignableFrom(value.getClass())) { + private Optional> determinCustomWriteTarget(Object value, TypeInformation type) { - if (value.getClass().isEnum()) { - return getPotentiallyConvertedSimpleWrite(value); - } + return getConversions().getCustomWriteTarget(value.getClass(), type.getType()) + .or(() -> getConversions().getCustomWriteTarget(type.getType())) + .or(() -> getConversions().getCustomWriteTarget(value.getClass())); + } - return value; - } else { - if (getConversionService().canConvert(value.getClass(), type.getType())) { - value = getConversionService().convert(value, type.getType()); - } - } - } + @Nullable + protected Object getPotentiallyConvertedSimpleWrite(Object value, TypeInformation type) { - return getPotentiallyConvertedSimpleWrite(value); + if (value instanceof Enum enumValue) { + return enumValue.name(); } if (value.getClass().isArray()) { @@ -731,9 +759,10 @@ public Object writeValue(@Nullable Object value, TypeInformation type) { } } - return - - getConversionService().convert(value, type.getType()); + if (getConversionService().canConvert(value.getClass(), type.getType())) { + return getConversionService().convert(value, type.getType()); + } + return value; } private Object writeArray(Object value, TypeInformation type) { @@ -1174,7 +1203,9 @@ public Object getValue(AggregatePath path) { return null; } - return context.convert(value, path.getRequiredLeafProperty().getTypeInformation()); + // TODO: converting here seems wrong, since we have the ConvertingParameterValueProvider + // return context.convert(value, path.getRequiredLeafProperty().getTypeInformation()); + return value; } @Override