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
This commit is contained in:
@@ -29,6 +29,7 @@ import io.r2dbc.spi.test.MockRowMetadata;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -43,6 +44,7 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
@@ -91,7 +93,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
.bindMarkers(PostgresDialect.INSTANCE.getBindMarkersFactory()).build();
|
||||
|
||||
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(PostgresDialect.INSTANCE, new MoneyConverter(),
|
||||
new RowConverter(), new RowDocumentConverter());
|
||||
new RowConverter(), new RowDocumentConverter(), new PkConverter());
|
||||
|
||||
entityTemplate = new R2dbcEntityTemplate(client, PostgresDialect.INSTANCE,
|
||||
new MappingR2dbcConverter(new R2dbcMappingContext(), conversions));
|
||||
@@ -632,6 +634,53 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-1725
|
||||
void projectDtoShouldReadPropertiesOnce() {
|
||||
|
||||
MockRowMetadata metadata = MockRowMetadata.builder()
|
||||
.columnMetadata(MockColumnMetadata.builder().name("number").type(R2dbcType.BINARY).build()).build();
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
|
||||
byteBuffer.putDouble(1.2);
|
||||
byteBuffer.flip();
|
||||
|
||||
MockResult result = MockResult.builder()
|
||||
.row(MockRow.builder().identified("number", Object.class, byteBuffer).metadata(metadata).build()).build();
|
||||
|
||||
recorder.addStubbing(s -> s.startsWith("SELECT"), result);
|
||||
|
||||
entityTemplate.select(WithDoubleHolder.class).as(DoubleHolderProjection.class).all().as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
assertThat(actual.number.number).isCloseTo(1.2d, withinPercentage(1d));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
static class PkConverter implements Converter<ByteBuffer, DoubleHolder> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DoubleHolder convert(ByteBuffer source) {
|
||||
return new DoubleHolder(source.getDouble());
|
||||
}
|
||||
}
|
||||
|
||||
static class WithDoubleHolder {
|
||||
DoubleHolder number;
|
||||
}
|
||||
|
||||
static class DoubleHolderProjection {
|
||||
DoubleHolder number;
|
||||
|
||||
public DoubleHolderProjection(DoubleHolder number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
|
||||
record DoubleHolder(double number) {
|
||||
|
||||
}
|
||||
|
||||
@Test // GH-1696
|
||||
void shouldConsiderRowDocumentConverter() {
|
||||
|
||||
|
||||
@@ -238,17 +238,8 @@ public class MappingRelationalConverter extends AbstractRelationalConverter impl
|
||||
|
||||
EntityInstantiator instantiator = getEntityInstantiators().getInstantiatorFor(mappedEntity);
|
||||
R instance = instantiator.createInstance(mappedEntity, provider);
|
||||
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
|
||||
|
||||
populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);
|
||||
|
||||
PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor,
|
||||
getConversionService());
|
||||
RelationalPropertyValueProvider valueProvider = newValueProvider(documentAccessor, evaluator, context);
|
||||
|
||||
readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, Predicates.isTrue());
|
||||
|
||||
return accessor.getBean();
|
||||
return populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);
|
||||
}
|
||||
|
||||
private Object doReadOrProject(ConversionContext context, RowDocument source, TypeInformation<?> typeHint,
|
||||
@@ -452,11 +443,7 @@ public class MappingRelationalConverter extends AbstractRelationalConverter impl
|
||||
EntityInstantiator instantiator = getEntityInstantiators().getInstantiatorFor(entity);
|
||||
S instance = instantiator.createInstance(entity, provider);
|
||||
|
||||
if (entity.requiresPropertyPopulation()) {
|
||||
return populateProperties(context, entity, documentAccessor, evaluator, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
return populateProperties(context, entity, documentAccessor, evaluator, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -509,6 +496,10 @@ public class MappingRelationalConverter extends AbstractRelationalConverter impl
|
||||
private <S> S populateProperties(ConversionContext context, RelationalPersistentEntity<S> entity,
|
||||
RowDocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) {
|
||||
|
||||
if (!entity.requiresPropertyPopulation()) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
||||
getConversionService());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user