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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user