Browse Source

DATAMONGO-2046 - Performance improvements in mapping and conversion subsystem.

In MappingMongoConverter, we now avoid the creation of a ParameterValueProvider for parameter-less constructors. We also skip property population if entity can be constructed entirely through constructor creation. Replaced the lambda in MappingMongoConverter.readAndPopulateIdentifier(…) with direct call to ….readIdValue(…). Objectpath now uses decomposed ObjectPathItems to avoid array copying and creation. It now stores a reference to its parent and ObjectPathItem fields are now merged into ObjectPath, which reduces the number of created objects during reads.

Extended CachingMongoPersistentProperty with DBRef caching. Turned key access in DocumentAccessor into an optimistic lookup. DbRefResolverCallbacks are now created lazily.

Related tickets: DATACMNS-1366.
Original pull request: #602.
pull/601/merge
Mark Paluch 7 years ago committed by Oliver Gierke
parent
commit
799fa6c87e
  1. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java
  2. 76
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  3. 89
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java
  4. 30
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/CachingMongoPersistentProperty.java

15
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java

@ -159,15 +159,20 @@ class DocumentAccessor { @@ -159,15 +159,20 @@ class DocumentAccessor {
String fieldName = property.getFieldName();
if (!fieldName.contains(".")) {
if (this.document instanceof Document) {
return ((Document) this.document).containsKey(fieldName);
}
if (this.document instanceof DBObject) {
return ((DBObject) this.document).containsField(fieldName);
if (((Document) this.document).containsKey(fieldName)) {
return true;
}
} else if (this.document instanceof DBObject) {
if (((DBObject) this.document).containsField(fieldName)) {
return true;
}
}
if (!fieldName.contains(".")) {
return false;
}
String[] parts = fieldName.split("\\.");

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

@ -17,7 +17,6 @@ package org.springframework.data.mongodb.core.convert; @@ -17,7 +17,6 @@ package org.springframework.data.mongodb.core.convert;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import org.bson.Document;
import org.bson.conversions.Bson;
@ -34,6 +33,7 @@ import org.springframework.data.convert.TypeMapper; @@ -34,6 +33,7 @@ import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@ -242,11 +242,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -242,11 +242,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException(String.format(INVALID_TYPE_TO_READ, target, typeToUse.getType()));
}
return read((MongoPersistentEntity<S>) mappingContext.getRequiredPersistentEntity(typeToUse), target, path);
return read((MongoPersistentEntity<S>) entity, target, path);
}
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
DocumentAccessor source, SpELExpressionEvaluator evaluator, ObjectPath path) {
AssociationAwareMongoDbPropertyValueProvider provider = new AssociationAwareMongoDbPropertyValueProvider(source,
evaluator, path);
@ -260,28 +261,40 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -260,28 +261,40 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
PreferredConstructor<S, MongoPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
ParameterValueProvider<MongoPersistentProperty> provider = persistenceConstructor != null
&& persistenceConstructor.hasParameters() ? getParameterProvider(entity, documentAccessor, evaluator, path)
: NoOpParameterValueProvider.INSTANCE;
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, bson, evaluator, path);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, provider);
if (entity.requiresPropertyPopulation()) {
return populateProperties(entity, documentAccessor, path, evaluator, instance);
}
return instance;
}
private <S> S populateProperties(MongoPersistentEntity<S> entity, DocumentAccessor documentAccessor, ObjectPath path,
SpELExpressionEvaluator evaluator, S instance) {
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
// Make sure id property is set before all other properties
Object rawId = readAndPopulateIdentifier(accessor, documentAccessor, entity,
(property, id) -> readIdValue(path, evaluator, property, id));
path, evaluator);
ObjectPath currentPath = path.push(accessor.getBean(), entity, rawId);
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
currentPath);
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
MappingMongoConverter.this);
readProperties(entity, accessor, documentAccessor, valueProvider, callback);
readProperties(entity, accessor, documentAccessor, valueProvider, currentPath, evaluator);
return accessor.getBean();
}
@ -293,12 +306,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -293,12 +306,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param accessor must not be {@literal null}.
* @param document must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param callback the callback to actually resolve the value for the identifier property, must not be
* {@literal null}.
* @param path
* @param evaluator
* @return
*/
private Object readAndPopulateIdentifier(PersistentPropertyAccessor<?> accessor, DocumentAccessor document,
MongoPersistentEntity<?> entity, BiFunction<MongoPersistentProperty, Object, Object> callback) {
MongoPersistentEntity<?> entity, ObjectPath path, SpELExpressionEvaluator evaluator) {
Object rawId = document.getRawId(entity);
@ -312,7 +325,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -312,7 +325,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return rawId;
}
accessor.setProperty(idProperty, callback.apply(idProperty, rawId));
accessor.setProperty(idProperty, readIdValue(path, evaluator, idProperty, rawId));
return rawId;
}
@ -327,11 +340,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -327,11 +340,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
DocumentAccessor documentAccessor, MongoDbPropertyValueProvider valueProvider, DbRefResolverCallback callback) {
DocumentAccessor documentAccessor, MongoDbPropertyValueProvider valueProvider, ObjectPath currentPath,
SpELExpressionEvaluator evaluator) {
DbRefResolverCallback callback = null;
for (MongoPersistentProperty prop : entity) {
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
if (callback == null) {
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
}
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
continue;
}
@ -347,6 +368,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -347,6 +368,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (prop.isAssociation()) {
if (callback == null) {
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
}
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
continue;
}
@ -355,6 +381,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -355,6 +381,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
}
private DbRefResolverCallback getDbRefResolverCallback(DocumentAccessor documentAccessor, ObjectPath currentPath,
SpELExpressionEvaluator evaluator) {
return new DefaultDbRefResolverCallback(documentAccessor.getDocument(), currentPath, evaluator,
MappingMongoConverter.this);
}
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor<?> accessor,
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback) {
@ -1381,7 +1414,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -1381,7 +1414,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param evaluator must not be {@literal null}.
* @param path must not be {@literal null}.
*/
AssociationAwareMongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
AssociationAwareMongoDbPropertyValueProvider(DocumentAccessor source, SpELExpressionEvaluator evaluator,
ObjectPath path) {
super(source, evaluator, path);
}
@ -1606,4 +1640,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -1606,4 +1640,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
static class NestedDocument {
}
enum NoOpParameterValueProvider implements ParameterValueProvider<MongoPersistentProperty> {
INSTANCE;
@Override
public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter) {
return null;
}
}
}

