DATAJPA-1575 - Reuse annotation lookup results and eliminate Stream usage.
Evaluate JpaQuery-kind once instead for each invocation as the method signature isn't changing between invocations. Add early returns. Reuse cached annotation lookup results. Replace forEach calls with for-loops.
This commit is contained in:
@@ -43,7 +43,6 @@ import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEnt
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryExecution.SlicedExecution;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution;
|
||||
import org.springframework.data.jpa.util.JpaMetamodel;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
@@ -68,6 +67,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
private final EntityManager em;
|
||||
private final JpaMetamodel metamodel;
|
||||
private final PersistenceProvider provider;
|
||||
private final Lazy<JpaQueryExecution> execution;
|
||||
|
||||
final Lazy<ParameterBinder> parameterBinder = new Lazy<>(this::createBinder);
|
||||
|
||||
@@ -86,6 +86,24 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
this.em = em;
|
||||
this.metamodel = JpaMetamodel.of(em.getMetamodel());
|
||||
this.provider = PersistenceProvider.fromEntityManager(em);
|
||||
this.execution = Lazy.of(() -> {
|
||||
|
||||
if (method.isStreamQuery()) {
|
||||
return new StreamExecution();
|
||||
} else if (method.isProcedureQuery()) {
|
||||
return new ProcedureExecution();
|
||||
} else if (method.isCollectionQuery()) {
|
||||
return new CollectionExecution();
|
||||
} else if (method.isSliceQuery()) {
|
||||
return new SlicedExecution();
|
||||
} else if (method.isPageQuery()) {
|
||||
return new PagedExecution();
|
||||
} else if (method.isModifyingQuery()) {
|
||||
return null;
|
||||
} else {
|
||||
return new SingleEntityExecution();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -142,17 +160,13 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
|
||||
protected JpaQueryExecution getExecution() {
|
||||
|
||||
if (method.isStreamQuery()) {
|
||||
return new StreamExecution();
|
||||
} else if (method.isProcedureQuery()) {
|
||||
return new ProcedureExecution();
|
||||
} else if (method.isCollectionQuery()) {
|
||||
return new CollectionExecution();
|
||||
} else if (method.isSliceQuery()) {
|
||||
return new SlicedExecution();
|
||||
} else if (method.isPageQuery()) {
|
||||
return new PagedExecution();
|
||||
} else if (method.isModifyingQuery()) {
|
||||
JpaQueryExecution execution = this.execution.getNullable();
|
||||
|
||||
if (execution != null) {
|
||||
return execution;
|
||||
}
|
||||
|
||||
if (method.isModifyingQuery()) {
|
||||
return new ModifyingExecution(method, em);
|
||||
} else {
|
||||
return new SingleEntityExecution();
|
||||
@@ -167,8 +181,12 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
*/
|
||||
protected <T extends Query> T applyHints(T query, JpaQueryMethod method) {
|
||||
|
||||
for (QueryHint hint : method.getHints()) {
|
||||
applyQueryHint(query, hint);
|
||||
List<QueryHint> hints = method.getHints();
|
||||
|
||||
if (!hints.isEmpty()) {
|
||||
for (QueryHint hint : hints) {
|
||||
applyQueryHint(query, hint);
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
@@ -219,14 +237,15 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
*/
|
||||
private Query applyEntityGraphConfiguration(Query query, JpaQueryMethod method) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.notNull(method, "JpaQueryMethod must not be null!");
|
||||
JpaEntityGraph entityGraph = method.getEntityGraph();
|
||||
|
||||
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(),
|
||||
getQueryMethod().getEntityInformation().getJavaType());
|
||||
if (entityGraph != null) {
|
||||
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(),
|
||||
getQueryMethod().getEntityInformation().getJavaType());
|
||||
|
||||
for (Map.Entry<String, Object> hint : hints.entrySet()) {
|
||||
query.setHint(hint.getKey(), hint.getValue());
|
||||
for (Map.Entry<String, Object> hint : hints.entrySet()) {
|
||||
query.setHint(hint.getKey(), hint.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -79,6 +80,14 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
private final Method method;
|
||||
|
||||
private @Nullable StoredProcedureAttributes storedProcedureAttributes;
|
||||
private final Lazy<LockModeType> lockModeType;
|
||||
private final Lazy<QueryHints> queryHints;
|
||||
private final Lazy<JpaEntityGraph> jpaEntityGraph;
|
||||
private final Lazy<Modifying> modifying;
|
||||
private final Lazy<Boolean> isNativeQuery;
|
||||
private final Lazy<Boolean> isCollectionQuery;
|
||||
private final Lazy<Boolean> isProcedureQuery;
|
||||
private final Lazy<JpaEntityMetadata<?>> entityMetadata;
|
||||
|
||||
/**
|
||||
* Creates a {@link JpaQueryMethod}.
|
||||
@@ -98,6 +107,28 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
|
||||
this.method = method;
|
||||
this.extractor = extractor;
|
||||
this.lockModeType = Lazy
|
||||
.of(() -> (LockModeType) Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Lock.class)) //
|
||||
.map(AnnotationUtils::getValue) //
|
||||
.orElse(null));
|
||||
|
||||
this.queryHints = Lazy.of(() -> AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class));
|
||||
this.modifying = Lazy.of(() -> AnnotatedElementUtils.findMergedAnnotation(method, Modifying.class));
|
||||
this.jpaEntityGraph = Lazy.of(() -> {
|
||||
|
||||
EntityGraph entityGraph = AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class);
|
||||
|
||||
if (entityGraph == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new JpaEntityGraph(entityGraph, getNamedQueryName());
|
||||
});
|
||||
this.isNativeQuery = Lazy.of(() -> getAnnotationValue("nativeQuery", Boolean.class));
|
||||
this.isCollectionQuery = Lazy
|
||||
.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(method.getReturnType()));
|
||||
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
|
||||
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
|
||||
|
||||
Assert.isTrue(!(isModifyingQuery() && getParameters().hasSpecialParameter()),
|
||||
String.format("Modifying method must not contain %s!", Parameters.TYPES));
|
||||
@@ -135,7 +166,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public JpaEntityMetadata<?> getEntityInformation() {
|
||||
return new DefaultJpaEntityMetadata(getDomainClass());
|
||||
return this.entityMetadata.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,8 +176,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Override
|
||||
public boolean isModifyingQuery() {
|
||||
|
||||
return null != AnnotationUtils.findAnnotation(method, Modifying.class);
|
||||
return modifying.getNullable() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +186,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
*/
|
||||
List<QueryHint> getHints() {
|
||||
|
||||
QueryHints hints = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class);
|
||||
QueryHints hints = this.queryHints.getNullable();
|
||||
if (hints != null) {
|
||||
return Arrays.asList(hints.value());
|
||||
}
|
||||
@@ -171,10 +201,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Nullable
|
||||
LockModeType getLockModeType() {
|
||||
|
||||
return (LockModeType) Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Lock.class)) //
|
||||
.map(AnnotationUtils::getValue) //
|
||||
.orElse(null);
|
||||
return lockModeType.getNullable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,9 +212,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Nullable
|
||||
JpaEntityGraph getEntityGraph() {
|
||||
|
||||
EntityGraph annotation = AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class);
|
||||
return annotation == null ? null : new JpaEntityGraph(annotation, getNamedQueryName());
|
||||
return jpaEntityGraph.getNullable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +223,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
*/
|
||||
boolean applyHintsToCountQuery() {
|
||||
|
||||
QueryHints hints = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class);
|
||||
QueryHints hints = this.queryHints.getNullable();
|
||||
return hints != null ? hints.forCounting() : false;
|
||||
}
|
||||
|
||||
@@ -208,7 +233,6 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
* @return
|
||||
*/
|
||||
QueryExtractor getQueryExtractor() {
|
||||
|
||||
return extractor;
|
||||
}
|
||||
|
||||
@@ -218,7 +242,6 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
* @return
|
||||
*/
|
||||
Class<?> getReturnType() {
|
||||
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
@@ -287,7 +310,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
* @return
|
||||
*/
|
||||
boolean isNativeQuery() {
|
||||
return getAnnotationValue("nativeQuery", Boolean.class).booleanValue();
|
||||
return this.isNativeQuery.get();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -314,7 +337,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
|
||||
/**
|
||||
* Returns whether we should flush automatically for modifying queries.
|
||||
*
|
||||
*
|
||||
* @return whether we should flush automatically.
|
||||
*/
|
||||
boolean getFlushAutomatically() {
|
||||
@@ -377,7 +400,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Override
|
||||
public boolean isCollectionQuery() {
|
||||
return super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(method.getReturnType());
|
||||
return this.isCollectionQuery.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,7 +409,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
* @return
|
||||
*/
|
||||
public boolean isProcedureQuery() {
|
||||
return AnnotationUtils.findAnnotation(method, Procedure.class) != null;
|
||||
return this.isProcedureQuery.get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,9 @@ public class ParameterBinder {
|
||||
|
||||
public <T extends Query> T bind(T jpaQuery, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling) {
|
||||
|
||||
parameterSetters.forEach(it -> it.setParameter(jpaQuery, accessor, errorHandling));
|
||||
for (QueryParameterSetter setter : parameterSetters) {
|
||||
setter.setParameter(jpaQuery, accessor, errorHandling);
|
||||
}
|
||||
|
||||
return jpaQuery;
|
||||
}
|
||||
|
||||
@@ -16,15 +16,12 @@
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -127,18 +124,26 @@ class ParameterBinderFactory {
|
||||
private static Iterable<QueryParameterSetter> createSetters(List<ParameterBinding> parameterBindings,
|
||||
DeclaredQuery declaredQuery, QueryParameterSetterFactory... strategies) {
|
||||
|
||||
return parameterBindings.stream() //
|
||||
.map(it -> createQueryParameterSetter(it, strategies, declaredQuery)) //
|
||||
.collect(StreamUtils.toUnmodifiableList());
|
||||
List<QueryParameterSetter> setters = new ArrayList<>(parameterBindings.size());
|
||||
for (ParameterBinding parameterBinding : parameterBindings) {
|
||||
setters.add(createQueryParameterSetter(parameterBinding, strategies, declaredQuery));
|
||||
}
|
||||
|
||||
return setters;
|
||||
}
|
||||
|
||||
private static QueryParameterSetter createQueryParameterSetter(ParameterBinding binding,
|
||||
QueryParameterSetterFactory[] strategies, DeclaredQuery declaredQuery) {
|
||||
|
||||
return Arrays.stream(strategies)//
|
||||
.map(it -> it.create(binding, declaredQuery))//
|
||||
.filter(Objects::nonNull)//
|
||||
.findFirst()//
|
||||
.orElse(QueryParameterSetter.NOOP);
|
||||
for (QueryParameterSetterFactory strategy : strategies) {
|
||||
|
||||
QueryParameterSetter setter = strategy.create(binding, declaredQuery);
|
||||
|
||||
if (setter != null) {
|
||||
return setter;
|
||||
}
|
||||
}
|
||||
|
||||
return QueryParameterSetter.NOOP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
@@ -30,7 +32,6 @@ import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ExistsExecution;
|
||||
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
@@ -212,6 +213,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
|
||||
private final @Nullable ParameterBinder cachedParameterBinder;
|
||||
private final @Nullable List<ParameterMetadata<?>> expressions;
|
||||
private final PersistenceProvider persistenceProvider;
|
||||
private final Map<List<ParameterMetadata<?>>, ParameterBinder> binderCache = new ConcurrentHashMap<>();
|
||||
|
||||
QueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) {
|
||||
|
||||
@@ -304,6 +306,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
|
||||
@Nullable JpaParametersParameterAccessor accessor) {
|
||||
|
||||
EntityManager entityManager = getEntityManager();
|
||||
|
||||
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
|
||||
ResultProcessor processor = getQueryMethod().getResultProcessor();
|
||||
|
||||
@@ -331,7 +334,8 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
|
||||
}
|
||||
|
||||
private ParameterBinder getBinder(List<ParameterMetadata<?>> expressions) {
|
||||
return ParameterBinderFactory.createCriteriaBinder(parameters, expressions);
|
||||
return this.binderCache.computeIfAbsent(expressions,
|
||||
key -> ParameterBinderFactory.createCriteriaBinder(parameters, key));
|
||||
}
|
||||
|
||||
private Sort getDynamicSort(JpaParametersParameterAccessor accessor) {
|
||||
|
||||
@@ -107,8 +107,7 @@ abstract class QueryParameterSetterFactory {
|
||||
* @param parameter the method parameter to bind.
|
||||
*/
|
||||
private static QueryParameterSetter createSetter(Function<JpaParametersParameterAccessor, Object> valueExtractor,
|
||||
ParameterBinding binding,
|
||||
@Nullable JpaParameter parameter) {
|
||||
ParameterBinding binding, @Nullable JpaParameter parameter) {
|
||||
|
||||
TemporalType temporalType = parameter != null && parameter.isTemporalParameter() //
|
||||
? parameter.getRequiredTemporalType() //
|
||||
@@ -240,9 +239,15 @@ abstract class QueryParameterSetterFactory {
|
||||
@Nullable
|
||||
private JpaParameter findParameterForBinding(ParameterBinding binding) {
|
||||
|
||||
return parameters.getBindableParameters().stream() //
|
||||
.filter(candidate -> binding.getRequiredName().equals(getName(candidate))) //
|
||||
.findFirst().orElse(null);
|
||||
JpaParameters bindableParameters = parameters.getBindableParameters();
|
||||
|
||||
for (JpaParameter bindableParameter : bindableParameters) {
|
||||
if (binding.getRequiredName().equals(getName(bindableParameter))) {
|
||||
return bindableParameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) {
|
||||
@@ -309,8 +314,7 @@ abstract class QueryParameterSetterFactory {
|
||||
|
||||
return new NamedOrIndexedQueryParameterSetter(values -> {
|
||||
return getAndPrepare(parameter, metadata, values);
|
||||
},
|
||||
metadata.getExpression(), temporalType);
|
||||
}, metadata.getExpression(), temporalType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -15,16 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
|
||||
|
||||
@@ -45,7 +46,7 @@ public class QueryParameterSetterFactoryUnitTests {
|
||||
public void before() {
|
||||
|
||||
// we have one bindable parameter
|
||||
when(parameters.getBindableParameters().stream()).thenReturn(Stream.of(mock(JpaParameter.class)));
|
||||
when(parameters.getBindableParameters().iterator()).thenReturn(Stream.of(mock(JpaParameter.class)).iterator());
|
||||
|
||||
setterFactory = QueryParameterSetterFactory.basic(parameters);
|
||||
}
|
||||
@@ -58,7 +59,7 @@ public class QueryParameterSetterFactoryUnitTests {
|
||||
@Test // DATAJPA-1058
|
||||
public void exceptionWhenQueryContainNamedParametersAndMethodParametersAreNotNamed() {
|
||||
|
||||
Assertions.assertThatExceptionOfType(IllegalStateException.class) //
|
||||
assertThatExceptionOfType(IllegalStateException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter"))) //
|
||||
.withMessageContaining("Java 8") //
|
||||
.withMessageContaining("@Param") //
|
||||
@@ -75,7 +76,7 @@ public class QueryParameterSetterFactoryUnitTests {
|
||||
// one argument present in the method signature
|
||||
when(binding.getRequiredPosition()).thenReturn(1);
|
||||
|
||||
Assertions.assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter"))) //
|
||||
.withMessage("At least 1 parameter(s) provided but only 0 parameter(s) present in query.");
|
||||
}
|
||||
@@ -89,7 +90,7 @@ public class QueryParameterSetterFactoryUnitTests {
|
||||
// one argument present in the method signature
|
||||
when(binding.getRequiredPosition()).thenReturn(1);
|
||||
|
||||
Assertions.assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith ?1"))) //
|
||||
.withMessage("At least 1 parameter(s) provided but only 0 parameter(s) present in query.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user