Skip to content

Commit 293bbd9

Browse files
mp911deodrotbohm
authored andcommitted
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().
1 parent e0f2d65 commit 293bbd9

File tree

11 files changed

+142
-57
lines changed

11 files changed

+142
-57
lines changed

src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -587,19 +587,24 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro
587587

588588
Method method = invocation.getMethod();
589589

590-
return QueryExecutionConverters //
591-
.getExecutionAdapter(method.getReturnType()) //
590+
QueryExecutionConverters.ExecutionAdapter executionAdapter = QueryExecutionConverters //
591+
.getExecutionAdapter(method.getReturnType());
592+
593+
if (executionAdapter == null) {
594+
return resultHandler.postProcessInvocationResult(doInvoke(invocation), method);
595+
}
596+
597+
return executionAdapter //
592598
.apply(() -> resultHandler.postProcessInvocationResult(doInvoke(invocation), method));
593599
}
594600

595601
@Nullable
596602
private Object doInvoke(MethodInvocation invocation) throws Throwable {
597603

598604
Method method = invocation.getMethod();
599-
Object[] arguments = invocation.getArguments();
600605

601606
if (hasQueryFor(method)) {
602-
return queries.get(method).execute(arguments);
607+
return queries.get(method).execute(invocation.getArguments());
603608
}
604609

605610
return invocation.proceed();

src/main/java/org/springframework/data/repository/query/Parameter.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.domain.Sort;
2929
import org.springframework.data.repository.util.QueryExecutionConverters;
3030
import org.springframework.data.util.ClassTypeInformation;
31+
import org.springframework.data.util.Lazy;
3132
import org.springframework.data.util.TypeInformation;
3233
import org.springframework.util.Assert;
3334

@@ -48,6 +49,7 @@ public class Parameter {
4849
private final MethodParameter parameter;
4950
private final Class<?> parameterType;
5051
private final boolean isDynamicProjectionParameter;
52+
private final Lazy<Optional<String>> name;
5153

5254
/**
5355
* Creates a new {@link Parameter} for the given {@link MethodParameter}.
@@ -61,6 +63,10 @@ protected Parameter(MethodParameter parameter) {
6163
this.parameter = parameter;
6264
this.parameterType = potentiallyUnwrapParameterType(parameter);
6365
this.isDynamicProjectionParameter = isDynamicProjectionParameter(parameter);
66+
this.name = Lazy.of(() -> {
67+
Param annotation = parameter.getParameterAnnotation(Param.class);
68+
return Optional.ofNullable(annotation == null ? parameter.getParameterName() : annotation.value());
69+
});
6470
}
6571

6672
/**
@@ -129,9 +135,7 @@ public boolean isNamedParameter() {
129135
* @return
130136
*/
131137
public Optional<String> getName() {
132-
133-
Param annotation = parameter.getParameterAnnotation(Param.class);
134-
return Optional.ofNullable(annotation == null ? parameter.getParameterName() : annotation.value());
138+
return this.name.get();
135139
}
136140

137141
/**

src/main/java/org/springframework/data/repository/query/ParameterAccessor.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020

2121
import org.springframework.data.domain.Pageable;
2222
import org.springframework.data.domain.Sort;
23+
import org.springframework.lang.Nullable;
2324

2425
/**
2526
* Interface to access method parameters. Allows dedicated access to parameters of special types
2627
*
2728
* @author Oliver Gierke
29+
* @author Mark Paluch
2830
*/
2931
public interface ParameterAccessor extends Iterable<Object> {
3032

@@ -48,9 +50,20 @@ public interface ParameterAccessor extends Iterable<Object> {
4850
*
4951
* @return
5052
* @since 1.12
53+
* @deprecated use {@link #findDynamicProjection()} instead.
5154
*/
55+
@Deprecated
5256
Optional<Class<?>> getDynamicProjection();
5357

58+
/**
59+
* Returns the dynamic projection type to be used when executing the query or {@literal null} if none is defined.
60+
*
61+
* @return
62+
* @since 2.2
63+
*/
64+
@Nullable
65+
Class<?> findDynamicProjection();
66+
5467
/**
5568
* Returns the bindable value with the given index. Bindable means, that {@link Pageable} and {@link Sort} values are
5669
* skipped without noticed in the index. For a method signature taking {@link String}, {@link Pageable} ,

src/main/java/org/springframework/data/repository/query/Parameters.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.core.ParameterNameDiscoverer;
2929
import org.springframework.data.domain.Pageable;
3030
import org.springframework.data.domain.Sort;
31+
import org.springframework.data.util.Lazy;
3132
import org.springframework.data.util.Streamable;
3233
import org.springframework.util.Assert;
3334

@@ -51,6 +52,7 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
5152
private final int pageableIndex;
5253
private final int sortIndex;
5354
private final List<T> parameters;
55+
private final Lazy<S> bindable;
5456

5557
private int dynamicProjectionIndex;
5658

@@ -99,6 +101,7 @@ public Parameters(Method method) {
99101

100102
this.pageableIndex = pageableIndex;
101103
this.sortIndex = sortIndex;
104+
this.bindable = Lazy.of(this::getBindable);
102105

103106
assertEitherAllParamAnnotatedOrNone();
104107
}
@@ -129,6 +132,21 @@ protected Parameters(List<T> originals) {
129132
this.pageableIndex = pageableIndexTemp;
130133
this.sortIndex = sortIndexTemp;
131134
this.dynamicProjectionIndex = dynamicProjectionTemp;
135+
this.bindable = Lazy.of(() -> (S) this);
136+
}
137+
138+
private S getBindable() {
139+
140+
List<T> bindables = new ArrayList<>();
141+
142+
for (T candidate : this) {
143+
144+
if (candidate.isBindable()) {
145+
bindables.add(candidate);
146+
}
147+
}
148+
149+
return createFrom(bindables);
132150
}
133151

134152
/**
@@ -262,17 +280,7 @@ public int getNumberOfParameters() {
262280
* @see Parameter#isSpecialParameter()
263281
*/
264282
public S getBindableParameters() {
265-
266-
List<T> bindables = new ArrayList<>();
267-
268-
for (T candidate : this) {
269-
270-
if (candidate.isBindable()) {
271-
bindables.add(candidate);
272-
}
273-
}
274-
275-
return createFrom(bindables);
283+
return this.bindable.get();
276284
}
277285

278286
protected abstract S createFrom(List<T> parameters);

src/main/java/org/springframework/data/repository/query/ParametersParameterAccessor.java

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,22 @@
1515
*/
1616
package org.springframework.data.repository.query;
1717

18-
import java.util.Arrays;
18+
import java.util.ArrayList;
1919
import java.util.Iterator;
2020
import java.util.List;
2121
import java.util.Optional;
22-
import java.util.stream.Collectors;
2322

2423
import org.springframework.data.domain.Pageable;
2524
import org.springframework.data.domain.Sort;
2625
import org.springframework.data.repository.util.QueryExecutionConverters;
26+
import org.springframework.lang.Nullable;
2727
import org.springframework.util.Assert;
2828

2929
/**
3030
* {@link ParameterAccessor} implementation using a {@link Parameters} instance to find special parameters.
3131
*
3232
* @author Oliver Gierke
33+
* @author Mark Paluch
3334
*/
3435
public class ParametersParameterAccessor implements ParameterAccessor {
3536

@@ -50,9 +51,11 @@ public ParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values)
5051
Assert.isTrue(parameters.getNumberOfParameters() == values.length, "Invalid number of parameters given!");
5152

5253
this.parameters = parameters;
53-
this.values = Arrays.stream(values)//
54-
.map(QueryExecutionConverters::unwrap)//
55-
.collect(Collectors.toList());
54+
this.values = new ArrayList<>(values.length);
55+
56+
for (Object value : values) {
57+
this.values.add(QueryExecutionConverters.unwrap(value));
58+
}
5659
}
5760

5861
/**
@@ -64,6 +67,15 @@ public ParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values)
6467
return parameters;
6568
}
6669

70+
/**
71+
* Returns the potentially unwrapped values.
72+
*
73+
* @return
74+
*/
75+
protected List<Object> getValues() {
76+
return this.values;
77+
}
78+
6779
/*
6880
* (non-Javadoc)
6981
* @see org.springframework.data.repository.query.ParameterAccessor#getPageable()
@@ -104,8 +116,23 @@ public Sort getSort() {
104116
* @return
105117
*/
106118
public Optional<Class<?>> getDynamicProjection() {
107-
return Optional.ofNullable(
108-
parameters.hasDynamicProjection() ? (Class<?>) values.get(parameters.getDynamicProjectionIndex()) : null);
119+
120+
return Optional.ofNullable(parameters.hasDynamicProjection() //
121+
? (Class<?>) values.get(parameters.getDynamicProjectionIndex()) //
122+
: null);
123+
}
124+
125+
/**
126+
* Returns the dynamic projection type if available, {@literal null} otherwise.
127+
*
128+
* @return
129+
*/
130+
@Nullable
131+
public Class<?> findDynamicProjection() {
132+
133+
return parameters.hasDynamicProjection() //
134+
? (Class<?>) values.get(parameters.getDynamicProjectionIndex())
135+
: null;
109136
}
110137

111138
/**
@@ -133,8 +160,13 @@ public Object getBindableValue(int index) {
133160
*/
134161
public boolean hasBindableNullValue() {
135162

136-
return parameters.getBindableParameters().stream()//
137-
.anyMatch(it -> values.get(it.getIndex()) == null);
163+
for (Parameter parameter : parameters.getBindableParameters()) {
164+
if (values.get(parameter.getIndex()) == null) {
165+
return true;
166+
}
167+
}
168+
169+
return false;
138170
}
139171

140172
/*

src/main/java/org/springframework/data/repository/query/QueryMethod.java

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class QueryMethod {
5252
private final Parameters<?, ?> parameters;
5353
private final ResultProcessor resultProcessor;
5454
private final Lazy<Class<?>> domainClass;
55+
private final Lazy<Boolean> isCollectionQuery;
5556

5657
/**
5758
* Creates a new {@link QueryMethod} from the given parameters. Looks up the correct query to use for following
@@ -111,6 +112,7 @@ public QueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory
111112
});
112113

113114
this.resultProcessor = new ResultProcessor(this, factory);
115+
this.isCollectionQuery = Lazy.of(this::calculateIsCollectionQuery);
114116
}
115117

116118
/**
@@ -170,22 +172,7 @@ public Class<?> getReturnedObjectType() {
170172
* @return
171173
*/
172174
public boolean isCollectionQuery() {
173-
174-
if (isPageQuery() || isSliceQuery()) {
175-
return false;
176-
}
177-
178-
Class<?> returnType = method.getReturnType();
179-
180-
if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
181-
return true;
182-
}
183-
184-
if (QueryExecutionConverters.supports(unwrappedReturnType)) {
185-
return !QueryExecutionConverters.isSingleValue(unwrappedReturnType);
186-
}
187-
188-
return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike();
175+
return isCollectionQuery.get();
189176
}
190177

191178
/**
@@ -262,6 +249,25 @@ public String toString() {
262249
return method.toString();
263250
}
264251

252+
private boolean calculateIsCollectionQuery() {
253+
254+
if (isPageQuery() || isSliceQuery()) {
255+
return false;
256+
}
257+
258+
Class<?> returnType = method.getReturnType();
259+
260+
if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
261+
return true;
262+
}
263+
264+
if (QueryExecutionConverters.supports(unwrappedReturnType)) {
265+
return !QueryExecutionConverters.isSingleValue(unwrappedReturnType);
266+
}
267+
268+
return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike();
269+
}
270+
265271
private static Class<? extends Object> potentiallyUnwrapReturnTypeFor(Method method) {
266272

267273
if (QueryExecutionConverters.supports(method.getReturnType())) {

src/main/java/org/springframework/data/repository/query/ResultProcessor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ public ResultProcessor withDynamicProjection(ParameterAccessor accessor) {
9696

9797
Assert.notNull(accessor, "Parameter accessor must not be null!");
9898

99-
return accessor.getDynamicProjection().map(this::withType).orElse(this);
99+
Class<?> projection = accessor.findDynamicProjection();
100+
101+
return projection == null //
102+
? this //
103+
: withType(projection);
100104
}
101105

102106
/**

src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springframework.scheduling.annotation.AsyncResult;
5858
import org.springframework.util.Assert;
5959
import org.springframework.util.ClassUtils;
60+
import org.springframework.util.ConcurrentReferenceHashMap;
6061
import org.springframework.util.concurrent.ListenableFuture;
6162

6263
import com.google.common.base.Optional;
@@ -102,6 +103,7 @@ public abstract class QueryExecutionConverters {
102103
private static final Set<Converter<Object, Object>> UNWRAPPERS = new HashSet<Converter<Object, Object>>();
103104
private static final Set<Class<?>> ALLOWED_PAGEABLE_TYPES = new HashSet<Class<?>>();
104105
private static final Map<Class<?>, ExecutionAdapter> EXECUTION_ADAPTER = new HashMap<>();
106+
private static final Map<Class<?>, Boolean> SUPPORTS_CACHE = new ConcurrentReferenceHashMap<>();
105107

106108
static {
107109

@@ -173,13 +175,16 @@ public static boolean supports(Class<?> type) {
173175

174176
Assert.notNull(type, "Type must not be null!");
175177

176-
for (WrapperType candidate : WRAPPER_TYPES) {
177-
if (candidate.getType().isAssignableFrom(type)) {
178-
return true;
178+
return SUPPORTS_CACHE.computeIfAbsent(type, key -> {
179+
180+
for (WrapperType candidate : WRAPPER_TYPES) {
181+
if (candidate.getType().isAssignableFrom(type)) {
182+
return true;
183+
}
179184
}
180-
}
181185

182-
return false;
186+
return false;
187+
});
183188
}
184189

185190
/**
@@ -308,11 +313,12 @@ public static TypeInformation<?> unwrapWrapperTypes(TypeInformation<?> type) {
308313
* @param returnType must not be {@literal null}.
309314
* @return
310315
*/
316+
@Nullable
311317
public static ExecutionAdapter getExecutionAdapter(Class<?> returnType) {
312318

313319
Assert.notNull(returnType, "Return type must not be null!");
314320

315-
return EXECUTION_ADAPTER.getOrDefault(returnType, ThrowingSupplier::get);
321+
return EXECUTION_ADAPTER.get(returnType);
316322
}
317323

318324
public interface ThrowingSupplier {

0 commit comments

Comments
 (0)