89
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java

@ -15,8 +15,6 @@ @@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.core.convert;
import lombok.Value;
import java.util.ArrayList;
import java.util.List;
@ -45,26 +43,33 @@ class ObjectPath { @@ -45,26 +43,33 @@ class ObjectPath {
static final ObjectPath ROOT = new ObjectPath();
private final ObjectPathItem[] items;
private final @Nullable ObjectPath parent;
private final @Nullable Object object;
private final @Nullable Object idValue;
private final String collection;
private ObjectPath() {
this.items = new ObjectPathItem[0];
this.parent = null;
this.object = null;
this.idValue = null;
this.collection = "";
}
/**
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
* {@link ObjectPathItem} to it.
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} and adding the provided path values.
*
* @param parent must not be {@literal null}.
* @param item
* @param collection
* @param idValue
* @param collection
*/
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
ObjectPathItem[] items = new ObjectPathItem[parent.items.length + 1];
System.arraycopy(parent.items, 0, items, 0, parent.items.length);
items[parent.items.length] = item;
private ObjectPath(ObjectPath parent, Object object, @Nullable Object idValue, String collection) {
this.items = items;
this.parent = parent;
this.object = object;
this.idValue = idValue;
this.collection = collection;
}
/**
@ -80,8 +85,7 @@ class ObjectPath { @@ -80,8 +85,7 @@ class ObjectPath {
Assert.notNull(object, "Object must not be null!");
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection());
return new ObjectPath(this, item);
return new ObjectPath(this, object, id, entity.getCollection());
}
/**
@ -100,15 +104,15 @@ class ObjectPath { @@ -100,15 +104,15 @@ class ObjectPath {
Assert.notNull(id, "Id must not be null!");
Assert.hasText(collection, "Collection name must not be null!");
for (ObjectPathItem item : items) {
for (ObjectPath current = this; current != null; current = current.parent) {
Object object = item.getObject();
Object object = current.getObject();
if (object == null || item.getIdValue() == null) {
if (object == null || current.getIdValue() == null) {
continue;
}
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) {
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())) {
return object;
}
}
@ -133,15 +137,15 @@ class ObjectPath { @@ -133,15 +137,15 @@ class ObjectPath {
Assert.hasText(collection, "Collection name must not be null!");
Assert.notNull(type, "Type must not be null!");
for (ObjectPathItem item : items) {
for (ObjectPath current = this; current != null; current = current.parent) {
Object object = item.getObject();
Object object = current.getObject();
if (object == null || item.getIdValue() == null) {
if (object == null || current.getIdValue() == null) {
continue;
}
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())
&& ClassUtils.isAssignable(type, object.getClass())) {
return type.cast(object);
}
@ -157,7 +161,21 @@ class ObjectPath { @@ -157,7 +161,21 @@ class ObjectPath {
*/
@Nullable
Object getCurrentObject() {
return items.length == 0 ? null : items[items.length - 1].getObject();
return getObject();
}
@Nullable
private Object getObject() {
return object;
}
@Nullable
private Object getIdValue() {
return idValue;
}
private String getCollection() {
return collection;
}
/*
@ -167,31 +185,16 @@ class ObjectPath { @@ -167,31 +185,16 @@ class ObjectPath {
@Override
public String toString() {
if (items.length == 0) {
if (parent == null) {
return "[empty]";
}
List<String> strings = new ArrayList<>(items.length);
List<String> strings = new ArrayList<>();
for (ObjectPathItem item : items) {
strings.add(ObjectUtils.nullSafeToString(item.object));
for (ObjectPath current = this; current != null; current = current.parent) {
strings.add(ObjectUtils.nullSafeToString(current.getObject()));
}
return StringUtils.collectionToDelimitedString(strings, " -> ");
}
/**
* An item in an {@link ObjectPath}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
private static class ObjectPathItem {
Object object;
@Nullable Object idValue;
String collection;
}
}

30
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/CachingMongoPersistentProperty.java

@ -24,11 +24,14 @@ import org.springframework.lang.Nullable; @@ -24,11 +24,14 @@ import org.springframework.lang.Nullable;
* {@link MongoPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getFieldName()}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty {
private @Nullable Boolean isIdProperty;
private @Nullable Boolean isAssociation;
private @Nullable boolean dbRefResolved;
private @Nullable DBRef dbref;
private @Nullable String fieldName;
private @Nullable Boolean usePropertyAccess;
private @Nullable Boolean isTransient;
@ -36,8 +39,7 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty @@ -36,8 +39,7 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
/**
* Creates a new {@link CachingMongoPersistentProperty}.
*
* @param field
* @param propertyDescriptor
* @param property
* @param owner
* @param simpleTypeHolder
* @param fieldNamingStrategy
@ -114,4 +116,28 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty @@ -114,4 +116,28 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
return this.isTransient;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#isDbReference()
*/
@Override
public boolean isDbReference() {
return getDBRef() != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getDBRef()
*/
@Override
public DBRef getDBRef() {
if (!dbRefResolved) {
this.dbref = super.getDBRef();
this.dbRefResolved = true;
}
return this.dbref;
}
}

Loading…
Cancel
Save