From de6984295681d547aee2c2f607416c97b2ab3543 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 29 Jan 2024 09:26:38 +0100 Subject: [PATCH] Read DTO projection properties only once. We ensure to not read DTO properties multiple times if these are already read by their persistence creator. Closes #1472 --- .../convert/MappingCassandraConverter.java | 27 ++++++---- .../MappingCassandraConverterUnitTests.java | 53 +++++++++++++++++++ 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java index 636398652..bebabc131 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java @@ -346,11 +346,15 @@ public class MappingCassandraConverter extends AbstractCassandraConverter EntityInstantiator instantiator = instantiators.getInstantiatorFor(mappedEntity); R instance = instantiator.createInstance(mappedEntity, provider); - PersistentPropertyAccessor accessor = mappedEntity.getPropertyAccessor(instance); - readProperties(context, mappedEntity, valueProviderToUse, accessor, Predicates.isTrue()); + if (mappedEntity.requiresPropertyPopulation()) { - return accessor.getBean(); + PersistentPropertyAccessor accessor = mappedEntity.getPropertyAccessor(instance); + readProperties(context, mappedEntity, valueProviderToUse, accessor, isConstructorArgument(mappedEntity).negate()); + return accessor.getBean(); + } + + return instance; } private Object doReadOrProject(ConversionContext context, Row row, TypeInformation typeHint, @@ -515,14 +519,19 @@ public class MappingCassandraConverter extends AbstractCassandraConverter EntityInstantiator instantiator = this.instantiators.getInstantiatorFor(entity); S instance = instantiator.createInstance(entity, provider); - if (entity.requiresPropertyPopulation()) { - ConvertingPropertyAccessor propertyAccessor = newConvertingPropertyAccessor(instance, entity); + return populateProperties(context, entity, valueProvider, instance); + } - readProperties(context, entity, valueProvider, propertyAccessor, isConstructorArgument(entity).negate()); - return propertyAccessor.getBean(); + private S populateProperties(ConversionContext context, CassandraPersistentEntity entity, + CassandraValueProvider valueProvider, S instance) { + + if (!entity.requiresPropertyPopulation()) { + return instance; } - return instance; + ConvertingPropertyAccessor propertyAccessor = newConvertingPropertyAccessor(instance, entity); + readProperties(context, entity, valueProvider, propertyAccessor, isConstructorArgument(entity).negate()); + return propertyAccessor.getBean(); } private void readProperties(ConversionContext context, CassandraPersistentEntity entity, @@ -1437,7 +1446,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter } - private record PropertyTranslatingPropertyAccessor (PersistentPropertyAccessor delegate, + private record PropertyTranslatingPropertyAccessor(PersistentPropertyAccessor delegate, PersistentPropertyTranslator propertyTranslator) implements PersistentPropertyAccessor { static PersistentPropertyAccessor create(PersistentPropertyAccessor delegate, diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java index 9641502c0..5f80b6b84 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java @@ -24,6 +24,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.ByteBuffer; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -31,6 +32,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.util.*; +import org.assertj.core.data.Percentage; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; @@ -55,6 +57,7 @@ import org.springframework.data.cassandra.domain.User; import org.springframework.data.cassandra.domain.UserToken; import org.springframework.data.cassandra.support.UserDefinedTypeBuilder; import org.springframework.data.cassandra.test.util.RowMockUtil; +import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.SimplePropertyValueConversions; import org.springframework.data.convert.ValueConverter; import org.springframework.data.projection.EntityProjection; @@ -88,9 +91,13 @@ public class MappingCassandraConverterUnitTests { @BeforeEach void setUp() { + CassandraCustomConversions conversions = new CassandraCustomConversions( + List.of(new ByteBufferToDoubleHolderConverter())); this.mappingContext = new CassandraMappingContext(); + this.mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); this.converter = new MappingCassandraConverter(mappingContext); + this.converter.setCustomConversions(conversions); this.converter.afterPropertiesSet(); } @@ -1074,6 +1081,27 @@ public class MappingCassandraConverterUnitTests { assertThat(result.tuple().one).isEqualTo("One"); } + @Test // GH-1472 + void projectShouldReadDtoProjectionPropertiesOnlyOnce() { + + ByteBuffer number = ByteBuffer.allocate(8); + number.putDouble(1.2d); + number.flip(); + + rowMock = RowMockUtil.newRowMock(RowMockUtil.column("number", number, DataTypes.BLOB)); + + EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create( + new SpelAwareProxyProjectionFactory(), EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy(), + this.mappingContext); + + EntityProjection projection = introspector.introspect(DoubleHolderDto.class, + WithDoubleHolder.class); + + DoubleHolderDto result = this.converter.project(projection, rowMock); + + assertThat(result.number.number).isCloseTo(1.2, Percentage.withPercentage(1)); + } + @Test // GH-1471 void propertyValueConversionsCacheShouldConsiderPropertyEquality() { @@ -1770,4 +1798,29 @@ public class MappingCassandraConverterUnitTests { this.lastName = lastName; } } + + @ReadingConverter + static class ByteBufferToDoubleHolderConverter implements Converter { + + @Override + public DoubleHolder convert(ByteBuffer source) { + return new DoubleHolder(source.getDouble()); + } + } + + record DoubleHolder(double number) { + + } + + static class WithDoubleHolder { + DoubleHolder number; + } + + static class DoubleHolderDto { + DoubleHolder number; + + public DoubleHolderDto(DoubleHolder number) { + this.number = number; + } + } }