DATAJPA-1140 - Allow mixing of SpEL expression and normal query parameters.

Restructured the complete parameter binding. There are now two principle ways to perform the binding:

- if no query is known to the binder the method parameters get iterated and assigned to query parameters.
This is implemented by the bind method of the ParameterBinder.
- if a query is known all the bindings, i.e the query parameters are iterated and matching values are searched.
This is implemented in the QueryAwareParameterBinder.

For finding a suitable value there exists various QueryParameterSetterStrategies. The code calling the various Query.setParameter(…) overloads is extracted and gathered in QueryParameterSetter.

Original pull request: #206.
This commit is contained in:
Jens Schauder
2017-07-05 14:28:26 +02:00
committed by Oliver Gierke
parent 37aabff481
commit 8dbbe553e1
10 changed files with 449 additions and 359 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2016 the original author or authors.
* Copyright 2008-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.
@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Jens Schauder
*/
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
@@ -87,7 +88,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
*/
@Override
protected ParameterBinder createBinder(Object[] values) {
return new SpelExpressionStringQueryParameterBinder(getQueryMethod().getParameters(), values, query,
return new QueryAwareParameterBinder(getQueryMethod().getParameters(), values, query,
evaluationContextProvider, parser);
}

View File

@@ -15,15 +15,13 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.Date;
import java.util.Iterator;
import javax.persistence.Parameter;
import javax.persistence.Query;
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;
@@ -33,6 +31,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
* @author Jens Schauder
*/
class CriteriaQueryParameterBinder extends ParameterBinder {
@@ -60,19 +59,18 @@ class CriteriaQueryParameterBinder extends ParameterBinder {
*/
@Override
@SuppressWarnings("unchecked")
protected void bind(Query query, JpaParameter parameter, Object value, int position) {
protected QueryParameterSetter bind(JpaParameter parameter, Object value, Integer position) {
ParameterMetadata<Object> metadata = (ParameterMetadata<Object>) expressions.next();
if (metadata.isIsNullParameter()) {
return;
return QueryParameterSetter.NOOP;
}
if (parameter.isTemporalParameter()) {
query.setParameter((Parameter<Date>) (Object) metadata.getExpression(), (Date) metadata.prepare(value),
parameter.getTemporalType());
} else {
query.setParameter(metadata.getExpression(), metadata.prepare(value));
return new ParameterExpressionQueryParameterSetter(metadata.getExpression(), parameter.getTemporalType(), metadata.prepare(value));
}
return new ParameterExpressionQueryParameterSetter(metadata.getExpression(), null, metadata.prepare(value));
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.Date;
import javax.persistence.Query;
import org.springframework.data.domain.Pageable;
@@ -35,18 +33,19 @@ import org.springframework.util.Assert;
* @author Thomas Darimont
* @author Mark Paluch
* @author Christoph Strobl
* @author Jens Schauder
*/
public class ParameterBinder {
/** Meta information for the method parameters */
private final JpaParameters parameters;
private final ParameterAccessor accessor;
private final Object[] values;
protected final ParameterAccessor accessor;
/**
* Creates a new {@link ParameterBinder}.
*
* @param parameters must not be {@literal null}.
* @param values 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) {
@@ -56,8 +55,7 @@ public class ParameterBinder {
Assert.isTrue(parameters.getNumberOfParameters() == values.length, "Invalid number of parameters given!");
this.parameters = parameters;
this.values = values.clone();
this.accessor = new ParametersParameterAccessor(parameters, this.values);
this.accessor = new ParametersParameterAccessor(parameters, values.clone());
}
ParameterBinder(JpaParameters parameters) {
@@ -66,20 +64,16 @@ public class ParameterBinder {
/**
* Returns the {@link Pageable} of the parameters, if available. Returns {@code null} otherwise.
*
* @return
*/
public Pageable getPageable() {
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.
*
* @return
*/
public Sort getSort() {
Sort getSort() {
return accessor.getSort();
}
@@ -87,21 +81,21 @@ public class ParameterBinder {
* Binds the parameters to the given {@link Query}.
*
* @param query must not be {@literal null}.
* @return
*/
public <T extends Query> T bind(T query) {
protected <T extends Query> T bind(T query) {
Assert.notNull(query, "Query must not be null!");
int bindableParameterIndex = 0;
int queryParameterPosition = 1;
for (JpaParameter parameter : parameters) {
if (canBindParameter(parameter)) {
if (parameter.isBindable()) {
Object value = accessor.getBindableValue(bindableParameterIndex);
bind(query, parameter, value, queryParameterPosition++);
bind(parameter, value, bindableParameterIndex + 1).setParameter(query);
bindableParameterIndex++;
}
}
@@ -110,62 +104,33 @@ public class ParameterBinder {
}
/**
* Returns {@literal true} if the given parameter can be bound.
*
* @param parameter
* @return
* 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 boolean canBindParameter(JpaParameter parameter) {
return parameter.isBindable();
}
protected QueryParameterSetter bind(JpaParameter parameter, Object value, Integer position) {
/**
* Perform the actual query parameter binding.
*
* @param query
* @param parameter
* @param value
* @param position
*/
protected void bind(Query query, JpaParameter parameter, Object value, int position) {
if (parameter.isTemporalParameter()) {
if (hasNamedParameter(query) && parameter.isNamedParameter()) {
query.setParameter(
parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O paraneter needs to have a name!")),
(Date) value, parameter.getTemporalType());
} else {
query.setParameter(position, (Date) value, parameter.getTemporalType());
}
return;
}
if (hasNamedParameter(query) && parameter.isNamedParameter()) {
query.setParameter(
parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O paraneter needs to have a name!")),
value);
} else {
query.setParameter(position, value);
}
return QueryParameterSetter.fromJpaParameter(parameter, position, value);
}
/**
* Binds the parameters to the given query and applies special parameter types (e.g. pagination).
*
* @param query must not be {@literal null}.
* @return
*/
public Query bindAndPrepare(Query query) {
Query bindAndPrepare(Query query) {
Assert.notNull(query, "Query must not be null!");
return bindAndPrepare(query, parameters);
}
boolean hasNamedParameter(Query query) {
return QueryUtils.hasNamedParameter(query);
}
private Query bindAndPrepare(Query query, Parameters<?, ?> parameters) {
Query result = bind(query);
@@ -179,17 +144,4 @@ public class ParameterBinder {
return result;
}
/**
* Returns the parameters.
*
* @return
*/
JpaParameters getParameters() {
return parameters;
}
protected Object[] getValues() {
return values;
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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

@@ -0,0 +1,157 @@
/*
* 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.Date;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.criteria.ParameterExpression;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
/**
* The interface encapsulates the setting of query parameters which might use a significant number of variations of
* {@literal Query.setParameter}.
*
* @author Jens Schauder
* @since 2.0
*/
interface QueryParameterSetter {
void setParameter(Query query);
/** 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);
}
/**
* 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.
*/
static QueryParameterSetter lenientQueryParameterSetter(String name, Integer position, Object value) {
return (Query q) -> {
try {
if (name != null) {
q.setParameter(name, value);
} else {
q.setParameter(position, value);
}
} catch (IllegalArgumentException iae) {
// Since Eclipse doesn't reliably report whether a query has parameters
// we simply try to set the parameters and ignore possible failures.
}
};
}
/**
* {@link QueryParameterSetter} for named or indexed parameters that might have a {@link TemporalType} specified.
*/
class NamedOrIndexedQueryParameterSetter implements QueryParameterSetter {
private final boolean useParameterAccessByName;
private final String name;
private final Integer index;
private final TemporalType temporalType;
private final Object value;
/**
* @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) {
this.useParameterAccessByName = name != null;
this.name = name;
this.index = index;
this.temporalType = temporalType;
this.value = value;
}
public void setParameter(Query query) {
if (temporalType != null) {
if (useParameterAccessByName && QueryUtils.hasNamedParameter(query)) {
query.setParameter(name, (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);
}
}
}
}
/**
* {@link QueryParameterSetter} identifying parameters by {@link ParameterExpression}
*/
class ParameterExpressionQueryParameterSetter implements QueryParameterSetter {
private final ParameterExpression parameterExpression;
private final TemporalType temporalType;
private final Object value;
ParameterExpressionQueryParameterSetter(ParameterExpression parameterExpression, TemporalType temporalType,
Object value) {
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);
}
}
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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

@@ -1,171 +0,0 @@
/*
* Copyright 2014 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 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.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
/**
* A {@link StringQueryParameterBinder} that is able to bind synthetic query parameters.
*
* @author Thomas Darimont
*/
class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinder {
private final StringQuery query;
private final EvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
/**
* Creates a new {@link SpelExpressionStringQueryParameterBinder}.
*
* @param parameters must not be {@literal null}
* @param values must not be {@literal null}
* @param query must not be {@literal null}
* @param evaluationContextProvider must not be {@literal null}
* @param parser must not be {@literal null}
*/
public SpelExpressionStringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query,
EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
super(parameters, values, query);
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
Assert.notNull(parser, "SpelExpressionParser must not be null!");
this.evaluationContextProvider = evaluationContextProvider;
this.query = query;
this.parser = parser;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.ParameterBinder#bind(javax.persistence.Query)
*/
@Override
public <T extends Query> T bind(T jpaQuery) {
return potentiallyBindExpressionParameters(super.bind(jpaQuery));
}
/**
* @param jpaQuery must not be {@literal null}
* @return
*/
private <T extends Query> T potentiallyBindExpressionParameters(T jpaQuery) {
if (isJpaParameterInformationReliable(jpaQuery) && jpaQuery.getParameters().isEmpty()) {
// We can rely on the fact there are no parameters in the given query.
return jpaQuery;
}
for (ParameterBinding binding : query.getParameterBindings()) {
if (binding.isExpression()) {
Expression expr = parseExpressionString(binding.getExpression());
Object value = evaluateExpression(expr);
try {
if (binding.getName() != null) {
jpaQuery.setParameter(binding.getName(), binding.prepare(value));
} else {
jpaQuery.setParameter(binding.getPosition(), binding.prepare(value));
}
} catch (IllegalArgumentException iae) {
// Since Eclipse doesn't reliably report whether a query has parameters
// we simply try to set the parameters and ignore possible failures.
}
}
}
return jpaQuery;
}
private <T extends Query> boolean isJpaParameterInformationReliable(T jpaQuery) {
String className = jpaQuery.getClass().getName();
return className.startsWith("org.apache.openjpa") || className.startsWith("org.hibernate");
}
/**
* Parses the given {@code expressionString} into a SpEL {@link Expression}.
*
* @param expressionString
* @return
*/
private Expression parseExpressionString(String expressionString) {
return parser.parseExpression(expressionString);
}
/**
* Evaluates the given SpEL {@link Expression}.
*
* @param expr
* @return
*/
private Object evaluateExpression(Expression expr) {
return expr.getValue(getEvaluationContext(), Object.class);
}
/**
* Returns the {@link StandardEvaluationContext} to use for evaluation.
*
* @return
*/
private EvaluationContext getEvaluationContext() {
return evaluationContextProvider.getEvaluationContext(getParameters(), getValues());
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.ParameterBinder#canBindParameter(org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter)
*/
@Override
protected boolean canBindParameter(JpaParameter parameter) {
List<ParameterBinding> parameterBindings = query.getParameterBindings();
// if no parameter bindings are present, we simply rely on the check in super.
if (parameterBindings.isEmpty()) {
return super.canBindParameter(parameter);
}
// otherwise determine whether there are any non expression parameters left to be bound.
int expressionParameterCount = 0;
for (ParameterBinding binding : parameterBindings) {
if (binding.isExpression()) {
expressionParameterCount++;
}
}
boolean allParametersAreUsedInExpressions = parameterBindings.size() - expressionParameterCount == 0;
// if all parameters are used in expressions, then we can skip their bindings now, since they'll get bound later.
return !allParametersAreUsedInExpressions && super.canBindParameter(parameter);
}
}

View File

@@ -32,12 +32,19 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Encapsulation of a String JPA query.
* 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
*
*
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Oliver Wehrens
* @author Mark Paluch
* @author Jens Schauder
*/
class StringQuery {

View File

@@ -1,97 +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 javax.persistence.Query;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.util.Assert;
/**
* {@link ParameterBinder} that takes {@link LikeParameterBinding}s encapsulated in a {@link StringQuery} into account.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class StringQueryParameterBinder extends ParameterBinder {
private final StringQuery query;
/**
* Creates a new {@link StringQueryParameterBinder} 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}.
*/
public StringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query) {
super(parameters, values);
Assert.notNull(query, "StringQuery must not be null!");
this.query = query;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.ParameterBinder#bind(javax.persistence.Query, org.springframework.data.repository.query.Parameter, java.lang.Object, int)
*/
@Override
protected void bind(Query jpaQuery, JpaParameter methodParameter, Object value, int position) {
ParameterBinding binding = getBindingFor(jpaQuery, position, methodParameter);
super.bind(jpaQuery, methodParameter, binding.prepare(value), position);
}
/**
* Finds the {@link LikeParameterBinding} to be applied before binding a parameter value to the query.
*
* @param jpaQuery must not be {@literal null}.
* @param position
* @param parameter must not be {@literal null}.
* @return the {@link ParameterBinding} for the given parameters or {@literal null} if none available.
*/
private ParameterBinding getBindingFor(Query jpaQuery, int position, Parameter parameter) {
Assert.notNull(jpaQuery, "Query must not be null!");
Assert.notNull(parameter, "Parameter must not be null!");
if (hasNamedParameter(jpaQuery)) {
return query.getBindingFor(
parameter.getName().orElseThrow(() -> new IllegalArgumentException("Parameter needs to be named!")));
}
try {
jpaQuery.getParameter(position);
return query.getBindingFor(position);
} catch (IllegalArgumentException o_O) {
// We should actually reject parameters unavailable, but as EclipseLink doesn't implement ….getParameter(int) for
// native queries correctly we need to fall back to an indexed parameter
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=427892
return new ParameterBinding(position);
}
}
}

View File

@@ -15,10 +15,12 @@
*/
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.*;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@@ -27,6 +29,7 @@ import java.util.List;
import java.util.Optional;
import javax.persistence.Embeddable;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;
@@ -45,6 +48,7 @@ import org.springframework.data.repository.query.Param;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Jens Schauder
*/
@RunWith(MockitoJUnitRunner.class)
public class ParameterBinderUnitTests {
@@ -150,14 +154,13 @@ public class ParameterBinderUnitTests {
public void usesParameterNameIfAnnotated() throws Exception {
when(query.setParameter(eq("username"), anyObject())).thenReturn(query);
new ParameterBinder(new JpaParameters(valid), new Object[] { "foo" }) {
@Override
boolean hasNamedParameter(Query query) {
Parameter parameter = mock(Parameter.class);
when(parameter.getName()).thenReturn("username");
when(query.getParameters()).thenReturn(singleton(parameter));
new ParameterBinder(new JpaParameters(valid), new Object[] { "foo" }).bind(query);
return true;
}
}.bind(query);
verify(query).setParameter(eq("username"), anyObject());
}