diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index f8d526a97..79c8f5ef7 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -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 execution; final Lazy 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 applyHints(T query, JpaQueryMethod method) { - for (QueryHint hint : method.getHints()) { - applyQueryHint(query, hint); + List 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 hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(), - getQueryMethod().getEntityInformation().getJavaType()); + if (entityGraph != null) { + Map hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(), + getQueryMethod().getEntityInformation().getJavaType()); - for (Map.Entry hint : hints.entrySet()) { - query.setHint(hint.getKey(), hint.getValue()); + for (Map.Entry hint : hints.entrySet()) { + query.setHint(hint.getKey(), hint.getValue()); + } } return query; diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 517960586..fc5349665 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -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; + private final Lazy queryHints; + private final Lazy jpaEntityGraph; + private final Lazy modifying; + private final Lazy isNativeQuery; + private final Lazy isCollectionQuery; + private final Lazy isProcedureQuery; + private final Lazy> 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 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(); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java index db040921d..e1ab7acd0 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java @@ -75,7 +75,9 @@ public class ParameterBinder { public 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; } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java index 0c208a5e5..61884f7ef 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java @@ -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 createSetters(List parameterBindings, DeclaredQuery declaredQuery, QueryParameterSetterFactory... strategies) { - return parameterBindings.stream() // - .map(it -> createQueryParameterSetter(it, strategies, declaredQuery)) // - .collect(StreamUtils.toUnmodifiableList()); + List 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; } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 47ed7b936..391b1350b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -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> expressions; private final PersistenceProvider persistenceProvider; + private final Map>, 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> expressions) { - return ParameterBinderFactory.createCriteriaBinder(parameters, expressions); + return this.binderCache.computeIfAbsent(expressions, + key -> ParameterBinderFactory.createCriteriaBinder(parameters, key)); } private Sort getDynamicSort(JpaParametersParameterAccessor accessor) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java index 57e2cece3..bb89b83dd 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java @@ -107,8 +107,7 @@ abstract class QueryParameterSetterFactory { * @param parameter the method parameter to bind. */ private static QueryParameterSetter createSetter(Function 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 diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java index fc20470ad..7ac06f4e7 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java @@ -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."); }