Skip to content

Commit

Permalink
Read DTO projection properties only once.
Browse files Browse the repository at this point in the history
We ensure to not read DTO properties multiple times if these are already read by their persistence creator.

Closes #4626
  • Loading branch information
mp911de committed Jan 29, 2024
1 parent 51e2e7e commit f62e5f3
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,6 @@ FieldName getFieldName(MongoPersistentProperty prop) {

populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);

PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor, conversionService);
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(context, documentAccessor, evaluator,
spELContext);

readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, evaluator,
Predicates.isTrue());

return accessor.getBean();
}

Expand Down Expand Up @@ -518,16 +511,16 @@ private <S> S read(ConversionContext context, MongoPersistentEntity<S> entity, D
EntityInstantiator instantiator = instantiators.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);
}

private <S> S populateProperties(ConversionContext context, MongoPersistentEntity<S> entity,
DocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) {

if (!entity.requiresPropertyPopulation()) {
return instance;
}

PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);

Expand Down Expand Up @@ -578,7 +571,9 @@ private Object readIdValue(ConversionContext context, SpELExpressionEvaluator ev
String expression = idProperty.getSpelExpression();
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : rawId;

return resolvedValue != null ? readValue(context.forProperty(idProperty), resolvedValue, idProperty.getTypeInformation()) : null;
return resolvedValue != null
? readValue(context.forProperty(idProperty), resolvedValue, idProperty.getTypeInformation())
: null;
}

