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
This commit is contained in:
Mark Paluch
2024-01-29 09:26:38 +01:00
parent e68a0472c7
commit de69842956
2 changed files with 71 additions and 9 deletions

View File

@@ -346,11 +346,15 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
EntityInstantiator instantiator = instantiators.getInstantiatorFor(mappedEntity);
R instance = instantiator.createInstance(mappedEntity, provider);
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
readProperties(context, mappedEntity, valueProviderToUse, accessor, Predicates.isTrue());
if (mappedEntity.requiresPropertyPopulation()) {
return accessor.getBean();
PersistentPropertyAccessor<R> 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<S> propertyAccessor = newConvertingPropertyAccessor(instance, entity);
return populateProperties(context, entity, valueProvider, instance);
}
readProperties(context, entity, valueProvider, propertyAccessor, isConstructorArgument(entity).negate());
return propertyAccessor.getBean();
private <S> S populateProperties(ConversionContext context, CassandraPersistentEntity<?> entity,
CassandraValueProvider valueProvider, S instance) {
if (!entity.requiresPropertyPopulation()) {
return instance;
}
return instance;
ConvertingPropertyAccessor<S> 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<T> (PersistentPropertyAccessor<T> delegate,
private record PropertyTranslatingPropertyAccessor<T>(PersistentPropertyAccessor<T> delegate,
PersistentPropertyTranslator propertyTranslator) implements PersistentPropertyAccessor<T> {
static <T> PersistentPropertyAccessor<T> create(PersistentPropertyAccessor<T> delegate,

View File

@@ -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<DoubleHolderDto, WithDoubleHolder> 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<ByteBuffer, DoubleHolder> {
@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;
}
}
}