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.
This commit is contained in:
Thomas Darimont
2014-06-25 12:41:49 +02:00
committed by Oliver Gierke
parent c4f245b11e
commit a94a807bb3
11 changed files with 359 additions and 150 deletions

View File

@@ -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<ParameterMetadata<?>> 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<ParameterMetadata<?>> expressions) {
CriteriaQueryParameterBinder(JpaParameters parameters, Object[] values, Iterable<ParameterMetadata<?>> expressions,
ExpressionEvaluationContextProvider evaluationContextProvider) {
super(parameters, values);
super(parameters, values, evaluationContextProvider);
Assert.notNull(expressions);
this.expressions = expressions.iterator();
}

View File

@@ -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<ConstructorResolver> getConstructorResolvers() {
return delegatee.getConstructorResolvers();
}
/* (non-Javadoc)
* @see org.springframework.expression.spel.support.StandardEvaluationContext#getMethodResolvers()
*/
@Override
public List<MethodResolver> getMethodResolvers() {
return delegatee.getMethodResolvers();
}
/* (non-Javadoc)
* @see org.springframework.expression.spel.support.StandardEvaluationContext#getPropertyAccessors()
*/
@Override
public List<PropertyAccessor> 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);
}
}
}

View File

@@ -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<JpaParameters, JpaParameter> {
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}.
*/

View File

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

View File

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

View File

@@ -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<T> 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<T> reifiedType = Expression.class.equals(type) ? (Class<T>) Object.class : type;
ParameterExpression<T> expression = name == null ? builder.parameter(reifiedType) : builder.parameter(reifiedType,
name);
ParameterMetadata<T> value = new ParameterMetadata<T>(expression, part.getType(),
accessor == null ? ParameterMetadata.PLACEHOLDER : accessor.next());
expressions.add(value);

View File

@@ -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<ParameterMetadata<?>> expressions) {
return new CriteriaQueryParameterBinder(parameters, values, expressions);
return new CriteriaQueryParameterBinder(parameters, values, expressions, evaluationContextProvider);
}
private Sort getDynamicSort(Object[] values) {

View File

@@ -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<ConstructorResolver> getConstructorResolvers() {
return delegatee.getConstructorResolvers();
}
/* (non-Javadoc)
* @see org.springframework.expression.spel.support.StandardEvaluationContext#getMethodResolvers()
*/
@Override
public List<MethodResolver> getMethodResolvers() {
return delegatee.getMethodResolvers();
}
/* (non-Javadoc)
* @see org.springframework.expression.spel.support.StandardEvaluationContext#getPropertyAccessors()
*/
@Override
public List<PropertyAccessor> 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);
}
}
}

View File

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

View File

@@ -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<User> users = repository.queryByLastname(expr);
assertThat(users, hasSize(1));
assertThat(users.get(0), is(firstUser));
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -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<User, Integer>, JpaSpecifi
*/
@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%")
List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);
/**
* @see DATAJPA-XXX
*/
List<User> queryByLastname(Expression lastname);
}