private void readProperties(ConversionContext context, MongoPersistentEntity<?> entity,
Expand Down Expand Up @@ -634,9 +629,8 @@ private DbRefResolverCallback getDbRefResolverCallback(ConversionContext context
}

@Nullable
private Object readAssociation(Association<MongoPersistentProperty> association,
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback,
ConversionContext context) {
private Object readAssociation(Association<MongoPersistentProperty> association, DocumentAccessor documentAccessor,
DbRefProxyHandler handler, DbRefResolverCallback callback, ConversionContext context) {

MongoPersistentProperty property = association.getInverse();
Object value = documentAccessor.get(property);
Expand All @@ -659,7 +653,7 @@ private Object readAssociation(Association<MongoPersistentProperty> association,
} else {

return dbRefResolver.resolveReference(property,
new DocumentReferenceSource(documentAccessor.getDocument(), documentAccessor.get(property)),
new DocumentReferenceSource(documentAccessor.getDocument(), documentAccessor.get(property)),
referenceLookupDelegate, context.forProperty(property)::convert);
}
}
Expand Down Expand Up @@ -2435,8 +2429,6 @@ class ProjectingConversionContext extends DefaultConversionContext {
this.returnedTypeDescriptor = projection;
}



@Override
public ConversionContext forProperty(String name) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.ByteBuffer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
Expand All @@ -31,6 +32,7 @@
import java.util.function.Function;
import java.util.stream.Stream;

import org.assertj.core.data.Percentage;
import org.bson.BsonUndefined;
import org.bson.types.Binary;
import org.bson.types.Code;
Expand Down Expand Up @@ -129,7 +131,8 @@ class MappingMongoConverterUnitTests {
@BeforeEach
void beforeEach() {

MongoCustomConversions conversions = new MongoCustomConversions();
MongoCustomConversions conversions = new MongoCustomConversions(
Arrays.asList(new ByteBufferToDoubleHolderConverter()));

mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(context);
Expand Down Expand Up @@ -1579,7 +1582,7 @@ void shouldWriteEntityWithGeoCircleCorrectly() {
assertThat(document.get("circle")).isInstanceOf(org.bson.Document.class);
assertThat(document.get("circle")).isEqualTo((Object) new org.bson.Document("center",
new org.bson.Document("x", circle.getCenter().getX()).append("y", circle.getCenter().getY()))
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
}

@Test // DATAMONGO-858
Expand Down Expand Up @@ -1612,7 +1615,7 @@ void shouldWriteEntityWithGeoSphereCorrectly() {
assertThat(document.get("sphere")).isInstanceOf(org.bson.Document.class);
assertThat(document.get("sphere")).isEqualTo((Object) new org.bson.Document("center",
new org.bson.Document("x", sphere.getCenter().getX()).append("y", sphere.getCenter().getY()))
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
}

@Test // DATAMONGO-858
Expand All @@ -1630,7 +1633,7 @@ void shouldWriteEntityWithGeoSphereWithMetricDistanceCorrectly() {
assertThat(document.get("sphere")).isInstanceOf(org.bson.Document.class);
assertThat(document.get("sphere")).isEqualTo((Object) new org.bson.Document("center",
new org.bson.Document("x", sphere.getCenter().getX()).append("y", sphere.getCenter().getY()))
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
}

@Test // DATAMONGO-858
Expand Down Expand Up @@ -1663,7 +1666,7 @@ void shouldWriteEntityWithGeoShapeCorrectly() {
assertThat(document.get("shape")).isInstanceOf(org.bson.Document.class);
assertThat(document.get("shape")).isEqualTo((Object) new org.bson.Document("center",
new org.bson.Document("x", sphere.getCenter().getX()).append("y", sphere.getCenter().getY()))
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
.append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString()));
}

@Test // DATAMONGO-858
Expand Down Expand Up @@ -2673,8 +2676,8 @@ void usesCustomConverterForPropertiesUsingTypesImplementingMapOnRead() {
converter.afterPropertiesSet();

org.bson.Document source = new org.bson.Document("typeImplementingMap",
new org.bson.Document("1st", "one").append("2nd", 2)).append("_class",
TypeWrappingTypeImplementingMap.class.getName());
new org.bson.Document("1st", "one").append("2nd", 2))
.append("_class", TypeWrappingTypeImplementingMap.class.getName());

TypeWrappingTypeImplementingMap target = converter.read(TypeWrappingTypeImplementingMap.class, source);

Expand Down Expand Up @@ -2862,8 +2865,8 @@ void projectShouldReadNestedInterfaceProjection() {
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
mappingContext);

EntityProjection<WithNestedInterfaceProjection, Person> projection = introspector.introspect(WithNestedInterfaceProjection.class,
Person.class);
EntityProjection<WithNestedInterfaceProjection, Person> projection = introspector
.introspect(WithNestedInterfaceProjection.class, Person.class);
WithNestedInterfaceProjection person = converter.project(projection, source);

assertThat(person.getFirstname()).isEqualTo("spring");
Expand All @@ -2881,14 +2884,35 @@ void projectShouldReadNestedDtoProjection() {
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
mappingContext);

EntityProjection<WithNestedDtoProjection, Person> projection = introspector.introspect(WithNestedDtoProjection.class,
Person.class);
EntityProjection<WithNestedDtoProjection, Person> projection = introspector
.introspect(WithNestedDtoProjection.class, Person.class);
WithNestedDtoProjection person = converter.project(projection, source);

assertThat(person.getFirstname()).isEqualTo("spring");
assertThat(person.getAddress().getStreet()).isEqualTo("data");
}

@Test // GH-4626
void projectShouldReadDtoProjectionPropertiesOnlyOnce() {

ByteBuffer number = ByteBuffer.allocate(8);
number.putDouble(1.2d);
number.flip();

org.bson.Document source = new org.bson.Document("number", number);

EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
mappingContext);

EntityProjection<DoubleHolderDto, WithDoubleHolder> projection = introspector.introspect(DoubleHolderDto.class,
WithDoubleHolder.class);
DoubleHolderDto result = converter.project(projection, source);

assertThat(result.number.number).isCloseTo(1.2, Percentage.withPercentage(1));
}

@Test // GH-2860
void projectShouldReadProjectionWithNestedEntity() {

Expand Down Expand Up @@ -3289,11 +3313,13 @@ interface WithNestedProjection {

interface WithNestedInterfaceProjection {
String getFirstname();

AddressProjection getAddress();
}

interface WithNestedDtoProjection {
String getFirstname();

AddressDto getAddress();
}

Expand Down Expand Up @@ -4342,4 +4368,30 @@ static class ComplexIdAndNoAnnotation {
ComplexId id;
String value;
}

@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;
}
}

}

0 comments on commit f62e5f3

Please sign in to comment.