diff --git a/pom.xml b/pom.xml index 02461b8e40..8b8a8a7f89 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 8d987fb028..0477b57d33 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.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index fddbaab696..d500702a4a 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java index 52ddb460ec..50167ad4f3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java @@ -20,7 +20,7 @@ import java.util.Collections; import java.util.List; -import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.convert.CustomConversions; @@ -39,21 +39,8 @@ */ public class JdbcCustomConversions extends CustomConversions { - private static final Collection STORE_CONVERTERS; - - static { - - List converters = new ArrayList<>(Jsr310TimestampBasedConverters.getConvertersToRegister()); - - converters - .addAll(AggregateReferenceConverters.getConvertersToRegister(DefaultConversionService.getSharedInstance())); - - STORE_CONVERTERS = Collections.unmodifiableCollection(converters); - - } - - private static final StoreConversions STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER, - STORE_CONVERTERS); + private static final Collection STORE_CONVERTERS = Collections + .unmodifiableCollection(Jsr310TimestampBasedConverters.getConvertersToRegister()); /** * Creates an empty {@link JdbcCustomConversions} object. @@ -69,12 +56,7 @@ public JdbcCustomConversions() { * @param converters must not be {@literal null}. */ public JdbcCustomConversions(List converters) { - - super(new ConverterConfiguration( // - STORE_CONVERSIONS, // - converters, // - JdbcCustomConversions::excludeConversionsBetweenDateAndJsr310Types // - )); + super(constructConverterConfiguration(converters)); } /** @@ -103,6 +85,16 @@ public JdbcCustomConversions(ConverterConfiguration converterConfiguration) { super(converterConfiguration); } + private static ConverterConfiguration constructConverterConfiguration(List converters) { + + return new ConverterConfiguration( // + StoreConversions.of(JdbcSimpleTypes.HOLDER, STORE_CONVERTERS), // + converters, // + JdbcCustomConversions::excludeConversionsBetweenDateAndJsr310Types // + ); + } + + /** * Obtain a read only copy of default store converters. * 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 a5a87ce8cd..ebb4d58717 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 @@ -29,6 +29,7 @@ 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; @@ -91,6 +92,8 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r this.typeFactory = JdbcTypeFactory.unsupported(); this.relationResolver = relationResolver; + + registerAggregateReferenceConverters(); } /** @@ -110,6 +113,14 @@ 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 @@ -327,7 +338,8 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele this.accessor = accessor; this.context = context; this.identifier = path.isEntity() - ? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), property -> delegate.getValue(path.append(property))) + ? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), + property -> delegate.getValue(path.append(property))) : identifier; } 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 c56f6d41ab..d20ea64d66 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 @@ -19,6 +19,7 @@ import static org.assertj.core.api.SoftAssertions.*; import static org.mockito.Mockito.*; +import java.nio.ByteBuffer; import java.sql.Array; import java.sql.Timestamp; import java.time.Instant; @@ -28,13 +29,16 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; 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.annotation.Id; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; @@ -50,22 +54,28 @@ * Unit tests for {@link MappingJdbcConverter}. * * @author Mark Paluch + * @author Jens Schauder */ -public class MappingJdbcConverterUnitTests { +class MappingJdbcConverterUnitTests { - JdbcMappingContext context = new JdbcMappingContext(); - StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory(); - MappingJdbcConverter converter = new MappingJdbcConverter( // + 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 }; + + private JdbcMappingContext context = new JdbcMappingContext(); + private StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory(); + private MappingJdbcConverter converter = new MappingJdbcConverter( // context, // (identifier, path) -> { throw new UnsupportedOperationException(); }, // new JdbcCustomConversions(), // - typeFactory // + typeFactory // ); @Test // DATAJDBC-104, DATAJDBC-1384 - public void testTargetTypesForPropertyType() { + void testTargetTypesForPropertyType() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); @@ -86,7 +96,7 @@ public void testTargetTypesForPropertyType() { } @Test // DATAJDBC-259 - public void classificationOfCollectionLikeProperties() { + void classificationOfCollectionLikeProperties() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); @@ -102,7 +112,7 @@ public void classificationOfCollectionLikeProperties() { } @Test // DATAJDBC-221 - public void referencesAreNotEntitiesAndGetStoredAsTheirId() { + void referencesAreNotEntitiesAndGetStoredAsTheirId() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); @@ -152,6 +162,39 @@ void accessesCorrectValuesForOneToOneRelationshipWithIdenticallyNamedIdPropertie assertThat(result).isEqualTo(new WithOneToOne("one", new Referenced(23L))); } + @Test // GH-1750 + void readByteArrayToNestedUuidWithCustomConverter() { + + JdbcMappingContext context = new JdbcMappingContext(); + StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory(); + Converter customConverter = new ByteArrayToUuid(); + MappingJdbcConverter converter = new MappingJdbcConverter( // + context, // + (identifier, path) -> { + throw new UnsupportedOperationException(); + }, // + new JdbcCustomConversions(Collections.singletonList(customConverter)), // + typeFactory // + ); + + assertSoftly(softly -> { + checkReadConversion(softly, converter, "uuidRef", AggregateReference.to(UUID)); + checkReadConversion(softly, converter, "uuid", UUID); + checkReadConversion(softly, converter, "optionalUuid", Optional.of(UUID)); + }); + + } + + private static void checkReadConversion(SoftAssertions softly, MappingJdbcConverter converter, String propertyName, + Object expected) { + + RelationalPersistentProperty property = converter.getMappingContext().getRequiredPersistentEntity(DummyEntity.class) + .getRequiredPersistentProperty(propertyName); + Object value = converter.readValue(BYTES_REPRESENTING_UUID, property.getTypeInformation() // + ); + + softly.assertThat(value).isEqualTo(expected); + } private void checkConversionToTimestampAndBack(SoftAssertions softly, RelationalPersistentEntity persistentEntity, String propertyName, Object value) { @@ -187,6 +230,8 @@ private static class DummyEntity { private final Timestamp timestamp; private final AggregateReference reference; private final UUID uuid; + private final AggregateReference uuidRef; + private final Optional optionalUuid; // DATAJDBC-259 private final List listOfString; @@ -195,9 +240,10 @@ private static class DummyEntity { 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, List listOfString, - String[] arrayOfString, List listOfEntity, OtherEntity[] arrayOfEntity) { + 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; @@ -210,6 +256,8 @@ private DummyEntity(Long id, SomeEnum someEnum, LocalDateTime localDateTime, Loc this.timestamp = timestamp; this.reference = reference; this.uuid = uuid; + this.uuidRef = uuidRef; + this.optionalUuid = optionalUUID; this.listOfString = listOfString; this.arrayOfString = arrayOfString; this.listOfEntity = listOfEntity; @@ -290,7 +338,7 @@ private enum SomeEnum { private static class OtherEntity {} private static class StubbedJdbcTypeFactory implements JdbcTypeFactory { - public Object[] arraySource; + Object[] arraySource; @Override public Array createArray(Object[] value) { @@ -299,9 +347,23 @@ public Array createArray(Object[] value) { } } - record WithOneToOne(@Id String id,@MappedCollection(idColumn = "renamed") Referenced referenced){} + private record WithOneToOne(@Id String id, @MappedCollection(idColumn = "renamed") Referenced referenced) { + } + + private record Referenced(@Id Long id) { + } - record Referenced(@Id Long id) { + private record ReferencedByUuid(@Id UUID id) { } + static class ByteArrayToUuid implements Converter { + @Override + public UUID convert(byte[] source) { + + ByteBuffer byteBuffer = ByteBuffer.wrap(source); + long high = byteBuffer.getLong(); + long low = byteBuffer.getLong(); + return new UUID(high, low); + } + } } diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index c834dc4cab..c906778f5e 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 671e71d242..a76f84146a 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.3.0-SNAPSHOT + 3.3.0-1750-custom-converters-for-aggregateref-SNAPSHOT diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java index a40dfb2271..f9fe08a9d7 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java @@ -17,6 +17,7 @@ import java.util.Collections; +import org.jetbrains.annotations.NotNull; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; @@ -47,7 +48,7 @@ public abstract class AbstractRelationalConverter implements RelationalConverter * @param context must not be {@literal null}. */ public AbstractRelationalConverter(RelationalMappingContext context) { - this(context, new CustomConversions(StoreConversions.NONE, Collections.emptyList()), new DefaultConversionService(), + this(context, new CustomConversions(StoreConversions.NONE, Collections.emptyList()), createBaseConversionService(), new EntityInstantiators()); } @@ -58,7 +59,7 @@ public AbstractRelationalConverter(RelationalMappingContext context) { * @param conversions must not be {@literal null}. */ public AbstractRelationalConverter(RelationalMappingContext context, CustomConversions conversions) { - this(context, conversions, new DefaultConversionService(), new EntityInstantiators()); + this(context, conversions, createBaseConversionService(), new EntityInstantiators()); } private AbstractRelationalConverter(RelationalMappingContext context, CustomConversions conversions, @@ -75,6 +76,14 @@ private AbstractRelationalConverter(RelationalMappingContext context, CustomConv conversions.registerConvertersIn(this.conversionService); } + @NotNull + private static DefaultConversionService createBaseConversionService() { + + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.removeConvertible(Object[].class, Object.class); + return conversionService; + } + @Override public ConversionService getConversionService() { return conversionService; 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 b3fbfc441b..3469c8b9fd 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 @@ -134,7 +134,6 @@ public MappingRelationalConverter(RelationalMappingContext context, CustomConver this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE); this.introspector = createIntrospector(projectionFactory, getConversions(), getMappingContext()); - } private static EntityProjectionIntrospector createIntrospector(ProjectionFactory projectionFactory, @@ -624,14 +623,6 @@ public Object readValue(@Nullable Object value, TypeInformation type) { return null; } - if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) { - - TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(value.getClass()); - TypeDescriptor targetDescriptor = createTypeDescriptor(type); - - return getConversionService().convert(value, sourceDescriptor, targetDescriptor); - } - return getPotentiallyConvertedSimpleRead(value, type); }