DATACMNS-1556 - General performance improvements in repository execution.
We now avoid the allocation of an Optional instance in the lookup of a dynamic projection in ParameterAccessor. Also, Lazy now exposes a ….getNullable() to be favored over ….getOptional() for hot code paths. Replaced Stream and Optional usage with for-loops and nullable return values. Reuse parameter names to avoid repeated annotation lookups. Reuse result from Parameters.getBindable(). Introduce ParametersParameterAccessor.getValues() to consistently reuse a single accessor instance allowing access to the unwrapped values. Introduce type cache to QueryExecutionConverters to quickly reject types that do not require wrapping. Avoid recalculation of QueryMethod.isCollectionQuery().
This commit is contained in:
committed by
Oliver Drotbohm
parent
e0f2d65d73
commit
293bbd9a63
@@ -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();
|
||||
|
||||
@@ -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<Optional<String>> 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<String> getName() {
|
||||
|
||||
Param annotation = parameter.getParameterAnnotation(Param.class);
|
||||
return Optional.ofNullable(annotation == null ? parameter.getParameterName() : annotation.value());
|
||||
return this.name.get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
@@ -48,9 +50,20 @@ public interface ParameterAccessor extends Iterable<Object> {
|
||||
*
|
||||
* @return
|
||||
* @since 1.12
|
||||
* @deprecated use {@link #findDynamicProjection()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
Optional<Class<?>> 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} ,
|
||||
|
||||
@@ -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<S extends Parameters<S, T>, T extends Parameter
|
||||
private final int pageableIndex;
|
||||
private final int sortIndex;
|
||||
private final List<T> parameters;
|
||||
private final Lazy<S> bindable;
|
||||
|
||||
private int dynamicProjectionIndex;
|
||||
|
||||
@@ -99,6 +101,7 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
|
||||
this.pageableIndex = pageableIndex;
|
||||
this.sortIndex = sortIndex;
|
||||
this.bindable = Lazy.of(this::getBindable);
|
||||
|
||||
assertEitherAllParamAnnotatedOrNone();
|
||||
}
|
||||
@@ -129,6 +132,21 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
this.pageableIndex = pageableIndexTemp;
|
||||
this.sortIndex = sortIndexTemp;
|
||||
this.dynamicProjectionIndex = dynamicProjectionTemp;
|
||||
this.bindable = Lazy.of(() -> (S) this);
|
||||
}
|
||||
|
||||
private S getBindable() {
|
||||
|
||||
List<T> bindables = new ArrayList<>();
|
||||
|
||||
for (T candidate : this) {
|
||||
|
||||
if (candidate.isBindable()) {
|
||||
bindables.add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
return createFrom(bindables);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,17 +280,7 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
|
||||
* @see Parameter#isSpecialParameter()
|
||||
*/
|
||||
public S getBindableParameters() {
|
||||
|
||||
List<T> 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<T> parameters);
|
||||
|
||||
@@ -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<Object> 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<Class<?>> 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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -52,6 +52,7 @@ public class QueryMethod {
|
||||
private final Parameters<?, ?> parameters;
|
||||
private final ResultProcessor resultProcessor;
|
||||
private final Lazy<Class<?>> domainClass;
|
||||
private final Lazy<Boolean> 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<? extends Object> potentiallyUnwrapReturnTypeFor(Method method) {
|
||||
|
||||
if (QueryExecutionConverters.supports(method.getReturnType())) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Converter<Object, Object>> UNWRAPPERS = new HashSet<Converter<Object, Object>>();
|
||||
private static final Set<Class<?>> ALLOWED_PAGEABLE_TYPES = new HashSet<Class<?>>();
|
||||
private static final Map<Class<?>, ExecutionAdapter> EXECUTION_ADAPTER = new HashMap<>();
|
||||
private static final Map<Class<?>, 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 {
|
||||
|
||||
@@ -74,7 +74,7 @@ public class Lazy<T> implements Supplier<T> {
|
||||
|
||||
/**
|
||||
* Creates a pre-resolved empty {@link Lazy}.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
@@ -103,7 +103,7 @@ public class Lazy<T> implements Supplier<T> {
|
||||
/**
|
||||
* 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<T> getOptional() {
|
||||
@@ -112,7 +112,7 @@ public class Lazy<T> implements Supplier<T> {
|
||||
|
||||
/**
|
||||
* 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<T> implements Supplier<T> {
|
||||
|
||||
/**
|
||||
* 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<T> implements Supplier<T> {
|
||||
* Returns the value of the lazy evaluation.
|
||||
*
|
||||
* @return
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
private T getNullable() {
|
||||
public T getNullable() {
|
||||
|
||||
T value = this.value;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user