Browse Source

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 #4626
4.1.x
Mark Paluch 2 years ago
parent
commit
410d38d548
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 30
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  2. 75
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

30
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

@ -363,13 +363,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -363,13 +363,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
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();
}
@ -506,16 +499,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -506,16 +499,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
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);
@ -566,7 +559,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -566,7 +559,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
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,
@ -622,9 +617,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -622,9 +617,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
@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);
@ -647,7 +641,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -647,7 +641,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
} else {
return dbRefResolver.resolveReference(property,
new DocumentReferenceSource(documentAccessor.getDocument(), documentAccessor.get(property)),
new DocumentReferenceSource(documentAccessor.getDocument(), documentAccessor.get(property)),
referenceLookupDelegate, context.forProperty(property)::convert);
}
}
@ -2426,8 +2420,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -2426,8 +2420,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.returnedTypeDescriptor = projection;
}
@Override
public ConversionContext forProperty(String name) {

75
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

@ -28,11 +28,14 @@ import lombok.RequiredArgsConstructor; @@ -28,11 +28,14 @@ import lombok.RequiredArgsConstructor;
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;
import java.util.*;
import org.assertj.core.data.Percentage;
import org.bson.BsonUndefined;
import org.bson.types.Binary;
import org.bson.types.Code;
import org.bson.types.Decimal128;
@ -125,7 +128,8 @@ class MappingMongoConverterUnitTests { @@ -125,7 +128,8 @@ class MappingMongoConverterUnitTests {
@BeforeEach
void beforeEach() {
MongoCustomConversions conversions = new MongoCustomConversions();
MongoCustomConversions conversions = new MongoCustomConversions(
Arrays.asList(new ByteBufferToDoubleHolderConverter()));
mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(context);
@ -1437,7 +1441,7 @@ class MappingMongoConverterUnitTests { @@ -1437,7 +1441,7 @@ class MappingMongoConverterUnitTests {
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
@ -1470,7 +1474,7 @@ class MappingMongoConverterUnitTests { @@ -1470,7 +1474,7 @@ class MappingMongoConverterUnitTests {
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
@ -1488,7 +1492,7 @@ class MappingMongoConverterUnitTests { @@ -1488,7 +1492,7 @@ class MappingMongoConverterUnitTests {
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
@ -1521,7 +1525,7 @@ class MappingMongoConverterUnitTests { @@ -1521,7 +1525,7 @@ class MappingMongoConverterUnitTests {
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
@ -2531,8 +2535,8 @@ class MappingMongoConverterUnitTests { @@ -2531,8 +2535,8 @@ class MappingMongoConverterUnitTests {
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);
@ -2720,8 +2724,8 @@ class MappingMongoConverterUnitTests { @@ -2720,8 +2724,8 @@ class MappingMongoConverterUnitTests {
.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");
@ -2739,14 +2743,35 @@ class MappingMongoConverterUnitTests { @@ -2739,14 +2743,35 @@ class MappingMongoConverterUnitTests {
.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() {
@ -3022,11 +3047,13 @@ class MappingMongoConverterUnitTests { @@ -3022,11 +3047,13 @@ class MappingMongoConverterUnitTests {
interface WithNestedInterfaceProjection {
String getFirstname();
AddressProjection getAddress();
}
interface WithNestedDtoProjection {
String getFirstname();
AddressDto getAddress();
}
@ -3925,4 +3952,30 @@ class MappingMongoConverterUnitTests { @@ -3925,4 +3952,30 @@ class MappingMongoConverterUnitTests {
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;
}
}
}

Loading…
Cancel
Save