Skip to content

Commit 3dd2492

Browse files
committed
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
1 parent 783c6a8 commit 3dd2492

File tree

2 files changed

+77
-9
lines changed

2 files changed

+77
-9
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,15 @@ private <R> R doReadProjection(ConversionContext context, CassandraValueProvider
344344

345345
EntityInstantiator instantiator = instantiators.getInstantiatorFor(mappedEntity);
346346
R instance = instantiator.createInstance(mappedEntity, provider);
347-
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
348347

349-
readProperties(context, mappedEntity, valueProviderToUse, accessor, Predicates.isTrue());
348+
if (mappedEntity.requiresPropertyPopulation()) {
350349

351-
return accessor.getBean();
350+
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
351+
readProperties(context, mappedEntity, valueProviderToUse, accessor, isConstructorArgument(mappedEntity).negate());
352+
return accessor.getBean();
353+
}
354+
355+
return instance;
352356
}
353357

354358
private Object doReadOrProject(ConversionContext context, Row row, TypeInformation<?> typeHint,
@@ -513,14 +517,19 @@ private <S> S doReadEntity(ConversionContext context, CassandraValueProvider val
513517
EntityInstantiator instantiator = this.instantiators.getInstantiatorFor(entity);
514518
S instance = instantiator.createInstance(entity, provider);
515519

516-
if (entity.requiresPropertyPopulation()) {
517-
ConvertingPropertyAccessor<S> propertyAccessor = newConvertingPropertyAccessor(instance, entity);
520+
return populateProperties(context, entity, valueProvider, instance);
521+
}
522+
523+
private <S> S populateProperties(ConversionContext context, CassandraPersistentEntity<?> entity,
524+
CassandraValueProvider valueProvider, S instance) {
518525

519-
readProperties(context, entity, valueProvider, propertyAccessor, isConstructorArgument(entity).negate());
520-
return propertyAccessor.getBean();
526+
if (!entity.requiresPropertyPopulation()) {
527+
return instance;
521528
}
522529

523-
return instance;
530+
ConvertingPropertyAccessor<S> propertyAccessor = newConvertingPropertyAccessor(instance, entity);
531+
readProperties(context, entity, valueProvider, propertyAccessor, isConstructorArgument(entity).negate());
532+
return propertyAccessor.getBean();
524533
}
525534

526535
private void readProperties(ConversionContext context, CassandraPersistentEntity<?> entity,
@@ -1419,7 +1428,7 @@ public <T> T getParameterValue(Parameter<T, CassandraPersistentProperty> paramet
14191428

14201429
}
14211430

1422-
private record PropertyTranslatingPropertyAccessor<T> (PersistentPropertyAccessor<T> delegate,
1431+
private record PropertyTranslatingPropertyAccessor<T>(PersistentPropertyAccessor<T> delegate,
14231432
PersistentPropertyTranslator propertyTranslator) implements PersistentPropertyAccessor<T> {
14241433

14251434
static <T> PersistentPropertyAccessor<T> create(PersistentPropertyAccessor<T> delegate,

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919
import static org.springframework.data.cassandra.core.mapping.BasicMapId.*;
2020
import static org.springframework.data.cassandra.test.util.RowMockUtil.*;
2121

22+
import com.carrotsearch.hppc.mutables.DoubleHolder;
2223
import lombok.AllArgsConstructor;
2324
import lombok.Data;
2425
import lombok.EqualsAndHashCode;
2526
import lombok.NoArgsConstructor;
2627
import lombok.RequiredArgsConstructor;
2728
import lombok.ToString;
2829

30+
import java.awt.print.Book;
2931
import java.io.Serializable;
3032
import java.math.BigDecimal;
3133
import java.math.BigInteger;
3234
import java.net.InetAddress;
3335
import java.net.UnknownHostException;
36+
import java.nio.ByteBuffer;
3437
import java.time.Instant;
3538
import java.time.LocalDate;
3639
import java.time.LocalDateTime;
@@ -49,11 +52,14 @@
4952
import java.util.Set;
5053
import java.util.UUID;
5154

55+
import org.assertj.core.data.Percentage;
5256
import org.json.simple.JSONObject;
5357
import org.json.simple.parser.JSONParser;
5458
import org.json.simple.parser.ParseException;
5559
import org.junit.jupiter.api.BeforeEach;
5660
import org.junit.jupiter.api.Test;
61+
62+
import org.springframework.beans.DirectFieldAccessor;
5763
import org.springframework.beans.factory.annotation.Value;
5864
import org.springframework.core.convert.converter.Converter;
5965
import org.springframework.data.annotation.Id;
@@ -82,6 +88,9 @@
8288
import org.springframework.data.cassandra.domain.UserToken;
8389
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
8490
import org.springframework.data.cassandra.test.util.RowMockUtil;
91+
import org.springframework.data.convert.ReadingConverter;
92+
import org.springframework.data.convert.SimplePropertyValueConversions;
93+
import org.springframework.data.convert.ValueConverter;
8594
import org.springframework.data.projection.EntityProjection;
8695
import org.springframework.data.projection.EntityProjectionIntrospector;
8796
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -110,9 +119,13 @@ public class MappingCassandraConverterUnitTests {
110119
@BeforeEach
111120
void setUp() {
112121

122+
CassandraCustomConversions conversions = new CassandraCustomConversions(
123+
List.of(new ByteBufferToDoubleHolderConverter()));
113124
this.mappingContext = new CassandraMappingContext();
125+
this.mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
114126

115127
this.mappingCassandraConverter = new MappingCassandraConverter(mappingContext);
128+
this.mappingCassandraConverter.setCustomConversions(conversions);
116129
this.mappingCassandraConverter.afterPropertiesSet();
117130
}
118131

@@ -1012,6 +1025,27 @@ void shouldCreateDtoProjectionsThroughConstructor() {
10121025
assertThat(result.getTuple().one).isEqualTo("One");
10131026
}
10141027

1028+
@Test // GH-1472
1029+
void projectShouldReadDtoProjectionPropertiesOnlyOnce() {
1030+
1031+
ByteBuffer number = ByteBuffer.allocate(8);
1032+
number.putDouble(1.2d);
1033+
number.flip();
1034+
1035+
rowMock = RowMockUtil.newRowMock(RowMockUtil.column("number", number, DataTypes.BLOB));
1036+
1037+
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(
1038+
new SpelAwareProxyProjectionFactory(), EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy(),
1039+
this.mappingContext);
1040+
1041+
EntityProjection<DoubleHolderDto, WithDoubleHolder> projection = introspector.introspect(DoubleHolderDto.class,
1042+
WithDoubleHolder.class);
1043+
1044+
DoubleHolderDto result = this.mappingCassandraConverter.project(projection, rowMock);
1045+
1046+
assertThat(result.number.number).isCloseTo(1.2, Percentage.withPercentage(1));
1047+
}
1048+
10151049
private static List<Object> getValues(Map<CqlIdentifier, Object> statement) {
10161050
return new ArrayList<>(statement.values());
10171051
}
@@ -1522,4 +1556,29 @@ static class Author {
15221556
String lastName;
15231557

15241558
}
1559+
1560+
@ReadingConverter
1561+
static class ByteBufferToDoubleHolderConverter implements Converter<ByteBuffer, DoubleHolder> {
1562+
1563+
@Override
1564+
public DoubleHolder convert(ByteBuffer source) {
1565+
return new DoubleHolder(source.getDouble());
1566+
}
1567+
}
1568+
1569+
record DoubleHolder(double number) {
1570+
1571+
}
1572+
1573+
static class WithDoubleHolder {
1574+
DoubleHolder number;
1575+
}
1576+
1577+
static class DoubleHolderDto {
1578+
DoubleHolder number;
1579+
1580+
public DoubleHolderDto(DoubleHolder number) {
1581+
this.number = number;
1582+
}
1583+
}
15251584
}

0 commit comments

Comments
 (0)