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:
Mark Paluch
2019-07-19 17:17:05 +02:00
parent 10d7339799
commit ab9a9bc0fc
7 changed files with 122 additions and 64 deletions

View File

@@ -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;

View File

@@ -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();
}
/**

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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.");
}