DATAJPA-1140 - Separated static analysis from invocation.

All the code identifying how to obtain values for query parameters and how to set the parameters is extracted in ParameterBuilderFactory. The purpose of the (only) ParameterBinder class is reduced to iterate the result of that analysis as a List<QueryParameterSetters> and invoke each element.

In order to benefit from the clean up in the ParameterBinder's clients of ParameterBinder now keep references to them when appropriate. There is only one real ParameterSetter and a NOOP implementation left, which handles all the different ways we need to invoke setParameter. 

Original pull request: #206.
Related issues: https://hibernate.atlassian.net/browse/HHH-11870
This commit is contained in:
Jens Schauder
2017-07-11 11:18:01 +02:00
committed by Oliver Gierke
parent 8dbbe553e1
commit f97928ef47
17 changed files with 572 additions and 619 deletions

View File

@@ -41,6 +41,7 @@ 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;
import org.springframework.data.util.Lazy;
import org.springframework.util.Assert;
/**
@@ -50,6 +51,7 @@ import org.springframework.util.Assert;
* @author Thomas Darimont
* @author Mark Paluch
* @author Christoph Strobl
* @author Jens Schauder
*/
public abstract class AbstractJpaQuery implements RepositoryQuery {
@@ -57,6 +59,8 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
private final EntityManager em;
private final JpaMetamodel metamodel;
Lazy<ParameterBinder> parameterBinder = new Lazy<>(this::createBinder);
/**
* Creates a new {@link AbstractJpaQuery} from the given {@link JpaQueryMethod}.
*
@@ -183,8 +187,8 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
return lockModeType == null ? query : query.setLockMode(lockModeType);
}
protected ParameterBinder createBinder(Object[] values) {
return new ParameterBinder(getQueryMethod().getParameters(), values);
protected ParameterBinder createBinder() {
return ParameterBinderFactory.createParameterBinder(getQueryMethod().getParameters());
}
protected Query createQuery(Object[] values) {

View File

@@ -79,7 +79,9 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
Query query = createJpaQuery(sortedQueryString);
return createBinder(values).bindAndPrepare(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, values);
}
/*
@@ -87,8 +89,9 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#createBinder(java.lang.Object[])
*/
@Override
protected ParameterBinder createBinder(Object[] values) {
return new QueryAwareParameterBinder(getQueryMethod().getParameters(), values, query,
protected ParameterBinder createBinder() {
return ParameterBinderFactory.createQueryAwareParameterBinder(getQueryMethod().getParameters(), query,
evaluationContextProvider, parser);
}
@@ -102,8 +105,9 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
String queryString = countQuery.getQueryString();
EntityManager em = getEntityManager();
return createBinder(values).bind(
getQueryMethod().isNativeQuery() ? em.createNativeQuery(queryString) : em.createQuery(queryString, Long.class));
return parameterBinder.get().bind(
getQueryMethod().isNativeQuery() ? em.createNativeQuery(queryString) : em.createQuery(queryString, Long.class),
values);
}
/**
@@ -123,9 +127,6 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
/**
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
* type.
*
* @param queryString
* @return
*/
protected Query createJpaQuery(String queryString) {
@@ -141,6 +142,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
getMetamodel().isJpaManaged(returnedType.getReturnedType());
return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType())
? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString);
? em.createQuery(queryString, Tuple.class)
: em.createQuery(queryString);
}
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import java.util.Iterator;
import javax.persistence.criteria.CriteriaQuery;
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.QueryParameterSetter.ParameterExpressionQueryParameterSetter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.util.Assert;
/**
* Special {@link ParameterBinder} to bind {@link CriteriaQuery} parameters. parameters.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
* @author Jens Schauder
*/
class CriteriaQueryParameterBinder extends ParameterBinder {
private final Iterator<ParameterMetadata<?>> expressions;
/**
* Creates a new {@link CriteriaQueryParameterBinder} for the given {@link Parameters}, values and some
* {@link javax.persistence.criteria.ParameterExpression}.
*
* @param parameters must not be {@literal null}.
* @param values must not be {@literal null}.
* @param expressions must not be {@literal null}.
*/
CriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, Iterable<ParameterMetadata<?>> expressions) {
super(parameters, values);
Assert.notNull(expressions, "Iterable of ParameterMetadata must not be null!");
this.expressions = expressions.iterator();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.ParameterBinder#bind(javax.persistence.Query, org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter, java.lang.Object, int)
*/
@Override
@SuppressWarnings("unchecked")
protected QueryParameterSetter bind(JpaParameter parameter, Object value, Integer position) {
ParameterMetadata<Object> metadata = (ParameterMetadata<Object>) expressions.next();
if (metadata.isIsNullParameter()) {
return QueryParameterSetter.NOOP;
}
if (parameter.isTemporalParameter()) {
return new ParameterExpressionQueryParameterSetter(metadata.getExpression(), parameter.getTemporalType(), metadata.prepare(value));
}
return new ParameterExpressionQueryParameterSetter(metadata.getExpression(), null, metadata.prepare(value));
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
/**
* {@link org.springframework.data.repository.query.ParameterAccessor} based on an {@link Parameters} instance. It also
* offers access to all the values, not just the bindable ones based on a {@link JpaParameter} instance.
*
* @author Jens Schauder
*/
public class JpaParametersParameterAccessor extends ParametersParameterAccessor {
/**
* Creates a new {@link ParametersParameterAccessor}.
*
* @param parameters must not be {@literal null}.
* @param values must not be {@literal null}.
*/
JpaParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values) {
super(parameters, values);
}
public <T> T getValue(JpaParameter parameter) {
return super.getValue(parameter.getIndex());
}
}

View File

@@ -21,7 +21,6 @@ import javax.persistence.TypedQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryCreationException;
@@ -140,7 +139,7 @@ final class NamedQuery extends AbstractJpaQuery {
protected Query doCreateQuery(Object[] values) {
Query query = getEntityManager().createNamedQuery(queryName);
return createBinder(values).bindAndPrepare(query);
return parameterBinder.get().bindAndPrepare(query, values);
}
/*
@@ -161,6 +160,6 @@ final class NamedQuery extends AbstractJpaQuery {
countQuery = em.createQuery(QueryUtils.createCountQueryFor(queryString, countProjection), Long.class);
}
return createBinder(values).bind(countQuery);
return parameterBinder.get().bind(countQuery, values);
}
}

View File

@@ -15,16 +15,15 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.List;
import javax.persistence.Query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.util.Assert;
import lombok.RequiredArgsConstructor;
/**
* {@link ParameterBinder} is used to bind method parameters to a {@link Query}. This is usually done whenever an
* {@link AbstractJpaQuery} is executed.
@@ -35,113 +34,43 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Jens Schauder
*/
@RequiredArgsConstructor
public class ParameterBinder {
/** Meta information for the method parameters */
private final JpaParameters parameters;
protected final ParameterAccessor accessor;
private final List<QueryParameterSetter> parameterSetters;
/**
* Creates a new {@link ParameterBinder}.
*
* @param parameters must not be {@literal null}.
* @param values values of the parameters passed to the method. Must not be {@literal null}.
*/
public ParameterBinder(JpaParameters parameters, Object[] values) {
public <T extends Query> T bind(T jpaQuery, Object[] values) {
Assert.notNull(parameters, "JpaParameters must not be null!");
Assert.notNull(values, "Values must not be null!");
Assert.isTrue(parameters.getNumberOfParameters() == values.length, "Invalid number of parameters given!");
this.parameters = parameters;
this.accessor = new ParametersParameterAccessor(parameters, values.clone());
}
ParameterBinder(JpaParameters parameters) {
this(parameters, new Object[0]);
}
/**
* Returns the {@link Pageable} of the parameters, if available. Returns {@code null} otherwise.
*/
Pageable getPageable() {
return accessor.getPageable();
}
/**
* Returns the sort instance to be used for query creation. Will use a {@link Sort} parameter if available or the
* {@link Sort} contained in a {@link Pageable} if available. Returns {@code null} if no {@link Sort} can be found.
*/
Sort getSort() {
return accessor.getSort();
}
/**
* Binds the parameters to the given {@link Query}.
*
* @param query must not be {@literal null}.
*/
protected <T extends Query> T bind(T query) {
Assert.notNull(query, "Query must not be null!");
int bindableParameterIndex = 0;
for (JpaParameter parameter : parameters) {
if (parameter.isBindable()) {
Object value = accessor.getBindableValue(bindableParameterIndex);
bind(parameter, value, bindableParameterIndex + 1).setParameter(query);
bindableParameterIndex++;
}
for (QueryParameterSetter setter : parameterSetters) {
setter.setParameter(jpaQuery, values);
}
return query;
}
/**
* Creates {@link QueryParameterSetter} for the given {@link JpaParameter}. This implementation uses the name or index
* of the passed in parameter. This method is intended to be overwritten by subclasses in order to implement
* alternative binding strategies.
*
* @param parameter Method parameter from which to create a {@link QueryParameterSetter}
* @param value The value of the method parameter.
* @param position the index of the query parameter. Note that there is no simple relationg between index of the
* method parameter and the position of the bind parameter due to unbindable parameters.
* @return guaranteed not to be {@literal null}.
*/
protected QueryParameterSetter bind(JpaParameter parameter, Object value, Integer position) {
return QueryParameterSetter.fromJpaParameter(parameter, position, value);
return jpaQuery;
}
/**
* Binds the parameters to the given query and applies special parameter types (e.g. pagination).
*
*
* @param query must not be {@literal null}.
* @param values values of method parameters to be assigned to the query parame
*/
Query bindAndPrepare(Query query) {
Query bindAndPrepare(Query query, Object[] values) {
Assert.notNull(query, "Query must not be null!");
return bindAndPrepare(query, parameters);
}
ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
private Query bindAndPrepare(Query query, Parameters<?, ?> parameters) {
Query result = bind(query, values);
Query result = bind(query);
if (!parameters.hasPageableParameter() || getPageable().isUnpaged()) {
if (!parameters.hasPageableParameter() || accessor.getPageable().isUnpaged()) {
return result;
}
result.setFirstResult((int) getPageable().getOffset());
result.setMaxResults(getPageable().getPageSize());
result.setFirstResult((int) accessor.getPageable().getOffset());
result.setMaxResults(accessor.getPageable().getPageSize());
return result;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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.QueryParameterSetterFactory.BasicQueryParameterSetterFactory;
import org.springframework.data.jpa.repository.query.QueryParameterSetterFactory.CriteriaQueryParameterSetterFactory;
import org.springframework.data.jpa.repository.query.QueryParameterSetterFactory.ExpressionBasedQueryParameterSetterFactory;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Factory for differently configured {@link ParameterBinder}.
*
* @author Jens Schauder
*/
class ParameterBinderFactory {
/**
* create a {@link ParameterBinder} that just matches parameter by name if those are available, or by index/position
* otherwise.
*
* @param parameters method parameters that are available for binding
* @return a {@link ParameterBinder} that can assign values for the method parameters to query parameters of a
* {@link javax.persistence.Query}
*/
static ParameterBinder createParameterBinder(JpaParameters parameters) {
List<QueryParameterSetter> setters = createParameterSetters( //
getParameterBindings(parameters), //
null, //
new BasicQueryParameterSetterFactory(parameters) //
);
return new ParameterBinder(parameters, setters);
}
/**
* create a {@link ParameterBinder} that just matches method parameter to parameters of a
* javax.{@link javax.persistence.criteria.CriteriaQuery}.
*
* @param parameters method parameters that are available for binding
* @return a {@link ParameterBinder} that can assign values for the method parameters to query parameters of a
* {@link javax.persistence.criteria.CriteriaQuery}
*/
static ParameterBinder createCriteriaParameterBinder(JpaParameters parameters,
List<ParameterMetadata<?>> expressions) {
List<QueryParameterSetter> setters = createParameterSetters( //
getParameterBindings(parameters), //
null, //
new CriteriaQueryParameterSetterFactory(parameters, expressions) //
);
return new ParameterBinder(parameters, setters);
}
/**
* create a {@link ParameterBinder} that just matches parameter by name if those are available, or by index/position
* otherwise. The resulting {@link ParameterBinder} can also handle SpEL expressions in the query. Uses the supplied
* query in order to ensure that all query parameters are bound.
*
* @param parameters method parameters that are available for binding
* @return a {@link ParameterBinder} that can assign values for the method parameters to query parameters of a
* {@link javax.persistence.Query} while processing SpEL expressions where applicable.
*/
static ParameterBinder createQueryAwareParameterBinder(JpaParameters parameters, StringQuery query,
EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
List<QueryParameterSetter> parameterSetters = createParameterSetters( //
query.getParameterBindings(), //
query.getQueryString(), //
new ExpressionBasedQueryParameterSetterFactory(evaluationContextProvider, parser, parameters), //
new BasicQueryParameterSetterFactory(parameters) //
);
return new ParameterBinder(parameters, parameterSetters);
}
private static List<ParameterBinding> getParameterBindings(JpaParameters parameters) {
List<ParameterBinding> result = new ArrayList<>();
int bindableParameterIndex = 0;
for (JpaParameter parameter : parameters) {
if (parameter.isBindable()) {
result.add(new ParameterBinding(++bindableParameterIndex));
}
}
return result;
}
private static List<QueryParameterSetter> createParameterSetters(List<ParameterBinding> parameterBindings,
String queryString, QueryParameterSetterFactory... strategies) {
return parameterBindings.stream() //
.map((pb) -> createQueryParameterSetter(pb, strategies, queryString)) //
.collect(Collectors.toList());
}
private static QueryParameterSetter createQueryParameterSetter(ParameterBinding binding,
QueryParameterSetterFactory[] strategies, String queryString) {
for (QueryParameterSetterFactory strategy : strategies) {
QueryParameterSetter candidate = strategy.create(binding, queryString);
if (candidate != null)
return candidate;
}
return QueryParameterSetter.NOOP;
}
}

View File

@@ -40,10 +40,10 @@ import org.springframework.data.repository.query.parser.PartTree;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Jens Schauder
*/
public class PartTreeJpaQuery extends AbstractJpaQuery {
private final Class<?> domainClass;
private final PartTree tree;
private final JpaParameters parameters;
@@ -58,12 +58,12 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
* @param em must not be {@literal null}.
* @param persistenceProvider must not be {@literal null}.
*/
public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em, PersistenceProvider persistenceProvider) {
PartTreeJpaQuery(JpaQueryMethod method, EntityManager em, PersistenceProvider persistenceProvider) {
super(method, em);
this.em = em;
this.domainClass = method.getEntityInformation().getJavaType();
Class<?> domainClass = method.getEntityInformation().getJavaType();
this.tree = new PartTree(method.getName(), domainClass);
this.parameters = method.getParameters();
@@ -117,48 +117,51 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
private class QueryPreparer {
private final CriteriaQuery<?> cachedCriteriaQuery;
private final ParameterBinder cachedParameterBinder;
private final List<ParameterMetadata<?>> expressions;
private final PersistenceProvider persistenceProvider;
public QueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) {
QueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) {
this.persistenceProvider = persistenceProvider;
JpaQueryCreator creator = createCreator(persistenceProvider, Optional.empty());
this.cachedCriteriaQuery = recreateQueries ? null : creator.createQuery();
this.expressions = recreateQueries ? null : creator.getParameterExpressions();
if (recreateQueries) {
this.cachedCriteriaQuery = null;
this.expressions = null;
this.cachedParameterBinder = null;
} else {
this.cachedCriteriaQuery = creator.createQuery();
this.expressions = creator.getParameterExpressions();
this.cachedParameterBinder = getBinder(expressions);
}
}
/**
* Creates a new {@link Query} for the given parameter values.
*
* @param values
* @return
*/
public Query createQuery(Object[] values) {
CriteriaQuery<?> criteriaQuery = cachedCriteriaQuery;
List<ParameterMetadata<?>> expressions = this.expressions;
ParameterBinder parameterBinder = cachedParameterBinder;
ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) {
JpaQueryCreator creator = createCreator(persistenceProvider, Optional.of(accessor));
criteriaQuery = creator.createQuery(getDynamicSort(values));
expressions = creator.getParameterExpressions();
List<ParameterMetadata<?>> expressions = creator.getParameterExpressions();
parameterBinder = getBinder(expressions);
}
TypedQuery<?> jpaQuery = createQuery(criteriaQuery);
return restrictMaxResultsIfNecessary(invokeBinding(getBinder(values, expressions), jpaQuery));
return restrictMaxResultsIfNecessary(invokeBinding(parameterBinder, jpaQuery, values));
}
/**
* Restricts the max results of the given {@link Query} if the current {@code tree} marks this {@code query} as
* limited.
*
* @param query
* @return
*/
private Query restrictMaxResultsIfNecessary(Query query) {
@@ -192,7 +195,6 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
* of some persistence providers (i.e. Hibernate in this case), see DATAJPA-396.
*
* @param criteriaQuery must not be {@literal null}.
* @return
*/
private TypedQuery<?> createQuery(CriteriaQuery<?> criteriaQuery) {
@@ -216,7 +218,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
.orElseGet(() -> new ParameterMetadataProvider(builder, parameters, persistenceProvider));
ResultProcessor processor = getQueryMethod().getResultProcessor();
ReturnedType returnedType = accessor.map(it -> processor.withDynamicProjection(it))//
ReturnedType returnedType = accessor.map(processor::withDynamicProjection)//
.orElse(processor).getReturnedType();
return new JpaQueryCreator(tree, returnedType, builder, provider);
@@ -224,18 +226,14 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
/**
* Invokes parameter binding on the given {@link TypedQuery}.
*
* @param binder
* @param query
* @return
*/
protected Query invokeBinding(ParameterBinder binder, TypedQuery<?> query) {
protected Query invokeBinding(ParameterBinder binder, TypedQuery<?> query, Object[] values) {
return binder.bindAndPrepare(query);
return binder.bindAndPrepare(query, values);
}
private ParameterBinder getBinder(Object[] values, List<ParameterMetadata<?>> expressions) {
return new CriteriaQueryParameterBinder(parameters, values, expressions);
private ParameterBinder getBinder(List<ParameterMetadata<?>> expressions) {
return ParameterBinderFactory.createCriteriaParameterBinder(parameters, expressions);
}
private Sort getDynamicSort(Object[] values) {
@@ -253,7 +251,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
*/
private class CountQueryPreparer extends QueryPreparer {
public CountQueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) {
CountQueryPreparer(PersistenceProvider persistenceProvider, boolean recreateQueries) {
super(persistenceProvider, recreateQueries);
}
@@ -278,12 +276,11 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
/**
* Customizes binding by skipping the pagination.
*
* @see org.springframework.data.jpa.repository.query.PartTreeJpaQuery.QueryPreparer#invokeBinding(org.springframework.data.jpa.repository.query.ParameterBinder,
* javax.persistence.TypedQuery)
* @see QueryPreparer#invokeBinding(ParameterBinder, TypedQuery, Object[])
*/
@Override
protected Query invokeBinding(ParameterBinder binder, javax.persistence.TypedQuery<?> query) {
return binder.bind(query);
protected Query invokeBinding(ParameterBinder binder, TypedQuery<?> query, Object[] values) {
return binder.bind(query, values);
}
}
}

View File

@@ -1,86 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Query;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.Parameters;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
/**
* {@link ParameterBinder} has a {@link StringQuery} and therefor knows which query parameters need values. For binding
* values to a query it iterates the required bindings and tries to find a value for each binding. For finding those
* values it uses a list of strategies.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Jens Schauder
*/
class QueryAwareParameterBinder extends ParameterBinder {
private final StringQuery query;
private final List<QueryParameterSetterStrategy> queryParameterSetterStrategies = new ArrayList<>();
/**
* Creates a new {@link QueryAwareParameterBinder} from the given {@link Parameters}, method arguments and
* {@link StringQuery}.
*
* @param parameters must not be {@literal null}.
* @param values must not be {@literal null}.
* @param query must not be {@literal null}.
*/
QueryAwareParameterBinder(JpaParameters parameters, Object[] values, StringQuery query,
EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
super(parameters, values);
Assert.notNull(query, "StringQuery must not be null!");
this.query = query;
queryParameterSetterStrategies
.add(new QueryParameterSetterStrategy.ExpressionParameterStrategy(evaluationContextProvider, parser, parameters, values.clone()));
queryParameterSetterStrategies.add(new QueryParameterSetterStrategy.BasicParameterStrategy(parameters, accessor));
}
@Override
public <T extends Query> T bind(T jpaQuery) {
for (ParameterBinding binding : query.getParameterBindings()) {
createQueryParameterSetter(jpaQuery, binding).setParameter(jpaQuery);
}
return jpaQuery;
}
private <T extends Query> QueryParameterSetter createQueryParameterSetter(T jpaQuery, ParameterBinding binding) {
for (QueryParameterSetterStrategy strategy : queryParameterSetterStrategies) {
QueryParameterSetter candidate = strategy.create(jpaQuery, binding);
if (candidate != null)
return candidate;
}
return QueryParameterSetter.NOOP;
}
}

View File

@@ -16,12 +16,18 @@
package org.springframework.data.jpa.repository.query;
import java.util.Date;
import java.util.function.Function;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.criteria.ParameterExpression;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import lombok.RequiredArgsConstructor;
import lombok.Value;
/**
* The interface encapsulates the setting of query parameters which might use a significant number of variations of
@@ -32,126 +38,141 @@ import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
*/
interface QueryParameterSetter {
void setParameter(Query query);
void setParameter(Query query, Object[] values);
/** Noop implementation */
QueryParameterSetter NOOP = query -> {};
/**
* Creates a {@link QueryParameterSetter} from a {@link JpaParameter}. Handles named and indexed parameters and
* TemporalType annotations.
*
* @param parameter the method parameter to bind
* @param position position of the bind parameter, might be {@literal null} only if parameters are named.
* @param value the value passed to the method parameter.
*/
static QueryParameterSetter fromJpaParameter(JpaParameter parameter, Integer position, Object value) {
TemporalType temporalType = parameter.isTemporalParameter() ? parameter.getTemporalType() : null;
String name = parameter.isNamedParameter()
? parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O parameter needs to have a name!"))
: null;
return new NamedOrIndexedQueryParameterSetter(name, position, temporalType, value);
}
QueryParameterSetter NOOP = (query, values) -> {};
/**
* Creates a {@link QueryParameterSetter} which ignores {@link IllegalArgumentException}s when calling
* {@literal setParameter}. Useful because certain JPA implementations do not correctly report the presence of
* parameters in a query.
*
* @param name the name of the parameter to bind. Might be {@literal null}.
* @param position position of the bind parameter, might be {@literal null} only if parameters are named.
* @param value the value passed to the method parameter.
* @param valueExtractor function that converts the list of all method parameters to the value used for setting the
* query parameter.
* @param binding the binding of the query parameter to set.
* @return QueryParameterSetter that can set the appropriate query parameter given a list of values for the method
* parameters.
*/
static QueryParameterSetter lenientQueryParameterSetter(String name, Integer position, Object value) {
static QueryParameterSetter createLenient(Function<Object[], Object> valueExtractor, ParameterBinding binding) {
return create(valueExtractor, binding, null, true);
}
return (Query q) -> {
try {
if (name != null) {
q.setParameter(name, value);
} else {
q.setParameter(position, value);
}
} catch (IllegalArgumentException iae) {
/**
* Creates a {@link QueryParameterSetter} which uses information based on the {@link JpaParameter} passed as an
* argument in order to fine tune the way the parameter is set.
*
* @param valueExtractor function that converts the list of all method parameters to the value used for setting the
* query parameter.
* @param binding The binding of the query parameter to set.
* @param parameter The name of this method parameter is used for setting the query parameter and also TemporalType
* annotations on that parameter is used when setting the query parameter.
* @return QueryParameterSetter that can set the appropriate query parameter given a list of values for the method
* parameters.
*/
static QueryParameterSetter create(Function<Object[], Object> valueExtractor, ParameterBinding binding,
JpaParameter parameter) {
return create(valueExtractor, binding, parameter, false);
}
// Since Eclipse doesn't reliably report whether a query has parameters
// we simply try to set the parameters and ignore possible failures.
}
};
static String getName(JpaParameter parameter, ParameterBinding binding) {
if (parameter != null)
return parameter.isNamedParameter()
? parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O parameter needs to have a name!"))
: null;
return binding.getName();
}
/**
* Creates a {@link QueryParameterSetter} from a {@link JpaParameter}. Handles named and indexed parameters,
* TemporalType annotations and might ignore certain exception when requested to do so.
*
* @param valueExtractor extracts the relevant value from an array of method parameter values.
* @param binding the binding of the query parameter to be set.
* @param parameter the method parameter to bind.
* @param lenient when true certain exceptions thrown when setting the query parameters get ignored.
*/
static QueryParameterSetter create(Function<Object[], Object> valueExtractor, ParameterBinding binding,
JpaParameter parameter, boolean lenient) {
TemporalType temporalType = parameter != null && parameter.isTemporalParameter() ? parameter.getTemporalType()
: null;
return new NamedOrIndexedQueryParameterSetter(valueExtractor.andThen(binding::prepare),
createParameter(binding, parameter), temporalType, lenient);
}
static Parameter createParameter(ParameterBinding binding, JpaParameter parameter) {
return new ParameterImpl(parameter, binding);
}
/**
* {@link QueryParameterSetter} for named or indexed parameters that might have a {@link TemporalType} specified.
*/
@Value
class NamedOrIndexedQueryParameterSetter implements QueryParameterSetter {
private final boolean useParameterAccessByName;
private final String name;
private final Integer index;
private final Function<Object[], Object> valueExtractor;
private final Parameter parameter;
private final TemporalType temporalType;
private final Object value;
private final boolean lenient;
/**
* @param name of the parameter, if {@literal null} index based parameter access will be used.
* @param index index of the parameter. Only used when {@literal name) is {@literal null}
* @param temporalType can be {@literal null}
* @param value the value to be set, might be {@literal null}
*/
NamedOrIndexedQueryParameterSetter(String name, Integer index, TemporalType temporalType, Object value) {
@SuppressWarnings("unchecked")
public void setParameter(Query query, Object[] values) {
this.useParameterAccessByName = name != null;
this.name = name;
this.index = index;
this.temporalType = temporalType;
this.value = value;
}
Object value = valueExtractor.apply(values);
public void setParameter(Query query) {
if (temporalType != null) {
if (useParameterAccessByName && QueryUtils.hasNamedParameter(query)) {
query.setParameter(name, (Date) value, temporalType);
try {
if (temporalType != null) {
// one would think we can simply use parameter to identify the parameter we want to set.
// But that does not work with list valued parameters. At least Hibernate tries to bind them by name.
// TODO: move to using setParameter(Parameter, value) when https://hibernate.atlassian.net/browse/HHH-11870 is
// fixed.
if (parameter instanceof ParameterExpression) {
query.setParameter(parameter, (Date) value, temporalType);
} else if (parameter.getName() != null && QueryUtils.hasNamedParameter(query)) {
query.setParameter(parameter.getName(), (Date) value, temporalType);
} else {
query.setParameter(parameter.getPosition(), (Date) value, temporalType);
}
} else {
query.setParameter(index, (Date) value, temporalType);
}
} else {
if (useParameterAccessByName && QueryUtils.hasNamedParameter(query)) {
query.setParameter(name, value);
} else {
query.setParameter(index, value);
if (parameter instanceof ParameterExpression) {
query.setParameter(parameter, value);
} else if (parameter.getName() != null && QueryUtils.hasNamedParameter(query)) {
query.setParameter(parameter.getName(), value);
} else {
query.setParameter(parameter.getPosition(), value);
}
}
} catch (IllegalArgumentException iae) {
if (!lenient) {
throw iae;
}
// Since Eclipse doesn't reliably report whether a query has parameters
// we simply try to set the parameters and ignore possible failures.
// this is relevant for queries with SpEL expressions, where the method parameters don't have to match the
// parameters in the query.
}
}
}
/**
* {@link QueryParameterSetter} identifying parameters by {@link ParameterExpression}
*/
class ParameterExpressionQueryParameterSetter implements QueryParameterSetter {
@Value
@RequiredArgsConstructor
class ParameterImpl implements Parameter {
private final ParameterExpression parameterExpression;
private final TemporalType temporalType;
private final Object value;
private final String name;
private final Integer position;
private final Class parameterType;
ParameterExpressionQueryParameterSetter(ParameterExpression parameterExpression, TemporalType temporalType,
Object value) {
ParameterImpl(JpaParameter parameter, ParameterBinding binding) {
this.temporalType = temporalType;
this.value = value;
this.parameterExpression = parameterExpression;
}
@SuppressWarnings("unchecked")
public void setParameter(Query query) {
if (temporalType != null) {
query.setParameter(parameterExpression, (Date) value, temporalType);
} else {
query.setParameter(parameterExpression, value);
}
this( //
QueryParameterSetter.getName(parameter, binding), //
binding.getPosition(), //
parameter == null ? Object.class : parameter.getType()); //
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import java.util.List;
import java.util.function.Function;
import javax.persistence.Query;
import javax.persistence.TemporalType;
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.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.Parameters;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import lombok.RequiredArgsConstructor;
/**
* Encapsulates different strategies for the creation of a {@link QueryParameterSetter} from a {@link Query} and a
* {@link ParameterBinding}
*
* @author Jens Schauder
* @since 2.0
*/
interface QueryParameterSetterFactory {
QueryParameterSetter create(ParameterBinding binding, String queryString);
/**
* Handles bindings that are SpEL expressions by evaluating the expression to obtain a value.
*
* @author Jens Schauder
*/
@RequiredArgsConstructor
class ExpressionBasedQueryParameterSetterFactory implements QueryParameterSetterFactory {
private final EvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
private final Parameters<?, ?> parameters;
@Override
public QueryParameterSetter create(ParameterBinding binding, String queryString) {
if (binding.isExpression()) {
Expression expr = parseExpressionString(binding.getExpression());
Function<Object[], Object> valueExtractor = vs -> {
EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameters, vs);
return expr.getValue(context, Object.class);
};
return QueryParameterSetter.createLenient(valueExtractor, binding);
}
return null;
}
private Expression parseExpressionString(String expressionString) {
return parser.parseExpression(expressionString);
}
}
/**
* Extracts values for parameter bindings from method parameters. It handles named as well as indexed parameters.
*
* @author Jens Schauder
* @since 2.0
*/
@RequiredArgsConstructor
class BasicQueryParameterSetterFactory implements QueryParameterSetterFactory {
private final JpaParameters parameters;
@Override
public QueryParameterSetter create(ParameterBinding binding, String queryString) {
JpaParameter parameter = QueryUtils.hasNamedParameter(queryString) //
? findParameterForBinding(binding)
: parameters.getBindableParameter(binding.getPosition() - 1);
if (parameter == null) {
return QueryParameterSetter.NOOP;
}
Function<Object[], Object> valueExtractor = values -> {
JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor(parameters, values);
return accessor.getValue(parameter);
};
return QueryParameterSetter.create(valueExtractor, binding, parameter);
}
private JpaParameter findParameterForBinding(ParameterBinding binding) {
for (JpaParameter methodParameterCandidate : parameters.getBindableParameters()) {
if (binding.getName().equals(getName(methodParameterCandidate))) {
return methodParameterCandidate;
}
}
return null;
}
private static String getName(JpaParameter p) {
return p.getName().orElseThrow(() -> new IllegalArgumentException("Parameter needs to be named!"));
}
}
@RequiredArgsConstructor
class CriteriaQueryParameterSetterFactory implements QueryParameterSetterFactory {
private final JpaParameters parameters;
private final List<ParameterMetadata<?>> expressions;
@Override
public QueryParameterSetter create(ParameterBinding binding, String queryString) {
ParameterMetadata<?> metadata = expressions.get(binding.getPosition() - 1);
if (metadata.isIsNullParameter()) {
return QueryParameterSetter.NOOP;
}
JpaParameter parameter = parameters.getBindableParameter(binding.getPosition() - 1);
Function<Object[], Object> valueExtractor = vs -> {
Object value = new JpaParametersParameterAccessor(parameters, vs).getValue(parameter);
return metadata.prepare(value);
};
TemporalType temporalTypeOrNull = parameter.isTemporalParameter() ? parameter.getTemporalType() : null;
return new NamedOrIndexedQueryParameterSetter(valueExtractor, metadata.getExpression(), temporalTypeOrNull,
false);
}
}
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import java.util.function.Predicate;
import javax.persistence.Query;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
/**
* Encapsulates different strategies for the creation of a {@link QueryParameterSetter} from a {@link Query} and a
* {@link ParameterBinding}
*
* @author Jens Schauder
* @since 2.0
*/
interface QueryParameterSetterStrategy {
QueryParameterSetter create(Query jpaQuery, ParameterBinding binding);
/**
* Handles bindings that are SpEL expressions by evalutating the expression to obtain a value.
*
* @author Jens Schauder
*/
class ExpressionParameterStrategy implements QueryParameterSetterStrategy {
private final EvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
private final Parameters<?, ?> parameters;
private final Object[] values;
ExpressionParameterStrategy(EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser,
Parameters<?, ?> parameters, Object[] values) {
this.parameters = parameters;
this.values = values;
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
Assert.notNull(parser, "SpelExpressionParser must not be null!");
this.evaluationContextProvider = evaluationContextProvider;
this.parser = parser;
}
@Override
public QueryParameterSetter create(Query jpaQuery, ParameterBinding binding) {
if (binding.isExpression()) {
Expression expr = parseExpressionString(binding.getExpression());
Object value = evaluateExpression(expr);
return QueryParameterSetter.lenientQueryParameterSetter(binding.getName(), binding.getPosition(),
binding.prepare(value));
}
return null;
}
private Expression parseExpressionString(String expressionString) {
return parser.parseExpression(expressionString);
}
private Object evaluateExpression(Expression expr) {
return expr.getValue(getEvaluationContext(), Object.class);
}
private EvaluationContext getEvaluationContext() {
return evaluationContextProvider.getEvaluationContext(parameters, values);
}
}
/**
* Extracts values for parameter bindings from method parameters. It handles named as well as indexed parameters.
*
* @author Jens Schauder
* @since 2.0
*/
class BasicParameterStrategy implements QueryParameterSetterStrategy {
private final JpaParameters parameters;
private final ParameterAccessor accessor;
BasicParameterStrategy(JpaParameters parameters, ParameterAccessor accessor) {
this.parameters = parameters;
this.accessor = accessor;
}
@Override
public QueryParameterSetter create(Query jpaQuery, ParameterBinding binding) {
JpaParameter parameter = findParameterForBinding( //
QueryUtils.hasNamedParameter(jpaQuery) //
? matchByName(binding) : matchByIndex(binding) //
);
if (parameter == null) {
return QueryParameterSetter.NOOP;
}
Object preparedValue = binding.prepare(accessor.getBindableValue(parameter.getIndex()));
return QueryParameterSetter.fromJpaParameter(parameter, binding.getPosition(), preparedValue);
}
private JpaParameter findParameterForBinding(Predicate<JpaParameter> predicate) {
for (JpaParameter methodParameterCandidate : parameters.getBindableParameters()) {
if (predicate.test(methodParameterCandidate)) {
return methodParameterCandidate;
}
}
return null;
}
private static Predicate<JpaParameter> matchByIndex(ParameterBinding binding) {
return p -> p.getIndex() + 1 == binding.getPosition();
}
private static Predicate<JpaParameter> matchByName(ParameterBinding binding) {
return p -> binding.getName().equals( //
p.getName().orElseThrow( //
() -> new IllegalArgumentException("Parameter needs to be named!") //
));
}
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.util.StringUtils;
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @author Jens Schauder
* @since 1.6
*/
class StoredProcedureJpaQuery extends AbstractJpaQuery {
@@ -47,7 +48,7 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
* @param method must not be {@literal null}
* @param em must not be {@literal null}
*/
public StoredProcedureJpaQuery(JpaQueryMethod method, EntityManager em) {
StoredProcedureJpaQuery(JpaQueryMethod method, EntityManager em) {
super(method, em);
this.procedureAttributes = method.getProcedureAttributes();
@@ -87,7 +88,7 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
*/
@Override
protected StoredProcedureQuery doCreateQuery(Object[] values) {
return createBinder(values).bind(createStoredProcedure());
return parameterBinder.get().bind(createStoredProcedure(), values);
}
/*
@@ -159,7 +160,9 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
}
if (useNamedParameters) {
procedureQuery.registerStoredProcedureParameter(param.getName().orElseThrow(() -> new IllegalArgumentException("Parameter needs to be named!")), param.getType(), ParameterMode.IN);
procedureQuery.registerStoredProcedureParameter(
param.getName().orElseThrow(() -> new IllegalArgumentException("Parameter needs to be named!")),
param.getType(), ParameterMode.IN);
} else {
procedureQuery.registerStoredProcedureParameter(param.getIndex() + 1, param.getType(), ParameterMode.IN);
}

View File

@@ -32,14 +32,11 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Encapsulation of a JPA query String.
* Encapsulation of a JPA query String. Offers access to parameters as bindings. The internal query String is cleaned
* from decorated parameters like {@literal %:lastname%} and the matching bindings take care of applying the decorations
* in the {@link ParameterBinding#prepare(Object)} method. Note that this class also handles replacing SpEL expressions
* with synthetic bind parameters
*
* Offers access to parameters as bindings. The internal query String is cleaned from decorated parameters like {@literal %:lastname%} and the matching bindings take care of applying the decorations in the {@link ParameterBinding#prepare(Object)} method.
*
* Note that this class also handles replacing SpEL expressions with synthetic bind parameters
*
*
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Oliver Wehrens
@@ -48,8 +45,6 @@ import org.springframework.util.StringUtils;
*/
class StringQuery {
private static final String PARAMETER_NAME_MISSING = "Name for parameter binding must not be null or empty! On JDKs < 8, you need to use @Param for named parameters, on JDK 8 or better, be sure to compile with -parameters.";
private final String query;
private final List<ParameterBinding> bindings;
private final String alias;
@@ -60,11 +55,11 @@ class StringQuery {
*
* @param query must not be {@literal null} or empty.
*/
public StringQuery(String query) {
StringQuery(String query) {
Assert.hasText(query, "Query must not be null or empty!");
this.bindings = new ArrayList<StringQuery.ParameterBinding>();
this.bindings = new ArrayList<>();
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
this.bindings);
this.alias = QueryUtils.detectAlias(query);
@@ -167,7 +162,7 @@ class StringQuery {
*
* @author Thomas Darimont
*/
public static enum ParameterBindingParser {
public enum ParameterBindingParser {
INSTANCE;

View File

@@ -715,14 +715,13 @@ public class UserRepositoryTests {
* Ignored until the query declaration is supported by OpenJPA.
*/
@Test
@Ignore
public void executesAnnotatedCollectionMethodCorrectly() {
flushTestUsers();
firstUser.addColleague(thirdUser);
repository.save(firstUser);
List<User> result = null; // repository.findColleaguesFor(firstUser);
List<User> result = repository.findColleaguesFor(firstUser);
assertThat(result).containsOnly(thirdUser);
}
@@ -1579,6 +1578,15 @@ public class UserRepositoryTests {
flushTestUsers();
List<User> users = repository.findUsersByFirstnameAsFakeSpELExpressionAndUserLastnameAsSpELExpression(firstUser.getFirstname(), firstUser);
assertThat(users).containsOnly(firstUser);
}
@Test // DATAJPA-1140
public void shouldFindUsersByFirstnameWithLeadingPageableParameter() {
flushTestUsers();
List<User> users = repository.findUsersByFirstnamePaginated(PageRequest.of(0, 2), firstUser.getFirstname());
assertThat(users).containsOnly(firstUser);
}

View File

@@ -17,8 +17,6 @@ package org.springframework.data.jpa.repository.query;
import static java.util.Collections.*;
import static javax.persistence.TemporalType.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@@ -95,42 +93,13 @@ public class ParameterBinderUnitTests {
User optionalParameter(Optional<String> name);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsToManyParameters() throws Exception {
new ParameterBinder(new JpaParameters(valid), new Object[] { "foo", "bar" });
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullParameters() throws Exception {
new ParameterBinder(new JpaParameters(valid), (Object[]) null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsToLittleParameters() throws SecurityException, NoSuchMethodException {
JpaParameters parameters = new JpaParameters(valid);
new ParameterBinder(parameters);
}
@Test
public void returnsPageableNoneIfNoPageableWasProvided() throws SecurityException, NoSuchMethodException {
Method method = SampleRepository.class.getMethod("validWithPageable", String.class, Pageable.class);
JpaParameters parameters = new JpaParameters(method);
ParameterBinder binder = new ParameterBinder(parameters, new Object[] { "foo", null });
assertThat(binder.getPageable(), is(Pageable.unpaged()));
}
@Test
public void bindWorksWithNullForSort() throws Exception {
Method validWithSort = SampleRepository.class.getMethod("validWithSort", String.class, Sort.class);
new ParameterBinder(new JpaParameters(validWithSort), new Object[] { "foo", null }).bind(query);
Object[] values = { "foo", null };
ParameterBinderFactory.createParameterBinder(new JpaParameters(validWithSort)).bind(query, values);
verify(query).setParameter(eq(1), eq("foo"));
}
@@ -139,18 +108,21 @@ public class ParameterBinderUnitTests {
Method validWithPageable = SampleRepository.class.getMethod("validWithPageable", String.class, Pageable.class);
new ParameterBinder(new JpaParameters(validWithPageable), new Object[] { "foo", null }).bind(query);
Object[] values = { "foo", null };
ParameterBinderFactory.createParameterBinder(new JpaParameters(validWithPageable)).bind(query, values);
verify(query).setParameter(eq(1), eq("foo"));
}
@Test
public void usesIndexedParametersIfNoParamAnnotationPresent() throws Exception {
new ParameterBinder(new JpaParameters(useIndexedParameters), new Object[] { "foo" }).bind(query);
Object[] values = { "foo" };
ParameterBinderFactory.createParameterBinder(new JpaParameters(useIndexedParameters)).bind(query, values);
verify(query).setParameter(eq(1), anyObject());
}
@Test
public void usesParameterNameIfAnnotated() throws Exception {
when(query.setParameter(eq("username"), anyObject())).thenReturn(query);
@@ -159,7 +131,8 @@ public class ParameterBinderUnitTests {
when(parameter.getName()).thenReturn("username");
when(query.getParameters()).thenReturn(singleton(parameter));
new ParameterBinder(new JpaParameters(valid), new Object[] { "foo" }).bind(query);
Object[] values = { "foo" };
ParameterBinderFactory.createParameterBinder(new JpaParameters(valid)).bind(query, values);
verify(query).setParameter(eq("username"), anyObject());
}
@@ -171,20 +144,12 @@ public class ParameterBinderUnitTests {
JpaParameters parameters = new JpaParameters(method);
SampleEmbeddable embeddable = new SampleEmbeddable();
new ParameterBinder(parameters, new Object[] { embeddable }).bind(query);
Object[] values = { embeddable };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
verify(query).setParameter(1, embeddable);
}
@Test
public void bindsSortForIndexedParameters() throws Exception {
Sort sort = Sort.by("name");
ParameterBinder binder = new ParameterBinder(new JpaParameters(indexedParametersWithSort),
new Object[] { "name", sort });
assertThat(binder.getSort(), is(sort));
}
@Test // DATAJPA-107
public void shouldSetTemporalQueryParameterToDate() throws Exception {
@@ -192,7 +157,8 @@ public class ParameterBinderUnitTests {
JpaParameters parameters = new JpaParameters(method);
Date date = new Date();
new ParameterBinder(parameters, new Object[] { date }).bind(query);
Object[] values = { date };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq(date), eq(TemporalType.DATE));
}
@@ -204,7 +170,8 @@ public class ParameterBinderUnitTests {
JpaParameters parameters = new JpaParameters(method);
Date date = new Date();
new ParameterBinder(parameters, new Object[] { date }).bind(query);
Object[] values = { date };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq(date), eq(TemporalType.TIMESTAMP));
}
@@ -215,7 +182,7 @@ public class ParameterBinderUnitTests {
Method method = SampleRepository.class.getMethod("invalidWithTemporalTypeParameter", String.class);
JpaParameters parameters = new JpaParameters(method);
new ParameterBinder(parameters, new Object[] { "foo", "" });
ParameterBinderFactory.createParameterBinder(parameters);
}
@Test // DATAJPA-461
@@ -224,7 +191,8 @@ public class ParameterBinderUnitTests {
Method method = SampleRepository.class.getMethod("validWithVarArgs", Integer[].class);
JpaParameters parameters = new JpaParameters(method);
Integer[] ids = new Integer[] { 1, 2, 3 };
new ParameterBinder(parameters, new Object[] { ids }).bind(query);
Object[] values = { ids };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq(ids));
}
@@ -235,7 +203,8 @@ public class ParameterBinderUnitTests {
Method method = SampleRepository.class.getMethod("optionalParameter", Optional.class);
JpaParameters parameters = new JpaParameters(method);
new ParameterBinder(parameters, new Object[] { Optional.of("Foo") }).bind(query);
Object[] values = { Optional.of("Foo") };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq("Foo"));
}

View File

@@ -228,11 +228,8 @@ public interface UserRepository
// DATAJPA-132
List<User> findByActiveFalse();
/**
* Commented out until OpenJPA supports this.
*/
// @Query("select u.colleagues from User u where u = ?1")
// List<User> findColleaguesFor(User user);
@Query("select u.colleagues from User u where u = ?1")
List<User> findColleaguesFor(User user);
// DATAJPA-188
List<User> findByCreatedAtBefore(Date date);
@@ -430,22 +427,30 @@ public interface UserRepository
value = "select * from (select rownum() as RN, u.* from SD_User u) where RN between ?#{ #pageable.offset -1} and ?#{#pageable.offset + #pageable.pageSize}",
countQuery = "select count(u.id) from SD_User u", nativeQuery = true)
Page<User> findUsersInNativeQueryWithPagination(Pageable pageable);
// DATAJPA-1140
@Query("select u from User u where u.firstname =:#{#user.firstname} and u.lastname =:lastname")
List<User> findUsersByUserFirstnameAsSpELExpressionAndLastnameAsString(@Param("user") User user, @Param("lastname") String lastname);
List<User> findUsersByUserFirstnameAsSpELExpressionAndLastnameAsString(@Param("user") User user,
@Param("lastname") String lastname);
// DATAJPA-1140
@Query("select u from User u where u.firstname =:firstname and u.lastname =:#{#user.lastname}")
List<User> findUsersByFirstnameAsStringAndUserLastnameAsSpELExpression(@Param("firstname") String firstname, @Param("user") User user);
List<User> findUsersByFirstnameAsStringAndUserLastnameAsSpELExpression(@Param("firstname") String firstname,
@Param("user") User user);
// DATAJPA-1140
@Query("select u from User u where u.firstname =:#{#user.firstname} and u.lastname =:#{#lastname}")
List<User> findUsersByUserFirstnameAsSpELExpressionAndLastnameAsFakeSpELExpression(@Param("user") User user, @Param("lastname") String lastname);
List<User> findUsersByUserFirstnameAsSpELExpressionAndLastnameAsFakeSpELExpression(@Param("user") User user,
@Param("lastname") String lastname);
// DATAJPA-1140
@Query("select u from User u where u.firstname =:#{#firstname} and u.lastname =:#{#user.lastname}")
List<User> findUsersByFirstnameAsFakeSpELExpressionAndUserLastnameAsSpELExpression(@Param("firstname") String firstname, @Param("user") User user);
List<User> findUsersByFirstnameAsFakeSpELExpressionAndUserLastnameAsSpELExpression(
@Param("firstname") String firstname, @Param("user") User user);
// DATAJPA-1140
@Query("select u from User u where u.firstname =:firstname")
List<User> findUsersByFirstnamePaginated(Pageable page, @Param("firstname") String firstname);
// DATAJPA-629
@Query("select u from #{#entityName} u where u.firstname = ?#{[0]} and u.lastname = ?#{[1]}")