DATAJPA-1575 - Introduce QueryMetadataCache.

This commit is contained in:
Mark Paluch
2019-07-23 15:18:23 +02:00
parent 6e2032e7b6
commit 74ff5b3a65
10 changed files with 281 additions and 100 deletions

View File

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

View File

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

View File

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

View File

@@ -69,17 +69,18 @@ public class ParameterBinder {
this.useJpaForPaging = useJpaForPaging;
}
public <T extends Query> T bind(T jpaQuery, JpaParametersParameterAccessor accessor) {
return bind(jpaQuery, accessor, ErrorHandling.STRICT);
public <T extends Query> T bind(T jpaQuery, QueryParameterSetter.QueryMetadata metadata,
JpaParametersParameterAccessor accessor) {
bind(metadata.withQuery(jpaQuery), accessor, ErrorHandling.STRICT);
return jpaQuery;
}
public <T extends Query> 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;
}
}

View File

@@ -214,6 +214,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
private final @Nullable List<ParameterMetadata<?>> expressions;
private final PersistenceProvider persistenceProvider;
private final Map<List<ParameterMetadata<?>>, 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<ParameterMetadata<?>> 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);
}
}
}

View File

@@ -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<JpaParametersParameterAccessor, Object> 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<Date>) 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<Object>) 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<? extends Query> 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<String, QueryMetadata> 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<String, QueryMetadata> 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<Parameter<?>> 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<Parameter<?>> 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<? extends Query> 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 <T> Query setParameter(Parameter<T> param, T value) {
return unwrapped.setParameter(param, value);
}
public Query setParameter(Parameter<Date> 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);
}
}
}

View File

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

View File

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

View File

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

View File

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