DATAJPA-1140 - Polishing.

Moved factory methods of QueryParameterSetter into QueryParameterSetterFactory. Introduced factory methods for the factory implementations as well, so that clients don't have to know about the implementation classes. Turned QueryParameterSetterFactory into an abstract class to be able to use private methods. Extracted value extractor functions into method calls.

Some minor (factory) method renames to avoid duplicating context too much. Delomboked source code to prevent compiler problems between Lombok and AspectJ.

Original pull request: #206.
This commit is contained in:
Oliver Gierke
2017-07-18 10:26:22 +02:00
parent d15139f962
commit 6bedbabc51
11 changed files with 389 additions and 230 deletions

View File

@@ -188,7 +188,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
}
protected ParameterBinder createBinder() {
return ParameterBinderFactory.createParameterBinder(getQueryMethod().getParameters());
return ParameterBinderFactory.createBinder(getQueryMethod().getParameters());
}
protected Query createQuery(Object[] values) {

View File

@@ -91,8 +91,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
@Override
protected ParameterBinder createBinder() {
return ParameterBinderFactory.createQueryAwareParameterBinder(getQueryMethod().getParameters(), query,
evaluationContextProvider, parser);
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
parser, evaluationContextProvider);
}
/*

View File

@@ -16,6 +16,7 @@
package org.springframework.data.jpa.repository.query;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -37,7 +38,7 @@ public class JpaParametersParameterAccessor extends ParametersParameterAccessor
super(parameters, values);
}
public <T> T getValue(JpaParameter parameter) {
public <T> T getValue(Parameter parameter) {
return super.getValue(parameter.getIndex());
}
}

View File

@@ -15,15 +15,11 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.List;
import javax.persistence.Query;
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.
@@ -34,17 +30,29 @@ import lombok.RequiredArgsConstructor;
* @author Christoph Strobl
* @author Jens Schauder
*/
@RequiredArgsConstructor
public class ParameterBinder {
private final JpaParameters parameters;
private final List<QueryParameterSetter> parameterSetters;
private final Iterable<QueryParameterSetter> parameterSetters;
/**
* Creates a new {@link ParameterBinder} for the given {@link JpaParameters} and {@link QueryParameterSetter}s.
*
* @param parameters must not be {@literal null}.
* @param parameterSetters must not be {@literal null}.
*/
public ParameterBinder(JpaParameters parameters, Iterable<QueryParameterSetter> parameterSetters) {
Assert.notNull(parameters, "JpaParameters must not be null!");
Assert.notNull(parameterSetters, "Parameter setters must not be null!");
this.parameters = parameters;
this.parameterSetters = parameterSetters;
}
public <T extends Query> T bind(T jpaQuery, Object[] values) {
for (QueryParameterSetter setter : parameterSetters) {
setter.setParameter(jpaQuery, values);
}
parameterSetters.forEach(it -> it.setParameter(jpaQuery, values));
return jpaQuery;
}
@@ -72,5 +80,4 @@ public class ParameterBinder {
return result;
}
}

View File

