diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index f1f5277d1..c6e843310 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -587,8 +587,14 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, Method method = invocation.getMethod(); - return QueryExecutionConverters // - .getExecutionAdapter(method.getReturnType()) // + QueryExecutionConverters.ExecutionAdapter executionAdapter = QueryExecutionConverters // + .getExecutionAdapter(method.getReturnType()); + + if (executionAdapter == null) { + return resultHandler.postProcessInvocationResult(doInvoke(invocation), method); + } + + return executionAdapter // .apply(() -> resultHandler.postProcessInvocationResult(doInvoke(invocation), method)); } @@ -596,10 +602,9 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, private Object doInvoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); - Object[] arguments = invocation.getArguments(); if (hasQueryFor(method)) { - return queries.get(method).execute(arguments); + return queries.get(method).execute(invocation.getArguments()); } return invocation.proceed(); diff --git a/src/main/java/org/springframework/data/repository/query/Parameter.java b/src/main/java/org/springframework/data/repository/query/Parameter.java index 5ca8419fc..72eee3dce 100644 --- a/src/main/java/org/springframework/data/repository/query/Parameter.java +++ b/src/main/java/org/springframework/data/repository/query/Parameter.java @@ -28,6 +28,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; @@ -48,6 +49,7 @@ public class Parameter { private final MethodParameter parameter; private final Class parameterType; private final boolean isDynamicProjectionParameter; + private final Lazy> name; /** * Creates a new {@link Parameter} for the given {@link MethodParameter}. @@ -61,6 +63,10 @@ public class Parameter { this.parameter = parameter; this.parameterType = potentiallyUnwrapParameterType(parameter); this.isDynamicProjectionParameter = isDynamicProjectionParameter(parameter); + this.name = Lazy.of(() -> { + Param annotation = parameter.getParameterAnnotation(Param.class); + return Optional.ofNullable(annotation == null ? parameter.getParameterName() : annotation.value()); + }); } /** @@ -129,9 +135,7 @@ public class Parameter { * @return */ public Optional getName() { - - Param annotation = parameter.getParameterAnnotation(Param.class); - return Optional.ofNullable(annotation == null ? parameter.getParameterName() : annotation.value()); + return this.name.get(); } /** diff --git a/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java b/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java index 8b0a55c07..3dddbd961 100644 --- a/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java +++ b/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java @@ -20,11 +20,13 @@ import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; /** * Interface to access method parameters. Allows dedicated access to parameters of special types * * @author Oliver Gierke + * @author Mark Paluch */ public interface ParameterAccessor extends Iterable { @@ -48,9 +50,20 @@ public interface ParameterAccessor extends Iterable { * * @return * @since 1.12 + * @deprecated use {@link #findDynamicProjection()} instead. */ + @Deprecated Optional> getDynamicProjection(); + /** + * Returns the dynamic projection type to be used when executing the query or {@literal null} if none is defined. + * + * @return + * @since 2.2 + */ + @Nullable + Class findDynamicProjection(); + /** * Returns the bindable value with the given index. Bindable means, that {@link Pageable} and {@link Sort} values are * skipped without noticed in the index. For a method signature taking {@link String}, {@link Pageable} , diff --git a/src/main/java/org/springframework/data/repository/query/Parameters.java b/src/main/java/org/springframework/data/repository/query/Parameters.java index 49b2a743b..6e58d88c5 100644 --- a/src/main/java/org/springframework/data/repository/query/Parameters.java +++ b/src/main/java/org/springframework/data/repository/query/Parameters.java @@ -28,6 +28,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.util.Lazy; import org.springframework.data.util.Streamable; import org.springframework.util.Assert; @@ -51,6 +52,7 @@ public abstract class Parameters, T extends Parameter private final int pageableIndex; private final int sortIndex; private final List parameters; + private final Lazy bindable; private int dynamicProjectionIndex; @@ -99,6 +101,7 @@ public abstract class Parameters, T extends Parameter this.pageableIndex = pageableIndex; this.sortIndex = sortIndex; + this.bindable = Lazy.of(this::getBindable); assertEitherAllParamAnnotatedOrNone(); } @@ -129,6 +132,21 @@ public abstract class Parameters, T extends Parameter this.pageableIndex = pageableIndexTemp; this.sortIndex = sortIndexTemp; this.dynamicProjectionIndex = dynamicProjectionTemp; + this.bindable = Lazy.of(() -> (S) this); + } + + private S getBindable() { + + List bindables = new ArrayList<>(); + + for (T candidate : this) { + + if (candidate.isBindable()) { + bindables.add(candidate); + } + } + + return createFrom(bindables); } /** @@ -262,17 +280,7 @@ public abstract class Parameters, T extends Parameter * @see Parameter#isSpecialParameter() */ public S getBindableParameters() { - - List bindables = new ArrayList<>(); - - for (T candidate : this) { - - if (candidate.isBindable()) { - bindables.add(candidate); - } - } - - return createFrom(bindables); + return this.bindable.get(); } protected abstract S createFrom(List parameters); diff --git a/src/main/java/org/springframework/data/repository/query/ParametersParameterAccessor.java b/src/main/java/org/springframework/data/repository/query/ParametersParameterAccessor.java index abb276bfe..7341a73e5 100644 --- a/src/main/java/org/springframework/data/repository/query/ParametersParameterAccessor.java +++ b/src/main/java/org/springframework/data/repository/query/ParametersParameterAccessor.java @@ -15,21 +15,22 @@ */ package org.springframework.data.repository.query; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.util.QueryExecutionConverters; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * {@link ParameterAccessor} implementation using a {@link Parameters} instance to find special parameters. * * @author Oliver Gierke + * @author Mark Paluch */ public class ParametersParameterAccessor implements ParameterAccessor { @@ -50,9 +51,11 @@ public class ParametersParameterAccessor implements ParameterAccessor { Assert.isTrue(parameters.getNumberOfParameters() == values.length, "Invalid number of parameters given!"); this.parameters = parameters; - this.values = Arrays.stream(values)// - .map(QueryExecutionConverters::unwrap)// - .collect(Collectors.toList()); + this.values = new ArrayList<>(values.length); + + for (Object value : values) { + this.values.add(QueryExecutionConverters.unwrap(value)); + } } /** @@ -64,6 +67,15 @@ public class ParametersParameterAccessor implements ParameterAccessor { return parameters; } + /** + * Returns the potentially unwrapped values. + * + * @return + */ + protected List getValues() { + return this.values; + } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() @@ -104,8 +116,23 @@ public class ParametersParameterAccessor implements ParameterAccessor { * @return */ public Optional> getDynamicProjection() { - return Optional.ofNullable( - parameters.hasDynamicProjection() ? (Class) values.get(parameters.getDynamicProjectionIndex()) : null); + + return Optional.ofNullable(parameters.hasDynamicProjection() // + ? (Class) values.get(parameters.getDynamicProjectionIndex()) // + : null); + } + + /** + * Returns the dynamic projection type if available, {@literal null} otherwise. + * + * @return + */ + @Nullable + public Class findDynamicProjection() { + + return parameters.hasDynamicProjection() // + ? (Class) values.get(parameters.getDynamicProjectionIndex()) + : null; } /** @@ -133,8 +160,13 @@ public class ParametersParameterAccessor implements ParameterAccessor { */ public boolean hasBindableNullValue() { - return parameters.getBindableParameters().stream()// - .anyMatch(it -> values.get(it.getIndex()) == null); + for (Parameter parameter : parameters.getBindableParameters()) { + if (values.get(parameter.getIndex()) == null) { + return true; + } + } + + return false; } /* diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/src/main/java/org/springframework/data/repository/query/QueryMethod.java index 7788d5e69..2194655c5 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryMethod.java +++ b/src/main/java/org/springframework/data/repository/query/QueryMethod.java @@ -52,6 +52,7 @@ public class QueryMethod { private final Parameters parameters; private final ResultProcessor resultProcessor; private final Lazy> domainClass; + private final Lazy isCollectionQuery; /** * Creates a new {@link QueryMethod} from the given parameters. Looks up the correct query to use for following @@ -111,6 +112,7 @@ public class QueryMethod { }); this.resultProcessor = new ResultProcessor(this, factory); + this.isCollectionQuery = Lazy.of(this::calculateIsCollectionQuery); } /** @@ -170,22 +172,7 @@ public class QueryMethod { * @return */ public boolean isCollectionQuery() { - - if (isPageQuery() || isSliceQuery()) { - return false; - } - - Class returnType = method.getReturnType(); - - if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) { - return true; - } - - if (QueryExecutionConverters.supports(unwrappedReturnType)) { - return !QueryExecutionConverters.isSingleValue(unwrappedReturnType); - } - - return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike(); + return isCollectionQuery.get(); } /** @@ -262,6 +249,25 @@ public class QueryMethod { return method.toString(); } + private boolean calculateIsCollectionQuery() { + + if (isPageQuery() || isSliceQuery()) { + return false; + } + + Class returnType = method.getReturnType(); + + if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) { + return true; + } + + if (QueryExecutionConverters.supports(unwrappedReturnType)) { + return !QueryExecutionConverters.isSingleValue(unwrappedReturnType); + } + + return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike(); + } + private static Class potentiallyUnwrapReturnTypeFor(Method method) { if (QueryExecutionConverters.supports(method.getReturnType())) { diff --git a/src/main/java/org/springframework/data/repository/query/ResultProcessor.java b/src/main/java/org/springframework/data/repository/query/ResultProcessor.java index 0e11c556a..1cf90bf01 100644 --- a/src/main/java/org/springframework/data/repository/query/ResultProcessor.java +++ b/src/main/java/org/springframework/data/repository/query/ResultProcessor.java @@ -96,7 +96,11 @@ public class ResultProcessor { Assert.notNull(accessor, "Parameter accessor must not be null!"); - return accessor.getDynamicProjection().map(this::withType).orElse(this); + Class projection = accessor.findDynamicProjection(); + + return projection == null // + ? this // + : withType(projection); } /** diff --git a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java index 2fc49b991..793fd05f9 100644 --- a/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java +++ b/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java @@ -57,6 +57,7 @@ import org.springframework.lang.Nullable; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.concurrent.ListenableFuture; import com.google.common.base.Optional; @@ -102,6 +103,7 @@ public abstract class QueryExecutionConverters { private static final Set> UNWRAPPERS = new HashSet>(); private static final Set> ALLOWED_PAGEABLE_TYPES = new HashSet>(); private static final Map, ExecutionAdapter> EXECUTION_ADAPTER = new HashMap<>(); + private static final Map, Boolean> SUPPORTS_CACHE = new ConcurrentReferenceHashMap<>(); static { @@ -173,13 +175,16 @@ public abstract class QueryExecutionConverters { Assert.notNull(type, "Type must not be null!"); - for (WrapperType candidate : WRAPPER_TYPES) { - if (candidate.getType().isAssignableFrom(type)) { - return true; - } - } + return SUPPORTS_CACHE.computeIfAbsent(type, key -> { - return false; + for (WrapperType candidate : WRAPPER_TYPES) { + if (candidate.getType().isAssignableFrom(type)) { + return true; + } + } + + return false; + }); } /** @@ -308,11 +313,12 @@ public abstract class QueryExecutionConverters { * @param returnType must not be {@literal null}. * @return */ + @Nullable public static ExecutionAdapter getExecutionAdapter(Class returnType) { Assert.notNull(returnType, "Return type must not be null!"); - return EXECUTION_ADAPTER.getOrDefault(returnType, ThrowingSupplier::get); + return EXECUTION_ADAPTER.get(returnType); } public interface ThrowingSupplier { diff --git a/src/main/java/org/springframework/data/util/Lazy.java b/src/main/java/org/springframework/data/util/Lazy.java index 8913612a2..80874f524 100644 --- a/src/main/java/org/springframework/data/util/Lazy.java +++ b/src/main/java/org/springframework/data/util/Lazy.java @@ -74,7 +74,7 @@ public class Lazy implements Supplier { /** * Creates a pre-resolved empty {@link Lazy}. - * + * * @return * @since 2.1 */ @@ -103,7 +103,7 @@ public class Lazy implements Supplier { /** * Returns the {@link Optional} value created by the configured {@link Supplier}, allowing the absence of values in * contrast to {@link #get()}. Will return the calculated instance for subsequent lookups. - * + * * @return */ public Optional getOptional() { @@ -112,7 +112,7 @@ public class Lazy implements Supplier { /** * Returns a new Lazy that will consume the given supplier in case the current one does not yield in a result. - * + * * @param supplier must not be {@literal null}. * @return */ @@ -125,7 +125,7 @@ public class Lazy implements Supplier { /** * Returns a new Lazy that will return the given value in case the current one does not yield in a result. - * + * * @param supplier must not be {@literal null}. * @return */ @@ -198,9 +198,10 @@ public class Lazy implements Supplier { * Returns the value of the lazy evaluation. * * @return + * @since 2.2 */ @Nullable - private T getNullable() { + public T getNullable() { T value = this.value; diff --git a/src/test/java/org/springframework/data/repository/query/ResultProcessorUnitTests.java b/src/test/java/org/springframework/data/repository/query/ResultProcessorUnitTests.java index fcbe5fdce..fba16dfa5 100755 --- a/src/test/java/org/springframework/data/repository/query/ResultProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/ResultProcessorUnitTests.java @@ -146,7 +146,7 @@ public class ResultProcessorUnitTests { ResultProcessor factory = getProcessor("findOneDynamic", Class.class); assertThat(factory.withDynamicProjection(accessor)).isEqualTo(factory); - doReturn(Optional.of(SampleProjection.class)).when(accessor).getDynamicProjection(); + doReturn(SampleProjection.class).when(accessor).findDynamicProjection(); ResultProcessor processor = factory.withDynamicProjection(accessor); assertThat(processor.getReturnedType().getReturnedType()).isEqualTo(SampleProjection.class); diff --git a/src/test/java/org/springframework/data/util/LazyUnitTests.java b/src/test/java/org/springframework/data/util/LazyUnitTests.java index 749b9353b..3567bb98e 100755 --- a/src/test/java/org/springframework/data/util/LazyUnitTests.java +++ b/src/test/java/org/springframework/data/util/LazyUnitTests.java @@ -28,8 +28,9 @@ import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link Lazy}. - * + * * @author Oliver Gierke + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class LazyUnitTests { @@ -86,6 +87,11 @@ public class LazyUnitTests { assertThat(Lazy.of(() -> null).getOptional()).isEmpty(); } + @Test // DATACMNS-1556 + public void allowsNullableValue() { + assertThat(Lazy.of(() -> null).getNullable()).isNull(); + } + @Test public void ignoresElseIfValuePresent() {