From dfcb9f77a4fc3a695cc13a59fb227d9e5b4135a0 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 19 Jul 2017 13:45:41 +0200 Subject: [PATCH] DATACMNS-1116 - AbstractMappingContext now caches PersistentPropertyPaths. As the calculation of a PersistentPropertyPath instances is rather expensive and they're heavily used in downstream mapping operations (e.g. query and fields mapping in MongoDB) we now cache the instances created per base type and property path. --- .../context/AbstractMappingContext.java | 20 ++++++++++++++++--- .../AbstractMappingContextUnitTests.java | 11 ++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index 7aa2b25fb..d8a90203c 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -18,6 +18,7 @@ package org.springframework.data.mapping.context; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.Value; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; @@ -28,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -55,6 +57,7 @@ import org.springframework.data.util.Pair; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; import org.springframework.util.ReflectionUtils.FieldFilter; @@ -83,6 +86,7 @@ public abstract class AbstractMappingContext NONE = Optional.empty(); private final Map, Optional> persistentEntities = new HashMap<>(); + private final Map> propertyPaths = new ConcurrentReferenceHashMap<>(); private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory(); private ApplicationEventPublisher applicationEventPublisher; @@ -268,18 +272,21 @@ public abstract class AbstractMappingContext getPersistentPropertyPath(String propertyPath, TypeInformation type) { - return getPersistentPropertyPath(Arrays.asList(propertyPath.split("\\.")), type); + + return propertyPaths.computeIfAbsent(TypeAndProperties.of(type, propertyPath), + it -> createPersistentPropertyPath(it.getPath(), it.getType())); } /** * Creates a {@link PersistentPropertyPath} for the given parts and {@link TypeInformation}. * - * @param parts must not be {@literal null} or empty. + * @param propertyPath must not be {@literal null}. * @param type must not be {@literal null}. * @return */ - private PersistentPropertyPath

getPersistentPropertyPath(Collection parts, TypeInformation type) { + private PersistentPropertyPath

createPersistentPropertyPath(String propertyPath, TypeInformation type) { + List parts = Arrays.asList(propertyPath.split("\\.")); DefaultPersistentPropertyPath

path = DefaultPersistentPropertyPath.empty(); Iterator iterator = parts.iterator(); E current = getRequiredPersistentEntity(type); @@ -545,6 +552,13 @@ public abstract class AbstractMappingContext type; + String path; + } + /** * Filter rejecting static fields as well as artificially introduced ones. See * {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details. diff --git a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java index 3bfc101b7..b89c762ac 100755 --- a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java @@ -31,10 +31,10 @@ import org.mockito.Mockito; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.data.annotation.Id; +import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.model.BasicPersistentEntity; -import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -155,7 +155,7 @@ public class AbstractMappingContextUnitTests { assertThat(entity.getPersistentProperty("persons")) .satisfies(it -> assertThat(mappingContext.getPersistentEntity(it)) - .satisfies(inner -> assertThat(((PersistentEntity)inner).getType()).isEqualTo(Person.class))); + .satisfies(inner -> assertThat(((PersistentEntity) inner).getType()).isEqualTo(Person.class))); } @Test // DATACMNS-380 @@ -226,6 +226,13 @@ public class AbstractMappingContextUnitTests { .matches(e -> context.getPersistentPropertyPath(e) != null); } + @Test // DATACMNS-1116 + public void cachesPersistentPropertyPaths() { + + assertThat(context.getPersistentPropertyPath("persons.name", Sample.class)) // + .isSameAs(context.getPersistentPropertyPath("persons.name", Sample.class)); + } + private static void assertHasEntityFor(Class type, SampleMappingContext context, boolean expected) { boolean found = false;