@@ -16,87 +16,94 @@
package org.springframework.data.jpa.repository.query;
import java.util.ArrayList;
import java.util.Arrays;
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.data.util.StreamUtils;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
/**
* Factory for differently configured {@link ParameterBinder}.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
class ParameterBinderFactory {
/**
* create a {@link ParameterBinder} that just matches parameter by name if those are available, or by index/position
* 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
* @param parameters method parameters that are available for binding, must not be {@literal null}.
* @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) {
static ParameterBinder createBinder(JpaParameters parameters) {
List<QueryParameterSetter> setters = createParameterSetters( //
getParameterBindings(parameters), //
null, //
new BasicQueryParameterSetterFactory(parameters) //
);
Assert.notNull(parameters, "JpaParameters must not be null!");
return new ParameterBinder(parameters, setters);
QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.basic(parameters);
List<ParameterBinding> bindings = getBindings(parameters);
return new ParameterBinder(parameters, createSetters(bindings, setterFactory));
}
/**
* create a {@link ParameterBinder} that just matches method parameter to parameters of a
* javax.{@link javax.persistence.criteria.CriteriaQuery}.
* Creates a {@link ParameterBinder} that just matches method parameter to parameters of a
* {@link javax.persistence.criteria.CriteriaQuery}.
*
* @param parameters method parameters that are available for binding
* @param parameters method parameters that are available for binding, must not be {@literal null}.
* @param metadata must not be {@literal null}.
* @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) {
static ParameterBinder createCriteriaBinder(JpaParameters parameters, List<ParameterMetadata<?>> metadata) {
List<QueryParameterSetter> setters = createParameterSetters( //
getParameterBindings(parameters), //
null, //
new CriteriaQueryParameterSetterFactory(parameters, expressions) //
);
Assert.notNull(parameters, "JpaParameters must not be null!");
Assert.notNull(metadata, "Parameter metadata must not be null!");
return new ParameterBinder(parameters, setters);
QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.forCriteriaQuery(parameters, metadata);
List<ParameterBinding> bindings = getBindings(parameters);
return new ParameterBinder(parameters, createSetters(bindings, setterFactory));
}
/**
* create a {@link ParameterBinder} that just matches parameter by name if those are available, or by index/position
* Creates 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
* @param parameters method parameters that are available for binding, must not be {@literal null}.
* @param query the {@link StringQuery} the binders shall be created for, must not be {@literal null}.
* @param parser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @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) {
static ParameterBinder createQueryAwareBinder(JpaParameters parameters, StringQuery query,
SpelExpressionParser parser, EvaluationContextProvider evaluationContextProvider) {
List<QueryParameterSetter> parameterSetters = createParameterSetters( //
query.getParameterBindings(), //
query.getQueryString(), //
new ExpressionBasedQueryParameterSetterFactory(evaluationContextProvider, parser, parameters), //
new BasicQueryParameterSetterFactory(parameters) //
);
Assert.notNull(parameters, "JpaParameters must not be null!");
Assert.notNull(query, "StringQuery must not be null!");
Assert.notNull(parser, "SpelExpressionParser must not be null!");
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
return new ParameterBinder(parameters, parameterSetters);
List<ParameterBinding> bindings = query.getParameterBindings();
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
evaluationContextProvider, parameters);
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
return new ParameterBinder(parameters,
createSetters(query.getQueryString(), bindings, expressionSetterFactory, basicSetterFactory));
}
private static List<ParameterBinding> getParameterBindings(JpaParameters parameters) {
private static List<ParameterBinding> getBindings(JpaParameters parameters) {
List<ParameterBinding> result = new ArrayList<>();
int bindableParameterIndex = 0;
@@ -111,24 +118,26 @@ class ParameterBinderFactory {
return result;
}
private static List<QueryParameterSetter> createParameterSetters(List<ParameterBinding> parameterBindings,
String queryString, QueryParameterSetterFactory... strategies) {
private static Iterable<QueryParameterSetter> createSetters(List<ParameterBinding> parameterBindings,
QueryParameterSetterFactory... factories) {
return createSetters(null, parameterBindings, factories);
}
private static Iterable<QueryParameterSetter> createSetters(String queryString,
List<ParameterBinding> parameterBindings, QueryParameterSetterFactory... strategies) {
return parameterBindings.stream() //
.map((pb) -> createQueryParameterSetter(pb, strategies, queryString)) //
.collect(Collectors.toList());
.map(it -> createQueryParameterSetter(it, strategies, queryString)) //
.collect(StreamUtils.toUnmodifiableList());
}
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;
return Arrays.stream(strategies)//
.map(it -> it.create(binding, queryString))//
.filter(it -> it != null)//
.findFirst()//
.orElse(QueryParameterSetter.NOOP);
}
}

View File

@@ -233,7 +233,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
}
private ParameterBinder getBinder(List<ParameterMetadata<?>> expressions) {
return ParameterBinderFactory.createCriteriaParameterBinder(parameters, expressions);
return ParameterBinderFactory.createCriteriaBinder(parameters, expressions);
}
private Sort getDynamicSort(Object[] values) {

View File

@@ -23,11 +23,7 @@ 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;
import org.springframework.util.Assert;
/**
* The interface encapsulates the setting of query parameters which might use a significant number of variations of
@@ -43,136 +39,80 @@ interface QueryParameterSetter {
/** Noop implementation */
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 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 createLenient(Function<Object[], Object> valueExtractor, ParameterBinding binding) {
return create(valueExtractor, binding, null, true);
}
/**
* 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);
}
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 Function<Object[], Object> valueExtractor;
private final Parameter parameter;
private final Parameter<?> parameter;
private final TemporalType temporalType;
private final boolean lenient;
/**
* @param valueExtractor must not be {@literal null}.
* @param parameter must not be {@literal null}.
* @param temporalType must not be {@literal null}.
* @param lenient must not be {@literal null}.
*/
NamedOrIndexedQueryParameterSetter(Function<Object[], Object> valueExtractor, Parameter<?> parameter,
TemporalType temporalType, boolean lenient) {
Assert.notNull(valueExtractor, "ValueExtractor must not be null!");
this.valueExtractor = valueExtractor;
this.parameter = parameter;
this.temporalType = temporalType;
this.lenient = lenient;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.QueryParameterSetter#setParameter(javax.persistence.Query, java.lang.Object[])
*/
@SuppressWarnings("unchecked")
public void setParameter(Query query, Object[] values) {
Object value = valueExtractor.apply(values);
try {
if (temporalType != null) {
// one would think we can simply use parameter to identify the parameter we want to set.
// 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);
query.setParameter((Parameter<Date>) 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 {
if (parameter instanceof ParameterExpression) {
query.setParameter(parameter, value);
query.setParameter((Parameter<Object>) parameter, value);
} else if (parameter.getName() != null && QueryUtils.hasNamedParameter(query)) {
query.setParameter(parameter.getName(), value);
} else {
query.setParameter(parameter.getPosition(), value);
}
}
} catch (IllegalArgumentException iae) {
} catch (IllegalArgumentException o_O) {
if (!lenient) {
throw iae;
throw o_O;
}
// Since Eclipse doesn't reliably report whether a query has parameters
// Since EclipseLink doesn't reliably report whether a query has parameters
// we simply try to set the parameters and ignore possible failures.
// this is relevant for queries with SpEL expressions, where the method parameters don't have to match the
// parameters in the query.
}
}
}
@Value
@RequiredArgsConstructor
class ParameterImpl implements Parameter {
private final String name;
private final Integer position;
private final Class parameterType;
ParameterImpl(JpaParameter parameter, ParameterBinding binding) {
this( //
QueryParameterSetter.getName(parameter, binding), //
binding.getPosition(), //
parameter == null ? Object.class : parameter.getType()); //
}
}
}

View File

@@ -26,56 +26,151 @@ import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.P
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.Parameter;
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;
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
* @author Oliver Gierke
* @since 2.0
*/
interface QueryParameterSetterFactory {
abstract class QueryParameterSetterFactory {
QueryParameterSetter create(ParameterBinding binding, String queryString);
abstract QueryParameterSetter create(ParameterBinding binding, String queryString);
/**
* Creates a new {@link QueryParameterSetterFactory} for the given {@link JpaParameters}.
*
* @param parameters must not be {@literal null}.
* @return
*/
public static QueryParameterSetterFactory basic(JpaParameters parameters) {
Assert.notNull(parameters, "JpaParameters must not be null!");
return new BasicQueryParameterSetterFactory(parameters);
}
/**
* Creates a new {@link QueryParameterSetterFactory} using the given {@link JpaParameters} and
* {@link ParameterMetadata}.
*
* @param parameters must not be {@literal null}.
* @param metadata must not be {@literal null}.
* @return
*/
public static QueryParameterSetterFactory forCriteriaQuery(JpaParameters parameters,
List<ParameterMetadata<?>> metadata) {
Assert.notNull(parameters, "JpaParameters must not be null!");
Assert.notNull(metadata, "ParameterMetadata must not be null!");
return new CriteriaQueryParameterSetterFactory(parameters, metadata);
}
/**
* Creates a new {@link QueryParameterSetterFactory} for the given {@link SpelExpressionParser},
* {@link EvaluationContextProvider} and {@link Parameters}.
*
* @param parser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @param parameters must not be {@literal null}.
* @return
*/
public static QueryParameterSetterFactory parsing(SpelExpressionParser parser,
EvaluationContextProvider evaluationContextProvider, Parameters<?, ?> parameters) {
Assert.notNull(parser, "SpelExpressionParser must not be null!");
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
Assert.notNull(parameters, "Parameters must not be null!");
return new ExpressionBasedQueryParameterSetterFactory(parser, evaluationContextProvider, parameters);
}
/**
* 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.
*/
private static QueryParameterSetter createSetter(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),
ParameterImpl.of(parameter, binding), temporalType, lenient);
}
/**
* Handles bindings that are SpEL expressions by evaluating the expression to obtain a value.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
@RequiredArgsConstructor
class ExpressionBasedQueryParameterSetterFactory implements QueryParameterSetterFactory {
private static class ExpressionBasedQueryParameterSetterFactory extends QueryParameterSetterFactory {
private final EvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
private final EvaluationContextProvider evaluationContextProvider;
private final Parameters<?, ?> parameters;
/**
* @param parser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @param parameters must not be {@literal null}.
*/
ExpressionBasedQueryParameterSetterFactory(SpelExpressionParser parser,
EvaluationContextProvider evaluationContextProvider, Parameters<?, ?> parameters) {
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
Assert.notNull(parser, "SpelExpressionParser must not be null!");
Assert.notNull(parameters, "Parameters must not be null!");
this.evaluationContextProvider = evaluationContextProvider;
this.parser = parser;
this.parameters = parameters;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.QueryParameterSetterFactory#create(org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding, java.lang.String)
*/
@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);
if (!binding.isExpression()) {
return null;
}
return null;
Expression expression = parser.parseExpression(binding.getExpression());
return createSetter(values -> evaluateExpression(expression, values), binding, null, true);
}
private Expression parseExpressionString(String expressionString) {
return parser.parseExpression(expressionString);
/**
* Evaluates the given {@link Expression} against the given values.
*
* @param expression must not be {@literal null}.
* @param values must not be {@literal null}.
* @return
*/
private Object evaluateExpression(Expression expression, Object[] values) {
EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameters, values);
return expression.getValue(context, Object.class);
}
}
@@ -83,44 +178,48 @@ interface QueryParameterSetterFactory {
* Extracts values for parameter bindings from method parameters. It handles named as well as indexed parameters.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
@RequiredArgsConstructor
class BasicQueryParameterSetterFactory implements QueryParameterSetterFactory {
private static class BasicQueryParameterSetterFactory extends QueryParameterSetterFactory {
private final JpaParameters parameters;
/**
* @param parameters must not be {@literal null}.
*/
BasicQueryParameterSetterFactory(JpaParameters parameters) {
Assert.notNull(parameters, "JpaParameters must not be null!");
this.parameters = parameters;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.QueryParameterSetterFactory#create(org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding, java.lang.String)
*/
@Override
public QueryParameterSetter create(ParameterBinding binding, String queryString) {
JpaParameter parameter = QueryUtils.hasNamedParameter(queryString) //
? findParameterForBinding(binding)
? 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);
return parameter == null //
? QueryParameterSetter.NOOP //
: createSetter(values -> getValue(values, parameter), binding, parameter, false);
}
private JpaParameter findParameterForBinding(ParameterBinding binding) {
for (JpaParameter methodParameterCandidate : parameters.getBindableParameters()) {
return parameters.getBindableParameters().stream() //
.filter(candidate -> binding.getName().equals(getName(candidate))) //
.findFirst().orElse(null);
}
if (binding.getName().equals(getName(methodParameterCandidate))) {
return methodParameterCandidate;
}
}
return null;
private Object getValue(Object[] values, Parameter parameter) {
return new JpaParametersParameterAccessor(parameters, values).getValue(parameter);
}
private static String getName(JpaParameter p) {
@@ -128,12 +227,37 @@ interface QueryParameterSetterFactory {
}
}
@RequiredArgsConstructor
class CriteriaQueryParameterSetterFactory implements QueryParameterSetterFactory {
/**
* {@link QueryParameterSetterFactory}
*
* @author Jens Schauder
* @author Oliver Gierke
*/
private static class CriteriaQueryParameterSetterFactory extends QueryParameterSetterFactory {
private final JpaParameters parameters;
private final List<ParameterMetadata<?>> expressions;
/**
* Creates a new {@link QueryParameterSetterFactory} from the given {@link JpaParameters} and
* {@link ParameterMetadata}.
*
* @param parameters must not be {@literal null}.
* @param metadata must not be {@literal null}.
*/
CriteriaQueryParameterSetterFactory(JpaParameters parameters, List<ParameterMetadata<?>> metadata) {
Assert.notNull(parameters, "JpaParameters must not be null!");
Assert.notNull(metadata, "Expressions must not be null!");
this.parameters = parameters;
this.expressions = metadata;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.QueryParameterSetterFactory#create(org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding, java.lang.String)
*/
@Override
public QueryParameterSetter create(ParameterBinding binding, String queryString) {
@@ -144,17 +268,90 @@ interface QueryParameterSetterFactory {
}
JpaParameter parameter = parameters.getBindableParameter(binding.getPosition() - 1);
TemporalType temporalType = parameter.isTemporalParameter() ? parameter.getTemporalType() : null;
Function<Object[], Object> valueExtractor = vs -> {
return new NamedOrIndexedQueryParameterSetter(values -> getAndPrepare(parameter, metadata, values),
metadata.getExpression(), temporalType, false);
}
Object value = new JpaParametersParameterAccessor(parameters, vs).getValue(parameter);
return metadata.prepare(value);
};
private Object getAndPrepare(JpaParameter parameter, ParameterMetadata<?> metadata, Object[] values) {
TemporalType temporalTypeOrNull = parameter.isTemporalParameter() ? parameter.getTemporalType() : null;
JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor(parameters, values);
return new NamedOrIndexedQueryParameterSetter(valueExtractor, metadata.getExpression(), temporalTypeOrNull,
false);
return metadata.prepare(accessor.getValue(parameter));
}
}
private static class ParameterImpl<T> implements javax.persistence.Parameter<T> {
private final Class<T> parameterType;
private final String name;
private final Integer position;
/**
* Creates a new {@link ParameterImpl} for the given {@link JpaParameter} and {@link ParameterBinding}.
*
* @param parameter can be {@literal null}.
* @param binding must not be {@literal null}.
* @return
*/
static javax.persistence.Parameter<?> of(JpaParameter parameter, ParameterBinding binding) {
Class<?> type = parameter == null ? Object.class : parameter.getType();
return new ParameterImpl<>(type, getName(parameter, binding), binding.getPosition());
}
/**
* Creates a new {@link ParameterImpl} for the given name, position and parameter type.
*
* @param parameterType must not be {@literal null}.
* @param name can be {@literal null}.
* @param position can be {@literal null}.
*/
private ParameterImpl(Class<T> parameterType, String name, Integer position) {
this.name = name;
this.position = position;
this.parameterType = parameterType;
}
/*
* (non-Javadoc)
* @see javax.persistence.Parameter#getName()
*/
@Override
public String getName() {
return name;
}
/*
* (non-Javadoc)
* @see javax.persistence.Parameter#getPosition()
*/
@Override
public Integer getPosition() {
return position;
}
/*
* (non-Javadoc)
* @see javax.persistence.Parameter#getParameterType()
*/
@Override
public Class<T> getParameterType() {
return parameterType;
}
private static String getName(JpaParameter parameter, ParameterBinding binding) {
if (parameter == null) {
return binding.getName();
}
return parameter.isNamedParameter() //
? parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O parameter needs to have a name!")) //
: null;
}
}
}

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.data.jpa.domain.sample;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@@ -26,17 +22,25 @@ import javax.persistence.Table;
/**
* @author Mark Paluch
* @author Oliver Gierke
* @see <a href="download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf">Final JPA 2.1
* Specification 2.4.1.3 Derived Identities Example 2</a>
*/
@Data
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
public class Site implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id @GeneratedValue Integer id;
public Site() {}
public Site(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
}

View File

@@ -54,6 +54,8 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
@@ -62,7 +64,6 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.domain.ExampleMatcher.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.sample.Address;
import org.springframework.data.jpa.domain.sample.Role;
@@ -1536,7 +1537,7 @@ public class UserRepositoryTests {
assertThat(users.getContent()).hasSize(2).containsExactly(thirdUser, fourthUser);
}
@Test // DATAJPA-1140
public void shouldFindUsersByUserFirstnameAsSpELExpressionAndLastnameAsStringInStringBasedQuery() {
@@ -1547,7 +1548,7 @@ public class UserRepositoryTests {
assertThat(users).containsOnly(firstUser);
}
@Test // DATAJPA-1140
public void shouldFindUsersByFirstnameAsStringAndUserLastnameAsSpELExpressionInStringBasedQuery() {
@@ -1569,7 +1570,7 @@ public class UserRepositoryTests {
assertThat(users).containsOnly(firstUser);
}
@Test // DATAJPA-1140
public void shouldFindUsersByFirstnameAsFakeSpELExpressionAndUserLastnameAsSpELExpressionInStringBasedQuery() {

View File

@@ -99,7 +99,7 @@ public class ParameterBinderUnitTests {
Method validWithSort = SampleRepository.class.getMethod("validWithSort", String.class, Sort.class);
Object[] values = { "foo", null };
ParameterBinderFactory.createParameterBinder(new JpaParameters(validWithSort)).bind(query, values);
ParameterBinderFactory.createBinder(new JpaParameters(validWithSort)).bind(query, values);
verify(query).setParameter(eq(1), eq("foo"));
}
@@ -109,7 +109,7 @@ public class ParameterBinderUnitTests {
Method validWithPageable = SampleRepository.class.getMethod("validWithPageable", String.class, Pageable.class);
Object[] values = { "foo", null };
ParameterBinderFactory.createParameterBinder(new JpaParameters(validWithPageable)).bind(query, values);
ParameterBinderFactory.createBinder(new JpaParameters(validWithPageable)).bind(query, values);
verify(query).setParameter(eq(1), eq("foo"));
}
@@ -117,7 +117,7 @@ public class ParameterBinderUnitTests {
public void usesIndexedParametersIfNoParamAnnotationPresent() throws Exception {
Object[] values = { "foo" };
ParameterBinderFactory.createParameterBinder(new JpaParameters(useIndexedParameters)).bind(query, values);
ParameterBinderFactory.createBinder(new JpaParameters(useIndexedParameters)).bind(query, values);
verify(query).setParameter(eq(1), anyObject());
}
@@ -132,7 +132,7 @@ public class ParameterBinderUnitTests {
when(query.getParameters()).thenReturn(singleton(parameter));
Object[] values = { "foo" };
ParameterBinderFactory.createParameterBinder(new JpaParameters(valid)).bind(query, values);
ParameterBinderFactory.createBinder(new JpaParameters(valid)).bind(query, values);
verify(query).setParameter(eq("username"), anyObject());
}
@@ -145,7 +145,7 @@ public class ParameterBinderUnitTests {
SampleEmbeddable embeddable = new SampleEmbeddable();
Object[] values = { embeddable };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
ParameterBinderFactory.createBinder(parameters).bind(query, values);
verify(query).setParameter(1, embeddable);
}
@@ -158,7 +158,7 @@ public class ParameterBinderUnitTests {
Date date = new Date();
Object[] values = { date };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
ParameterBinderFactory.createBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq(date), eq(TemporalType.DATE));
}
@@ -171,7 +171,7 @@ public class ParameterBinderUnitTests {
Date date = new Date();
Object[] values = { date };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
ParameterBinderFactory.createBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq(date), eq(TemporalType.TIMESTAMP));
}
@@ -182,7 +182,7 @@ public class ParameterBinderUnitTests {
Method method = SampleRepository.class.getMethod("invalidWithTemporalTypeParameter", String.class);
JpaParameters parameters = new JpaParameters(method);
ParameterBinderFactory.createParameterBinder(parameters);
ParameterBinderFactory.createBinder(parameters);
}
@Test // DATAJPA-461
@@ -192,7 +192,7 @@ public class ParameterBinderUnitTests {
JpaParameters parameters = new JpaParameters(method);
Integer[] ids = new Integer[] { 1, 2, 3 };
Object[] values = { ids };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
ParameterBinderFactory.createBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq(ids));
}
@@ -204,7 +204,7 @@ public class ParameterBinderUnitTests {
JpaParameters parameters = new JpaParameters(method);
Object[] values = { Optional.of("Foo") };
ParameterBinderFactory.createParameterBinder(parameters).bind(query, values);
ParameterBinderFactory.createBinder(parameters).bind(query, values);
verify(query).setParameter(eq(1), eq("Foo"));
}