diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java index 97e91df45..567fb6f0c 100644 --- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java +++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java @@ -18,6 +18,7 @@ package org.springframework.data.repository.query; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.Value; import java.beans.PropertyDescriptor; import java.util.ArrayList; @@ -25,6 +26,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.springframework.data.mapping.PreferredConstructor; @@ -33,6 +35,7 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionInformation; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; /** * A representation of the type returned by a {@link QueryMethod}. @@ -44,6 +47,8 @@ import org.springframework.util.ClassUtils; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public abstract class ReturnedType { + private static final Map CACHE = new ConcurrentReferenceHashMap<>(32); + private final @NonNull Class domainType; /** @@ -60,9 +65,12 @@ public abstract class ReturnedType { Assert.notNull(domainType, "Domain type must not be null!"); Assert.notNull(factory, "ProjectionFactory must not be null!"); - return returnedType.isInterface() - ? new ReturnedInterface(factory.getProjectionInformation(returnedType), domainType) - : new ReturnedClass(returnedType, domainType); + return CACHE.computeIfAbsent(CacheKey.of(returnedType, domainType, factory.hashCode()), key -> { + + return returnedType.isInterface() + ? new ReturnedInterface(factory.getProjectionInformation(returnedType), domainType) + : new ReturnedClass(returnedType, domainType); + }); } /** @@ -316,4 +324,10 @@ public abstract class ReturnedType { return ClassUtils.isPrimitiveOrWrapper(type); } } + + @Value(staticConstructor = "of") + private static class CacheKey { + Class returnedType, domainType; + int projectionFactoryHashCode; + } } diff --git a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java index 75b6c5a07..43fe37498 100755 --- a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java @@ -157,6 +157,17 @@ public class ReturnedTypeUnitTests { assertThat(properties).containsExactly("firstname"); } + @Test // DATACMNS-1112 + public void cachesInstancesBySourceTypes() { + + SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + + ReturnedType left = ReturnedType.of(Child.class, Object.class, factory); + ReturnedType right = ReturnedType.of(Child.class, Object.class, factory); + + assertThat(left).isSameAs(right); + } + private static ReturnedType getReturnedType(String methodName, Class... parameters) throws Exception { return getQueryMethod(methodName, parameters).getResultProcessor().getReturnedType(); }