Skip to content

Commit 52fe74e

Browse files
committed
Read properties for DTO projections only once.
We now skip property population for properties that are populated through an entity creator (constructor/factory method). Closes #1725
1 parent be94f76 commit 52fe74e

File tree

2 files changed

+56
-16
lines changed

2 files changed

+56
-16
lines changed

spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java

+50-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import reactor.core.publisher.Mono;
3030
import reactor.test.StepVerifier;
3131

32+
import java.nio.ByteBuffer;
3233
import java.time.LocalDateTime;
3334
import java.util.ArrayList;
3435
import java.util.Collections;
@@ -43,6 +44,7 @@
4344
import org.springframework.data.annotation.LastModifiedDate;
4445
import org.springframework.data.annotation.Version;
4546
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
47+
import org.springframework.data.convert.ReadingConverter;
4648
import org.springframework.data.domain.Sort;
4749
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
4850
import org.springframework.data.mapping.context.PersistentEntities;
@@ -91,7 +93,7 @@ void before() {
9193
.bindMarkers(PostgresDialect.INSTANCE.getBindMarkersFactory()).build();
9294

9395
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(PostgresDialect.INSTANCE, new MoneyConverter(),
94-
new RowConverter(), new RowDocumentConverter());
96+
new RowConverter(), new RowDocumentConverter(), new PkConverter());
9597

9698
entityTemplate = new R2dbcEntityTemplate(client, PostgresDialect.INSTANCE,
9799
new MappingR2dbcConverter(new R2dbcMappingContext(), conversions));
@@ -632,6 +634,53 @@ void shouldConsiderRowConverter() {
632634
}).verifyComplete();
633635
}
634636

637+
@Test // GH-1725
638+
void projectDtoShouldReadPropertiesOnce() {
639+
640+
MockRowMetadata metadata = MockRowMetadata.builder()
641+
.columnMetadata(MockColumnMetadata.builder().name("number").type(R2dbcType.BINARY).build()).build();
642+
643+
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
644+
byteBuffer.putDouble(1.2);
645+
byteBuffer.flip();
646+
647+
MockResult result = MockResult.builder()
648+
.row(MockRow.builder().identified("number", Object.class, byteBuffer).metadata(metadata).build()).build();
649+
650+
recorder.addStubbing(s -> s.startsWith("SELECT"), result);
651+
652+
entityTemplate.select(WithDoubleHolder.class).as(DoubleHolderProjection.class).all().as(StepVerifier::create) //
653+
.assertNext(actual -> {
654+
assertThat(actual.number.number).isCloseTo(1.2d, withinPercentage(1d));
655+
}).verifyComplete();
656+
}
657+
658+
@ReadingConverter
659+
static class PkConverter implements Converter<ByteBuffer, DoubleHolder> {
660+
661+
@Nullable
662+
@Override
663+
public DoubleHolder convert(ByteBuffer source) {
664+
return new DoubleHolder(source.getDouble());
665+
}
666+
}
667+
668+
static class WithDoubleHolder {
669+
DoubleHolder number;
670+
}
671+
672+
static class DoubleHolderProjection {
673+
DoubleHolder number;
674+
675+
public DoubleHolderProjection(DoubleHolder number) {
676+
this.number = number;
677+
}
678+
}
679+
680+
record DoubleHolder(double number) {
681+
682+
}
683+
635684
@Test // GH-1696
636685
void shouldConsiderRowDocumentConverter() {
637686

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java

+6-15
Original file line numberDiff line numberDiff line change
@@ -238,17 +238,8 @@ String getColumnName(RelationalPersistentProperty prop) {
238238

239239
EntityInstantiator instantiator = getEntityInstantiators().getInstantiatorFor(mappedEntity);
240240
R instance = instantiator.createInstance(mappedEntity, provider);
241-
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
242241

243-
populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);
244-
245-
PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor,
246-
getConversionService());
247-
RelationalPropertyValueProvider valueProvider = newValueProvider(documentAccessor, evaluator, context);
248-
249-
readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, Predicates.isTrue());
250-
251-
return accessor.getBean();
242+
return populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);
252243
}
253244

254245
private Object doReadOrProject(ConversionContext context, RowDocument source, TypeInformation<?> typeHint,
@@ -452,11 +443,7 @@ private <S> S read(ConversionContext context, RelationalPersistentEntity<S> enti
452443
EntityInstantiator instantiator = getEntityInstantiators().getInstantiatorFor(entity);
453444
S instance = instantiator.createInstance(entity, provider);
454445

455-
if (entity.requiresPropertyPopulation()) {
456-
return populateProperties(context, entity, documentAccessor, evaluator, instance);
457-
}
458-
459-
return instance;
446+
return populateProperties(context, entity, documentAccessor, evaluator, instance);
460447
}
461448

462449
@Override
@@ -509,6 +496,10 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {
509496
private <S> S populateProperties(ConversionContext context, RelationalPersistentEntity<S> entity,
510497
RowDocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) {
511498

499+
if (!entity.requiresPropertyPopulation()) {
500+
return instance;
501+
}
502+
512503
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
513504
getConversionService());
514505

0 commit comments

Comments
 (0)