From a94a807bb3fd12f6b799f1054c3465f122a17af3 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Wed, 25 Jun 2014 12:41:49 +0200 Subject: [PATCH] DATAJPA-564 - Add support for dynamically evaluating SpEL expressions in derived queries. We now support the dynamic discovery and binding of SpEL Expression parameters in query derivation. Introduced ExpressionAwareParameterBinder for generic SpEL expression evaluation support. --- .../query/CriteriaQueryParameterBinder.java | 11 +- .../query/ExpressionAwareParameterBinder.java | 240 ++++++++++++++++++ .../jpa/repository/query/JpaParameters.java | 8 + .../query/JpaQueryLookupStrategy.java | 30 ++- .../jpa/repository/query/ParameterBinder.java | 24 +- .../query/ParameterMetadataProvider.java | 10 +- .../repository/query/PartTreeJpaQuery.java | 21 +- ...lExpressionStringQueryParameterBinder.java | 131 +--------- .../query/StringQueryParameterBinder.java | 9 +- .../jpa/repository/UserRepositoryTests.java | 19 +- .../jpa/repository/sample/UserRepository.java | 6 + 11 files changed, 359 insertions(+), 150 deletions(-) create mode 100644 src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java diff --git a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java index 756638b66..249c7fc62 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java @@ -23,6 +23,7 @@ import javax.persistence.Query; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; import org.springframework.data.repository.query.Parameters; import org.springframework.util.Assert; @@ -33,7 +34,7 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Thomas Darimont */ -class CriteriaQueryParameterBinder extends ParameterBinder { +class CriteriaQueryParameterBinder extends ExpressionAwareParameterBinder { private final Iterator> expressions; @@ -42,10 +43,14 @@ class CriteriaQueryParameterBinder extends ParameterBinder { * {@link javax.persistence.criteria.ParameterExpression}. * * @param parameters + * @param values + * @param expressions + * @param evaluationContextProvider */ - CriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, Iterable> expressions) { + CriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, Iterable> expressions, + ExpressionEvaluationContextProvider evaluationContextProvider) { - super(parameters, values); + super(parameters, values, evaluationContextProvider); Assert.notNull(expressions); this.expressions = expressions.iterator(); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java new file mode 100644 index 000000000..24bd60a44 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/query/ExpressionAwareParameterBinder.java @@ -0,0 +1,240 @@ +/* + * 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 org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ConstructorResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.MethodResolver; +import org.springframework.expression.OperatorOverloader; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypeComparator; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.TypeLocator; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.Assert; + +/** + * A {@link ParameterBinder} that is able to detect and dynamically evaluate SpEL expression based parameters. + * + * @author Thomas Darimont + */ +class ExpressionAwareParameterBinder extends ParameterBinder { + + private final ExpressionEvaluationContextProvider evaluationContextProvider; + + /** + * Creates a new {@literal ExpressionAwareParameterBinder}. + * + * @param parameters + * @param evaluationContextProvider + */ + public ExpressionAwareParameterBinder(JpaParameters parameters, + ExpressionEvaluationContextProvider evaluationContextProvider) { + this(parameters, new Object[0], evaluationContextProvider); + } + + /** + * Creates a new {@literal ExpressionAwareParameterBinder}. + * + * @param parameters + * @param values + * @param evaluationContextProvider + */ + public ExpressionAwareParameterBinder(JpaParameters parameters, Object[] values, + ExpressionEvaluationContextProvider evaluationContextProvider) { + + super(parameters, values); + + Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!"); + + this.evaluationContextProvider = evaluationContextProvider; + } + + /* (non-Javadoc) + * @see org.springframework.data.jpa.repository.query.ParameterBinder#computeParameterValue(org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter, java.lang.Object, java.lang.Object[]) + */ + @Override + protected Object computeParameterValue(JpaParameter parameter, Object value, Object[] values) { + + if (!parameter.isExpressionParameter()) { + return super.computeParameterValue(parameter, value, values); + } + + if (value instanceof String) { + return evaluateExpression((String) value); + } + + if (value instanceof Expression) { + return evaluateExpression((Expression) value); + } + + throw new IllegalArgumentException("Cannot convert Value: " + value + " to a SpEL expression"); + } + + /** + * Parses the given {@code expressionString} into a SpEL {@link Expression}. + * + * @param expressionString + * @return + */ + protected Expression parseExpressionString(String expressionString) { + return new SpelExpressionParser().parseExpression(expressionString); + } + + /** + * Evaluates the given {@code expressionString} as a SpEL {@link Expression}. + * + * @param expressionString + * @return + */ + protected Object evaluateExpression(String expressionString) { + return evaluateExpression(parseExpressionString(expressionString)); + } + + /** + * Evaluates the given SpEL {@link Expression}. + * + * @param expr + * @return + */ + protected Object evaluateExpression(Expression expr) { + return expr.getValue(getEvaluationContext(), String.class); + } + + /** + * Returns the {@link StandardEvaluationContext} to use for evaluation. + * + * @return + */ + protected StandardEvaluationContext getEvaluationContext() { + + EvaluationContext delegatee = evaluationContextProvider.getEvaluationContext(); + StandardEvaluationContext evalContext = new DelegatingStandardEvaluationContext(getValues(), delegatee); + + return evalContext; + } + + /** + * A {@link StandardEvaluationContext} that delegates to the given {@link EvaluationContext}. Variables are first + * looked-up locally and if not the lookup is performed against the delegatee. + * + * @author Thomas Darimont + */ + protected static class DelegatingStandardEvaluationContext extends StandardEvaluationContext { + + private final EvaluationContext delegatee; + + /** + * Creates a new {@link DelegatingStandardEvaluationContext}. + * + * @param values must not be {@literal null} + * @param delegatee must not be {@literal null} + */ + public DelegatingStandardEvaluationContext(Object[] values, EvaluationContext delegatee) { + + super(values); + + Assert.notNull(delegatee, "EvaluationContext delegatee must not be null!"); + + this.delegatee = delegatee; + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getConstructorResolvers() + */ + @Override + public List getConstructorResolvers() { + return delegatee.getConstructorResolvers(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getMethodResolvers() + */ + @Override + public List getMethodResolvers() { + return delegatee.getMethodResolvers(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getPropertyAccessors() + */ + @Override + public List getPropertyAccessors() { + return delegatee.getPropertyAccessors(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeLocator() + */ + @Override + public TypeLocator getTypeLocator() { + return delegatee.getTypeLocator(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeConverter() + */ + @Override + public TypeConverter getTypeConverter() { + return delegatee.getTypeConverter(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeComparator() + */ + @Override + public TypeComparator getTypeComparator() { + return delegatee.getTypeComparator(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getOperatorOverloader() + */ + @Override + public OperatorOverloader getOperatorOverloader() { + return delegatee.getOperatorOverloader(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#getBeanResolver() + */ + @Override + public BeanResolver getBeanResolver() { + return delegatee.getBeanResolver(); + } + + /* (non-Javadoc) + * @see org.springframework.expression.spel.support.StandardEvaluationContext#lookupVariable(java.lang.String) + */ + @Override + public Object lookupVariable(String name) { + + Object result = super.lookupVariable(name); + if (result != null) { + return result; + } + + return delegatee.lookupVariable(name); + } + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java index fc3915bf6..868b3f484 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java @@ -26,6 +26,7 @@ import org.springframework.data.jpa.repository.Temporal; 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.expression.Expression; /** * Custom extension of {@link Parameters} discovering additional query parameter annotations. @@ -110,6 +111,13 @@ public class JpaParameters extends Parameters { return isDateParameter() && hasTemporalParamAnnotation(); } + /** + * @return {@literal true} if this parameter is of type {@link Expression}. + */ + public boolean isExpressionParameter() { + return Expression.class.equals(getType()); + } + /** * @return the {@link TemporalType} on the {@link Temporal} annotation of the given {@link Parameter}. */ diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index ceab37b15..8ea71d398 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -50,11 +50,14 @@ public final class JpaQueryLookupStrategy { private final EntityManager em; private final QueryExtractor provider; + protected final ExpressionEvaluationContextProvider evaluationContextProvider; - public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor) { + public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor, + ExpressionEvaluationContextProvider evaluationContextProvider) { this.em = em; this.provider = extractor; + this.evaluationContextProvider = evaluationContextProvider; } /* @@ -80,16 +83,17 @@ public final class JpaQueryLookupStrategy { */ private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrategy { - public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor) { + public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor, + ExpressionEvaluationContextProvider evaluationContextProvider) { - super(em, extractor); + super(em, extractor, evaluationContextProvider); } @Override protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) { try { - return new PartTreeJpaQuery(method, em); + return new PartTreeJpaQuery(method, em, this.evaluationContextProvider); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("Could not create query metamodel for method %s!", method.toString()), e); @@ -105,13 +109,10 @@ public final class JpaQueryLookupStrategy { */ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy { - private final ExpressionEvaluationContextProvider evaluationContextProvider; - public DeclaredQueryLookupStrategy(EntityManager em, QueryExtractor extractor, ExpressionEvaluationContextProvider evaluationContextProvider) { - super(em, extractor); - this.evaluationContextProvider = evaluationContextProvider; + super(em, extractor, evaluationContextProvider); } @Override @@ -159,9 +160,11 @@ public final class JpaQueryLookupStrategy { private final CreateQueryLookupStrategy createStrategy; public CreateIfNotFoundQueryLookupStrategy(EntityManager em, QueryExtractor extractor, - CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) { + CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy, + ExpressionEvaluationContextProvider evaluationContextProvider) { + + super(em, extractor, evaluationContextProvider); - super(em, extractor); this.createStrategy = createStrategy; this.lookupStrategy = lookupStrategy; } @@ -190,12 +193,13 @@ public final class JpaQueryLookupStrategy { switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) { case CREATE: - return new CreateQueryLookupStrategy(em, extractor); + return new CreateQueryLookupStrategy(em, extractor, evaluationContextProvider); case USE_DECLARED_QUERY: return new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider); case CREATE_IF_NOT_FOUND: - return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor), - new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider)); + return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor, + evaluationContextProvider), new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider), + evaluationContextProvider); default: throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java index bc4448b50..f63b7a895 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java @@ -106,7 +106,8 @@ public class ParameterBinder { if (parameter.isBindable()) { - Object value = values[methodParameterPosition]; + Object value = computeParameterValue(parameter, values[methodParameterPosition], values); + bind(query, parameter, value, queryParameterPosition++); } @@ -116,6 +117,27 @@ public class ParameterBinder { return query; } + /** + * Computes the value to bind for the given {@link JpaParameter} and the given {@code value} by potentially using the + * other {@code values}. This is intended to be customized in sub-classes. + * + * @param parameter + * @param value + * @param values + * @return + */ + protected Object computeParameterValue(JpaParameter parameter, Object value, Object[] values) { + return value; + } + + /** + * 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()) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index 4457acdee..aa191a961 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -30,6 +30,7 @@ import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; +import org.springframework.expression.Expression; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -121,7 +122,14 @@ class ParameterMetadataProvider { Assert.notNull(type); - ParameterExpression expression = name == null ? builder.parameter(type) : builder.parameter(type, name); + /* + * We treat Expression types as Object vales since the real value to be bound as a parameter is determined at query time. + */ + @SuppressWarnings("unchecked") + Class reifiedType = Expression.class.equals(type) ? (Class) Object.class : type; + + ParameterExpression expression = name == null ? builder.parameter(reifiedType) : builder.parameter(reifiedType, + name); ParameterMetadata value = new ParameterMetadata(expression, part.getType(), accessor == null ? ParameterMetadata.PLACEHOLDER : accessor.next()); expressions.add(value); diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 0029903ac..2236c79d4 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -26,6 +26,8 @@ import javax.persistence.criteria.CriteriaQuery; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; +import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; +import org.springframework.data.jpa.repository.support.StandardExpressionEvaluationContextProvider; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.PartTree; @@ -43,7 +45,8 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { private final QueryPreparer query; private final QueryPreparer countQuery; - private EntityManager em; + private final EntityManager em; + private final ExpressionEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link PartTreeJpaQuery}. @@ -53,8 +56,22 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { */ public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) { + this(method, em, StandardExpressionEvaluationContextProvider.INSTANCE); + } + + /** + * Creates a new {@link PartTreeJpaQuery}. + * + * @param method must not be {@literal null}. + * @param em must not be {@literal null}. + * @param evaluationContextProvider + */ + public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em, + ExpressionEvaluationContextProvider evaluationContextProvider) { + super(method, em); this.em = em; + this.evaluationContextProvider = evaluationContextProvider; this.domainClass = method.getEntityInformation().getJavaType(); this.tree = new PartTree(method.getName(), domainClass); @@ -205,7 +222,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { } private ParameterBinder getBinder(Object[] values, List> expressions) { - return new CriteriaQueryParameterBinder(parameters, values, expressions); + return new CriteriaQueryParameterBinder(parameters, values, expressions, evaluationContextProvider); } private Sort getDynamicSort(Object[] values) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java index 6123b3fd0..e4183044a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/SpelExpressionStringQueryParameterBinder.java @@ -15,24 +15,11 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; - import javax.persistence.Query; import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding; import org.springframework.data.jpa.repository.support.ExpressionEvaluationContextProvider; -import org.springframework.expression.BeanResolver; -import org.springframework.expression.ConstructorResolver; -import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; -import org.springframework.expression.MethodResolver; -import org.springframework.expression.OperatorOverloader; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypeComparator; -import org.springframework.expression.TypeConverter; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; /** @@ -43,7 +30,6 @@ import org.springframework.util.Assert; class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinder { private final StringQuery query; - private final ExpressionEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link SpelExpressionStringQueryParameterBinder}. @@ -56,12 +42,11 @@ class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinde public SpelExpressionStringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query, ExpressionEvaluationContextProvider evaluationContextProvider) { - super(parameters, values, query); + super(parameters, values, query, evaluationContextProvider); Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!"); this.query = query; - this.evaluationContextProvider = evaluationContextProvider; } /* (non-Javadoc) @@ -82,124 +67,18 @@ class SpelExpressionStringQueryParameterBinder extends StringQueryParameterBinde if (binding.isExpression()) { - Expression expr = new SpelExpressionParser().parseExpression(binding.getExpression()); + Expression expr = parseExpressionString(binding.getExpression()); - EvaluationContext delegatee = evaluationContextProvider.getEvaluationContext(); - StandardEvaluationContext evalContext = new DelegatingStandardEvaluationContext(getValues(), delegatee); - Object actualValue = expr.getValue(evalContext, String.class); + Object value = evaluateExpression(expr); if (binding.getName() != null) { - jpaQuery.setParameter(binding.getName(), binding.prepare(actualValue)); + jpaQuery.setParameter(binding.getName(), binding.prepare(value)); } else { - jpaQuery.setParameter(binding.getPosition(), binding.prepare(actualValue)); + jpaQuery.setParameter(binding.getPosition(), binding.prepare(value)); } } } return jpaQuery; } - - /** - * A {@link StandardEvaluationContext} that delegates to the given {@link EvaluationContext}. Variables are first - * looked-up locally and if not the lookup is performed against the delegatee. - * - * @author Thomas Darimont - */ - static class DelegatingStandardEvaluationContext extends StandardEvaluationContext { - - private final EvaluationContext delegatee; - - /** - * Creates a new {@link DelegatingStandardEvaluationContext}. - * - * @param values must not be {@literal null} - * @param delegatee must not be {@literal null} - */ - public DelegatingStandardEvaluationContext(Object[] values, EvaluationContext delegatee) { - - super(values); - - Assert.notNull(delegatee, "EvaluationContext delegatee must not be null!"); - - this.delegatee = delegatee; - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getConstructorResolvers() - */ - @Override - public List getConstructorResolvers() { - return delegatee.getConstructorResolvers(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getMethodResolvers() - */ - @Override - public List getMethodResolvers() { - return delegatee.getMethodResolvers(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getPropertyAccessors() - */ - @Override - public List getPropertyAccessors() { - return delegatee.getPropertyAccessors(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeLocator() - */ - @Override - public TypeLocator getTypeLocator() { - return delegatee.getTypeLocator(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeConverter() - */ - @Override - public TypeConverter getTypeConverter() { - return delegatee.getTypeConverter(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getTypeComparator() - */ - @Override - public TypeComparator getTypeComparator() { - return delegatee.getTypeComparator(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getOperatorOverloader() - */ - @Override - public OperatorOverloader getOperatorOverloader() { - return delegatee.getOperatorOverloader(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#getBeanResolver() - */ - @Override - public BeanResolver getBeanResolver() { - return delegatee.getBeanResolver(); - } - - /* (non-Javadoc) - * @see org.springframework.expression.spel.support.StandardEvaluationContext#lookupVariable(java.lang.String) - */ - @Override - public Object lookupVariable(String name) { - - Object result = super.lookupVariable(name); - if (result != null) { - return result; - } - - return delegatee.lookupVariable(name); - } - } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java index b792d311a..6f0e164d5 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java @@ -20,6 +20,7 @@ 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.jpa.repository.support.ExpressionEvaluationContextProvider; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.util.Assert; @@ -30,7 +31,7 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Thomas Darimont */ -public class StringQueryParameterBinder extends ParameterBinder { +public class StringQueryParameterBinder extends ExpressionAwareParameterBinder { private final StringQuery query; @@ -41,10 +42,12 @@ public class StringQueryParameterBinder extends ParameterBinder { * @param parameters must not be {@literal null}. * @param values must not be {@literal null}. * @param query must not be {@literal null}. + * @param evaluationContextProvider */ - public StringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query) { + public StringQueryParameterBinder(JpaParameters parameters, Object[] values, StringQuery query, + ExpressionEvaluationContextProvider evaluationContextProvider) { - super(parameters, values); + super(parameters, values, evaluationContextProvider); Assert.notNull(query, "StringQuery must not be null!"); this.query = query; diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 97edc3351..9e7f8e1f3 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -59,6 +59,8 @@ import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.SpecialUser; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.sample.UserRepository; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -287,7 +289,7 @@ public class UserRepositoryTests { flushTestUsers(); - repository.findByLastname(null); + repository.findByLastname((String) null); } @Test @@ -1615,6 +1617,21 @@ public class UserRepositoryTests { assertThat(users.get(0), is(firstUser)); } + /** + * @see DATAJPA-XXX + */ + @Test + public void shouldFindUserByLastnameWithSpelExpressionInDerivedQuery() { + + flushTestUsers(); + + Expression expr = new SpelExpressionParser().parseExpression("'Gier' + 'ke'"); + List users = repository.queryByLastname(expr); + + assertThat(users, hasSize(1)); + assertThat(users.get(0), is(firstUser)); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index b64aa5a44..a6b19a50b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -37,6 +37,7 @@ import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.query.Procedure; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.springframework.expression.Expression; import org.springframework.transaction.annotation.Transactional; import com.google.common.base.Optional; @@ -471,4 +472,9 @@ public interface UserRepository extends JpaRepository, JpaSpecifi */ @Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%") List findByLastnameWithSpelExpression(@Param("lastname") String lastname); + + /** + * @see DATAJPA-XXX + */ + List queryByLastname(Expression lastname); }