diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 1f6f21c91..399882d23 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -15,8 +15,6 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.QueryParameterSetter.ErrorHandling.*; - import javax.persistence.EntityManager; import javax.persistence.Query; @@ -42,6 +40,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { private final DeclaredQuery countQuery; private final QueryMethodEvaluationContextProvider evaluationContextProvider; private final SpelExpressionParser parser; + private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache(); /** * Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and @@ -86,9 +85,11 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { Query query = createJpaQuery(sortedQueryString, processor.getReturnedType()); + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(sortedQueryString, query); + // it is ok to reuse the binding contained in the ParameterBinder although we create a new query String because the // parameters in the query do not change. - return parameterBinder.get().bindAndPrepare(query, accessor); + return parameterBinder.get().bindAndPrepare(query, metadata, accessor); } /* @@ -116,7 +117,11 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { ? em.createNativeQuery(queryString) // : em.createQuery(queryString, Long.class); - return parameterBinder.get().bind(query, accessor, LENIENT); + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryString, query); + + parameterBinder.get().bind(metadata.withQuery(query), accessor, QueryParameterSetter.ErrorHandling.LENIENT); + + return query; } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 12e0285b9..adbdd6d10 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -176,7 +176,6 @@ public abstract class JpaQueryExecution { return PageableExecutionUtils.getPage(query.getResultList(), accessor.getPageable(), () -> count(repositoryQuery, accessor)); - } private long count(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 747830653..220ff21fe 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -55,6 +55,7 @@ final class NamedQuery extends AbstractJpaQuery { private final QueryExtractor extractor; private final boolean namedCountQueryIsPresent; private final DeclaredQuery declaredQuery; + private final QueryParameterSetter.QueryMetadataCache metadataCache; /** * Creates a new {@link NamedQuery}. @@ -93,6 +94,8 @@ final class NamedQuery extends AbstractJpaQuery { LOG.warn("Finder method {} is backed by a NamedQuery" + " but contains a Pageable parameter! Sorting delivered " + "via this Pageable will not be applied!", method); } + + this.metadataCache = new QueryParameterSetter.QueryMetadataCache(); } /** @@ -166,7 +169,9 @@ final class NamedQuery extends AbstractJpaQuery { ? em.createNamedQuery(queryName) // : em.createNamedQuery(queryName, typeToRead); - return parameterBinder.get().bindAndPrepare(query, accessor); + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryName, query); + + return parameterBinder.get().bindAndPrepare(query, metadata, accessor); } /* @@ -179,18 +184,21 @@ final class NamedQuery extends AbstractJpaQuery { EntityManager em = getEntityManager(); TypedQuery countQuery; + String cacheKey; if (namedCountQueryIsPresent) { - + cacheKey = countQueryName; countQuery = em.createNamedQuery(countQueryName, Long.class); } else { String countQueryString = declaredQuery.deriveCountQuery(null, countProjection).getQueryString(); - + cacheKey = countQueryString; countQuery = em.createQuery(countQueryString, Long.class); } - return parameterBinder.get().bind(countQuery, accessor, LENIENT); + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(cacheKey, countQuery); + + return parameterBinder.get().bind(countQuery, metadata, accessor); } /* 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 e1ab7acd0..6a0e6f723 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 @@ -69,17 +69,18 @@ public class ParameterBinder { this.useJpaForPaging = useJpaForPaging; } - public T bind(T jpaQuery, JpaParametersParameterAccessor accessor) { - return bind(jpaQuery, accessor, ErrorHandling.STRICT); + public T bind(T jpaQuery, QueryParameterSetter.QueryMetadata metadata, + JpaParametersParameterAccessor accessor) { + bind(metadata.withQuery(jpaQuery), accessor, ErrorHandling.STRICT); + return jpaQuery; } - public T bind(T jpaQuery, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling) { + public void bind(QueryParameterSetter.BindableQuery query, JpaParametersParameterAccessor accessor, + ErrorHandling errorHandling) { for (QueryParameterSetter setter : parameterSetters) { - setter.setParameter(jpaQuery, accessor, errorHandling); + setter.setParameter(query, accessor, errorHandling); } - - return jpaQuery; } /** @@ -88,19 +89,18 @@ public class ParameterBinder { * @param query must not be {@literal null}. * @param values values of method parameters to be assigned to the query parameters. */ - Query bindAndPrepare(Query query, JpaParametersParameterAccessor accessor) { + Query bindAndPrepare(Query query, QueryParameterSetter.QueryMetadata metadata, + JpaParametersParameterAccessor accessor) { - Assert.notNull(query, "Query must not be null!"); - - Query result = bind(query, accessor); + bind(query, metadata, accessor); if (!useJpaForPaging || !parameters.hasPageableParameter() || accessor.getPageable().isUnpaged()) { - return result; + return query; } - result.setFirstResult((int) accessor.getPageable().getOffset()); - result.setMaxResults(accessor.getPageable().getPageSize()); + query.setFirstResult((int) accessor.getPageable().getOffset()); + query.setMaxResults(accessor.getPageable().getPageSize()); - return result; + return query; } } 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 391b1350b..f41b2906e 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 @@ -214,6 +214,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { private final @Nullable List> expressions; private final PersistenceProvider persistenceProvider; private final Map>, ParameterBinder> binderCache = new ConcurrentHashMap<>(); + private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache(); QueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) { @@ -251,7 +252,9 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { throw new IllegalStateException("ParameterBinder is null!"); } - return restrictMaxResultsIfNecessary(invokeBinding(parameterBinder, createQuery(criteriaQuery), accessor)); + TypedQuery query = createQuery(criteriaQuery); + + return restrictMaxResultsIfNecessary(invokeBinding(parameterBinder, query, accessor, this.metadataCache)); } /** @@ -327,10 +330,12 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { /** * Invokes parameter binding on the given {@link TypedQuery}. */ - protected Query invokeBinding(ParameterBinder binder, TypedQuery query, - JpaParametersParameterAccessor accessor) { + protected Query invokeBinding(ParameterBinder binder, TypedQuery query, JpaParametersParameterAccessor accessor, + QueryParameterSetter.QueryMetadataCache metadataCache) { - return binder.bindAndPrepare(query, accessor); + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata("query", query); + + return binder.bindAndPrepare(query, metadata, accessor); } private ParameterBinder getBinder(List> expressions) { @@ -382,9 +387,12 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { * @see QueryPreparer#invokeBinding(ParameterBinder, TypedQuery, JpaParametersParameterAccessor) */ @Override - protected Query invokeBinding(ParameterBinder binder, TypedQuery query, - JpaParametersParameterAccessor accessor) { - return binder.bind(query, accessor); + protected Query invokeBinding(ParameterBinder binder, TypedQuery query, JpaParametersParameterAccessor accessor, + QueryParameterSetter.QueryMetadataCache metadataCache) { + + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata("countquery", query); + + return binder.bind(query, metadata, accessor); } } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java index b18319b00..e97a841e1 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java @@ -18,7 +18,11 @@ package org.springframework.data.jpa.repository.query; import static org.springframework.data.jpa.repository.query.QueryParameterSetter.ErrorHandling.*; import java.lang.reflect.Proxy; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import javax.persistence.Parameter; @@ -42,7 +46,7 @@ import org.springframework.util.Assert; */ interface QueryParameterSetter { - void setParameter(Query query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling); + void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling); /** Noop implementation */ QueryParameterSetter NOOP = (query, values, errorHandling) -> {}; @@ -52,8 +56,6 @@ interface QueryParameterSetter { */ class NamedOrIndexedQueryParameterSetter implements QueryParameterSetter { - private static final Logger LOGGER = LoggerFactory.getLogger(NamedOrIndexedQueryParameterSetter.class); - private final Function valueExtractor; private final Parameter parameter; private final @Nullable TemporalType temporalType; @@ -79,7 +81,8 @@ interface QueryParameterSetter { */ @SuppressWarnings("unchecked") @Override - public void setParameter(Query query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling) { + public void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor, + ErrorHandling errorHandling) { Object value = valueExtractor.apply(accessor); @@ -92,7 +95,7 @@ interface QueryParameterSetter { if (parameter instanceof ParameterExpression) { errorHandling.execute(() -> query.setParameter((Parameter) parameter, (Date) value, temporalType)); - } else if (parameter.getName() != null && QueryUtils.hasNamedParameter(query)) { + } else if (query.hasNamedParameters() && parameter.getName() != null) { errorHandling.execute(() -> query.setParameter(parameter.getName(), (Date) value, temporalType)); } else { @@ -100,7 +103,7 @@ interface QueryParameterSetter { if (position != null // && (query.getParameters().size() >= parameter.getPosition() // - || registerExcessParameters(query) // + || query.registerExcessParameters() // || errorHandling == LENIENT)) { errorHandling.execute(() -> query.setParameter(parameter.getPosition(), (Date) value, temporalType)); @@ -111,7 +114,7 @@ interface QueryParameterSetter { if (parameter instanceof ParameterExpression) { errorHandling.execute(() -> query.setParameter((Parameter) parameter, value)); - } else if (parameter.getName() != null && QueryUtils.hasNamedParameter(query)) { + } else if (query.hasNamedParameters() && parameter.getName() != null) { errorHandling.execute(() -> query.setParameter(parameter.getName(), value)); } else { @@ -121,51 +124,12 @@ interface QueryParameterSetter { if (position != null // && (query.getParameters().size() >= position // || errorHandling == LENIENT // - || registerExcessParameters(query))) { - + || query.registerExcessParameters())) { errorHandling.execute(() -> query.setParameter(position, value)); } } } } - - private boolean registerExcessParameters(Query query) { - - // DATAJPA-1172 - // Since EclipseLink doesn't reliably report whether a query has parameters - // we simply try to set the parameters and ignore possible failures. - // this is relevant for native queries with SpEL expressions, where the method parameters don't have to match the - // parameters in the query. - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=521915 - - return query.getParameters().size() == 0 && unwrapClass(query).getName().startsWith("org.eclipse"); - } - - /** - * Returns the actual target {@link Query} instance, even if the provided query is a {@link Proxy} based on - * {@link org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler}. - * - * @param query a {@link Query} instance, possibly a Proxy. - * @return the class of the actual underlying class if it can be determined, the class of the passed in instance - * otherwise. - */ - private static Class unwrapClass(Query query) { - - Class queryType = query.getClass(); - - try { - - return Proxy.isProxyClass(queryType) // - ? query.unwrap(null).getClass() // - : queryType; - - } catch (RuntimeException e) { - - LOGGER.warn("Failed to unwrap actual class for Query proxy.", e); - - return queryType; - } - } } enum ErrorHandling { @@ -195,4 +159,184 @@ interface QueryParameterSetter { abstract void execute(Runnable block); } + + /** + * Cache for {@link QueryMetadata}. Optimizes for small cache sizes on a best-effort basis. + */ + class QueryMetadataCache { + + private Map cache = Collections.emptyMap(); + + /** + * Retrieve the {@link QueryMetadata} for a given {@code cacheKey}. + * + * @param cacheKey + * @param query + * @return + */ + public QueryMetadata getMetadata(String cacheKey, Query query) { + + QueryMetadata queryMetadata = cache.get(cacheKey); + + if (queryMetadata == null) { + + queryMetadata = new QueryMetadata(query); + + Map cache; + + if (this.cache.isEmpty()) { + cache = Collections.singletonMap(cacheKey, queryMetadata); + } else { + cache = new HashMap<>(this.cache); + cache.put(cacheKey, queryMetadata); + } + + synchronized (this) { + this.cache = cache; + } + } + + return queryMetadata; + } + } + + /** + * Metadata for a JPA {@link Query}. + */ + class QueryMetadata { + + private final boolean namedParameters; + private final Set> parameters; + private final boolean registerExcessParameters; + + QueryMetadata(Query query) { + + this.namedParameters = QueryUtils.hasNamedParameter(query); + this.parameters = query.getParameters(); + + // DATAJPA-1172 + // Since EclipseLink doesn't reliably report whether a query has parameters + // we simply try to set the parameters and ignore possible failures. + // this is relevant for native queries with SpEL expressions, where the method parameters don't have to match the + // parameters in the query. + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=521915 + + this.registerExcessParameters = query.getParameters().size() == 0 + && unwrapClass(query).getName().startsWith("org.eclipse"); + } + + QueryMetadata(QueryMetadata metadata) { + + this.namedParameters = metadata.namedParameters; + this.parameters = metadata.parameters; + this.registerExcessParameters = metadata.registerExcessParameters; + } + + /** + * Create a {@link BindableQuery} for a {@link Query}. + * + * @param query + * @return + */ + public BindableQuery withQuery(Query query) { + return new BindableQuery(this, query); + } + + /** + * @return + */ + public Set> getParameters() { + return parameters; + } + + /** + * @return {@literal true} if the underlying query uses named parameters. + */ + public boolean hasNamedParameters() { + return this.namedParameters; + } + + public boolean registerExcessParameters() { + return this.registerExcessParameters; + } + + /** + * Returns the actual target {@link Query} instance, even if the provided query is a {@link Proxy} based on + * {@link org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler}. + * + * @param query a {@link Query} instance, possibly a Proxy. + * @return the class of the actual underlying class if it can be determined, the class of the passed in instance + * otherwise. + */ + private static Class unwrapClass(Query query) { + + Class queryType = query.getClass(); + + try { + + return Proxy.isProxyClass(queryType) // + ? query.unwrap(null).getClass() // + : queryType; + + } catch (RuntimeException e) { + + LoggerFactory.getLogger(QueryMetadata.class).warn("Failed to unwrap actual class for Query proxy.", e); + + return queryType; + } + } + } + + /** + * A bindable {@link Query}. + */ + class BindableQuery extends QueryMetadata { + + private final Query query; + private final Query unwrapped; + + BindableQuery(QueryMetadata metadata, Query query) { + super(metadata); + this.query = query; + this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query; + } + + private BindableQuery(Query query) { + super(query); + this.query = query; + this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query; + } + + public static BindableQuery from(Query query) { + return new BindableQuery(query); + } + + public Query getQuery() { + return query; + } + + public Query setParameter(Parameter param, T value) { + return unwrapped.setParameter(param, value); + } + + public Query setParameter(Parameter param, Date value, TemporalType temporalType) { + return unwrapped.setParameter(param, value, temporalType); + } + + public Query setParameter(String name, Object value) { + return unwrapped.setParameter(name, value); + } + + public Query setParameter(String name, Date value, TemporalType temporalType) { + return query.setParameter(name, value, temporalType); + } + + public Query setParameter(int position, Object value) { + return unwrapped.setParameter(position, value); + } + + public Query setParameter(int position, Date value, TemporalType temporalType) { + return unwrapped.setParameter(position, value, temporalType); + } + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java index fb13de6cf..d8e9dc38a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java @@ -30,6 +30,7 @@ import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.ResultProcessor; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -51,6 +52,7 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery { private final StoredProcedureAttributes procedureAttributes; private final boolean useNamedParameters; + private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache(); /** * Creates a new {@link StoredProcedureJpaQuery}. @@ -97,7 +99,11 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery { */ @Override protected StoredProcedureQuery doCreateQuery(JpaParametersParameterAccessor accessor) { - return parameterBinder.get().bind(createStoredProcedure(), accessor); + + StoredProcedureQuery storedProcedure = createStoredProcedure(); + QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata("singleton", storedProcedure); + + return parameterBinder.get().bind(storedProcedure, metadata, accessor); } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 482b2ab7f..e22653794 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -105,7 +105,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { /** * Configures the {@link EntityPathResolver} to be used. Defaults to {@link SimpleEntityPathResolver#INSTANCE}. - * + * * @param entityPathResolver must not be {@literal null}. */ public void setEntityPathResolver(EntityPathResolver entityPathResolver) { @@ -252,7 +252,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { /** * Creates a new {@link EclipseLinkProjectionQueryCreationListener} for the given {@link EntityManager}. - * + * * @param em must not be {@literal null}. */ public EclipseLinkProjectionQueryCreationListener(EntityManager em) { @@ -262,7 +262,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { this.metamodel = JpaMetamodel.of(em.getMetamodel()); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.QueryCreationListener#onCreation(org.springframework.data.repository.query.RepositoryQuery) */ diff --git a/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java index cf2bb21b5..9056f9e42 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java @@ -84,7 +84,9 @@ public class NamedOrIndexedQueryParameterSetterUnitTests { temporalType // ); - softly.assertThatThrownBy(() -> setter.setParameter(query, methodArguments, STRICT)) // + softly + .assertThatThrownBy( + () -> setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, STRICT)) // .describedAs("p-type: %s, p-name: %s, p-position: %s, temporal: %s", // parameter.getClass(), // parameter.getName(), // @@ -111,7 +113,9 @@ public class NamedOrIndexedQueryParameterSetterUnitTests { temporalType // ); - softly.assertThatCode(() -> setter.setParameter(query, methodArguments, LENIENT)) // + softly + .assertThatCode( + () -> setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT)) // .describedAs("p-type: %s, p-name: %s, p-position: %s, temporal: %s", // parameter.getClass(), // parameter.getName(), // @@ -142,7 +146,7 @@ public class NamedOrIndexedQueryParameterSetterUnitTests { temporalType // ); - setter.setParameter(query, methodArguments, LENIENT); + setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT); if (temporalType == null) { verify(query).setParameter(eq(11), any(Date.class)); @@ -172,7 +176,7 @@ public class NamedOrIndexedQueryParameterSetterUnitTests { temporalType // ); - setter.setParameter(query, methodArguments, LENIENT); + setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT); if (temporalType == null) { verify(query, never()).setParameter(anyInt(), any(Date.class)); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java index 11032b0e4..cc652a5f8 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java @@ -17,8 +17,9 @@ package org.springframework.data.jpa.repository.query; import static java.util.Collections.*; import static javax.persistence.TemporalType.*; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; import java.lang.reflect.Method; import java.util.Date; @@ -36,6 +37,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.Temporal; @@ -102,8 +104,7 @@ public class ParameterBinderUnitTests { Method validWithSort = SampleRepository.class.getMethod("validWithSort", String.class, Sort.class); Object[] values = { "foo", null }; - ParameterBinderFactory.createBinder(new JpaParameters(validWithSort)).bind(query, - new JpaParametersParameterAccessor(new JpaParameters(validWithSort), values)); + bind(validWithSort, values); verify(query).setParameter(eq(1), eq("foo")); } @@ -113,8 +114,7 @@ public class ParameterBinderUnitTests { Method validWithPageable = SampleRepository.class.getMethod("validWithPageable", String.class, Pageable.class); Object[] values = { "foo", null }; - ParameterBinderFactory.createBinder(new JpaParameters(validWithPageable)).bind(query, - new JpaParametersParameterAccessor(new JpaParameters(validWithPageable), values)); + bind(validWithPageable, values); verify(query).setParameter(eq(1), eq("foo")); } @@ -122,8 +122,7 @@ public class ParameterBinderUnitTests { public void usesIndexedParametersIfNoParamAnnotationPresent() throws Exception { Object[] values = { "foo" }; - ParameterBinderFactory.createBinder(new JpaParameters(useIndexedParameters)).bind(query, - new JpaParametersParameterAccessor(new JpaParameters(useIndexedParameters), values)); + bind(useIndexedParameters, values); verify(query).setParameter(eq(1), any()); } @@ -138,8 +137,7 @@ public class ParameterBinderUnitTests { when(query.getParameters()).thenReturn(singleton(parameter)); Object[] values = { "foo" }; - ParameterBinderFactory.createBinder(new JpaParameters(valid)).bind(query, - new JpaParametersParameterAccessor(new JpaParameters(valid), values)); + bind(valid, values); verify(query).setParameter(eq("username"), any()); } @@ -152,7 +150,7 @@ public class ParameterBinderUnitTests { SampleEmbeddable embeddable = new SampleEmbeddable(); Object[] values = { embeddable }; - ParameterBinderFactory.createBinder(parameters).bind(query, getAccessor(method, values)); + bind(method, parameters, values); verify(query).setParameter(1, embeddable); } @@ -165,7 +163,7 @@ public class ParameterBinderUnitTests { Date date = new Date(); Object[] values = { date }; - ParameterBinderFactory.createBinder(parameters).bind(query, getAccessor(method, values)); + bind(method, parameters, values); verify(query).setParameter(eq(1), eq(date), eq(TemporalType.DATE)); } @@ -178,7 +176,7 @@ public class ParameterBinderUnitTests { Date date = new Date(); Object[] values = { date }; - ParameterBinderFactory.createBinder(parameters).bind(query, getAccessor(method, values)); + bind(method, parameters, values); verify(query).setParameter(eq(1), eq(date), eq(TemporalType.TIMESTAMP)); } @@ -199,7 +197,7 @@ public class ParameterBinderUnitTests { JpaParameters parameters = new JpaParameters(method); Integer[] ids = new Integer[] { 1, 2, 3 }; Object[] values = { ids }; - ParameterBinderFactory.createBinder(parameters).bind(query, getAccessor(method, values)); + bind(method, parameters, values); verify(query).setParameter(eq(1), eq(ids)); } @@ -211,7 +209,7 @@ public class ParameterBinderUnitTests { JpaParameters parameters = new JpaParameters(method); Object[] values = { Optional.of("Foo") }; - ParameterBinderFactory.createBinder(parameters).bind(query, getAccessor(method, values)); + bind(method, parameters, values); verify(query).setParameter(eq(1), eq("Foo")); } @@ -222,12 +220,21 @@ public class ParameterBinderUnitTests { Method method = SampleRepository.class.getMethod("withQuery", String.class, String.class); Object[] values = { "foo", "superfluous" }; - ParameterBinderFactory.createBinder(new JpaParameters(method)).bind(query, getAccessor(method, values)); + bind(method, new JpaParameters(method), values); verify(query).setParameter(eq(1), any()); verify(query, never()).setParameter(eq(2), any()); } + private void bind(Method method, Object[] values) { + bind(method, new JpaParameters(method), values); + } + + private void bind(Method method, JpaParameters parameters, Object[] values) { + ParameterBinderFactory.createBinder(parameters).bind(QueryParameterSetter.BindableQuery.from(query), + getAccessor(method, values), QueryParameterSetter.ErrorHandling.STRICT); + } + private JpaParametersParameterAccessor getAccessor(Method method, Object... values) { return new JpaParametersParameterAccessor(new JpaParameters(method), values); }