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 783c6a87ec
commit 3dd24923c8
2 changed files with 77 additions and 9 deletions

View File

@@ -344,11 +344,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,
@@ -513,14 +517,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,
@@ -1419,7 +1428,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

@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.cassandra.core.mapping.BasicMapId.*;
import static org.springframework.data.cassandra.test.util.RowMockUtil.*;
import com.carrotsearch.hppc.mutables.DoubleHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -26,11 +27,13 @@ import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.awt.print.Book;
import java.io.Serializable;
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;
@@ -49,11 +52,14 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.assertj.core.data.Percentage;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
@@ -82,6 +88,9 @@ 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;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -110,9 +119,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.mappingCassandraConverter = new MappingCassandraConverter(mappingContext);
this.mappingCassandraConverter.setCustomConversions(conversions);
this.mappingCassandraConverter.afterPropertiesSet();
}
@@ -1012,6 +1025,27 @@ public class MappingCassandraConverterUnitTests {
assertThat(result.getTuple().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.mappingCassandraConverter.project(projection, rowMock);
assertThat(result.number.number).isCloseTo(1.2, Percentage.withPercentage(1));
}
private static List<Object> getValues(Map<CqlIdentifier, Object> statement) {
return new ArrayList<>(statement.values());
}
@@ -1522,4 +1556,29 @@ public class MappingCassandraConverterUnitTests {
String 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;
}
}
}