Add support for Value Expressions.
Closes #3619 Original pull request: #3627
This commit is contained in:
@@ -22,12 +22,12 @@ import java.util.Objects;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.expression.ValueEvaluationContextProvider;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentLruCache;
|
||||
@@ -50,12 +50,12 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
private final DeclaredQuery query;
|
||||
private final Lazy<DeclaredQuery> countQuery;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final SpelExpressionParser parser;
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
|
||||
private final QueryRewriter queryRewriter;
|
||||
private final QuerySortRewriter querySortRewriter;
|
||||
private final Lazy<ParameterBinder> countParameterBinder;
|
||||
private final ValueEvaluationContextProvider valueExpressionContextProvider;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
|
||||
@@ -65,30 +65,29 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryString must not be {@literal null}.
|
||||
* @param countQueryString must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @param parser must not be {@literal null}.
|
||||
* @param queryRewriter must not be {@literal null}.
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
*/
|
||||
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString, QueryRewriter queryRewriter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
super(method, em);
|
||||
|
||||
Assert.hasText(queryString, "Query string must not be null or empty");
|
||||
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
|
||||
Assert.notNull(parser, "Parser must not be null");
|
||||
Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null");
|
||||
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
|
||||
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
|
||||
this.valueExpressionDelegate = valueExpressionDelegate;
|
||||
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
|
||||
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate,
|
||||
method.isNativeQuery());
|
||||
|
||||
this.countQuery = Lazy.of(() -> {
|
||||
|
||||
if (StringUtils.hasText(countQueryString)) {
|
||||
|
||||
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), parser,
|
||||
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate,
|
||||
method.isNativeQuery());
|
||||
}
|
||||
|
||||
@@ -99,7 +98,6 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
return this.createBinder(this.countQuery.get());
|
||||
});
|
||||
|
||||
this.parser = parser;
|
||||
this.queryRewriter = queryRewriter;
|
||||
|
||||
JpaParameters parameters = method.getParameters();
|
||||
@@ -140,8 +138,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
}
|
||||
|
||||
protected ParameterBinder createBinder(DeclaredQuery query) {
|
||||
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query, parser,
|
||||
evaluationContextProvider);
|
||||
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
|
||||
valueExpressionDelegate, valueExpressionContextProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -59,7 +60,7 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
* @param parser must not be {@literal null}.
|
||||
* @param nativeQuery is a given query is native or not
|
||||
*/
|
||||
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
|
||||
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, ValueExpressionParser parser,
|
||||
boolean nativeQuery) {
|
||||
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
|
||||
}
|
||||
@@ -74,7 +75,7 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
* @return A query supporting SpEL expressions.
|
||||
*/
|
||||
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
|
||||
SpelExpressionParser parser, boolean nativeQuery) {
|
||||
ValueExpressionParser parser, boolean nativeQuery) {
|
||||
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
* @param parser Must not be {@literal null}.
|
||||
*/
|
||||
private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata<?> metadata,
|
||||
SpelExpressionParser parser) {
|
||||
ValueExpressionParser parser) {
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(metadata, "metadata must not be null");
|
||||
@@ -99,9 +100,9 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
|
||||
query = potentiallyQuoteExpressionsParameter(query);
|
||||
|
||||
Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION);
|
||||
ValueExpression expr = parser.parse(query);
|
||||
|
||||
String result = expr.getValue(evalContext, String.class);
|
||||
String result = Objects.toString(expr.evaluate(ValueEvaluationContext.of(null, evalContext)));
|
||||
|
||||
if (result == null) {
|
||||
return query;
|
||||
|
||||
@@ -19,9 +19,8 @@ import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.repository.query.QueryCreationException;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -34,31 +33,20 @@ enum JpaQueryFactory {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
* Creates a {@link RepositoryQuery} from the given {@link String} query.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param em must not be {@literal null}.
|
||||
* @param countQueryString
|
||||
* @param queryString must not be {@literal null}.
|
||||
* @param evaluationContextProvider
|
||||
* @return
|
||||
*/
|
||||
AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString, QueryRewriter queryRewriter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
if (method.isScrollQuery()) {
|
||||
throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries");
|
||||
}
|
||||
|
||||
return method.isNativeQuery()
|
||||
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,
|
||||
PARSER)
|
||||
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,
|
||||
PARSER);
|
||||
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate)
|
||||
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
@@ -30,7 +32,9 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -135,7 +139,7 @@ public final class JpaQueryLookupStrategy {
|
||||
*/
|
||||
private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
|
||||
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeclaredQueryLookupStrategy}.
|
||||
@@ -145,11 +149,11 @@ public final class JpaQueryLookupStrategy {
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
*/
|
||||
public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, QueryRewriterProvider queryRewriterProvider) {
|
||||
ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider) {
|
||||
|
||||
super(em, queryMethodFactory, queryRewriterProvider);
|
||||
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.valueExpressionDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,13 +172,13 @@ public final class JpaQueryLookupStrategy {
|
||||
}
|
||||
|
||||
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),
|
||||
getCountQuery(method, namedQueries, em), queryRewriter, evaluationContextProvider);
|
||||
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
String name = method.getNamedQueryName();
|
||||
if (namedQueries.hasQuery(name)) {
|
||||
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
|
||||
getCountQuery(method, namedQueries, em), queryRewriter, evaluationContextProvider);
|
||||
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
RepositoryQuery query = NamedQuery.lookupFrom(method, em);
|
||||
@@ -267,28 +271,47 @@ public final class JpaQueryLookupStrategy {
|
||||
* @param key may be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @param escape must not be {@literal null}.
|
||||
* @deprecated since 3.4, use
|
||||
* {@link #create(EntityManager, JpaQueryMethodFactory, Key, ValueExpressionDelegate, QueryRewriterProvider, EscapeCharacter)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated(since = "3.4")
|
||||
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
@Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape) {
|
||||
return create(em, queryMethodFactory, key,
|
||||
new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(),
|
||||
evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionDelegate.create()),
|
||||
queryRewriterProvider, escape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}.
|
||||
*
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryMethodFactory must not be {@literal null}.
|
||||
* @param key may be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @param queryRewriterProvider must not be {@literal null}.
|
||||
* @param escape must not be {@literal null}.
|
||||
*/
|
||||
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
@Nullable Key key, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider,
|
||||
EscapeCharacter escape) {
|
||||
|
||||
Assert.notNull(em, "EntityManager must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
|
||||
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
|
||||
|
||||
switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
|
||||
case CREATE:
|
||||
return new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
|
||||
case USE_DECLARED_QUERY:
|
||||
return new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider,
|
||||
queryRewriterProvider);
|
||||
case CREATE_IF_NOT_FOUND:
|
||||
return new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
|
||||
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
|
||||
new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider, queryRewriterProvider),
|
||||
queryRewriterProvider);
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
|
||||
}
|
||||
return switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
|
||||
case CREATE -> new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
|
||||
case USE_DECLARED_QUERY ->
|
||||
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider);
|
||||
case CREATE_IF_NOT_FOUND -> new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
|
||||
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
|
||||
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider),
|
||||
queryRewriterProvider);
|
||||
default -> throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,10 +25,9 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.NativeQuery;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@@ -57,12 +56,12 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
* @param queryString must not be {@literal null} or empty.
|
||||
* @param countQueryString must not be {@literal null} or empty.
|
||||
* @param rewriter the query rewriter to use.
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
*/
|
||||
public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString,
|
||||
QueryRewriter rewriter, QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
SpelExpressionParser parser) {
|
||||
QueryRewriter rewriter, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
super(method, em, queryString, countQueryString, rewriter, evaluationContextProvider, parser);
|
||||
super(method, em, queryString, countQueryString, rewriter, valueExpressionDelegate);
|
||||
|
||||
MergedAnnotations annotations = MergedAnnotations.from(method.getMethod());
|
||||
MergedAnnotation<NativeQuery> annotation = annotations.get(NativeQuery.class);
|
||||
|
||||
@@ -18,12 +18,12 @@ package org.springframework.data.jpa.repository.query;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.expression.ValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin;
|
||||
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -86,7 +86,7 @@ class ParameterBinderFactory {
|
||||
* {@link jakarta.persistence.Query} while processing SpEL expressions where applicable.
|
||||
*/
|
||||
static ParameterBinder createQueryAwareBinder(JpaParameters parameters, DeclaredQuery query,
|
||||
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ValueExpressionParser parser, ValueEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(parameters, "JpaParameters must not be null");
|
||||
Assert.notNull(query, "StringQuery must not be null");
|
||||
@@ -95,7 +95,7 @@ class ParameterBinderFactory {
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
|
||||
evaluationContextProvider, parameters);
|
||||
evaluationContextProvider);
|
||||
|
||||
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -493,12 +494,12 @@ class ParameterBinding {
|
||||
sealed interface ParameterOrigin permits Expression,MethodInvocationArgument {
|
||||
|
||||
/**
|
||||
* Creates a {@link Expression} for the given {@code expression} string.
|
||||
* Creates a {@link Expression} for the given {@code expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return {@link Expression} for the given {@code expression} string.
|
||||
* @return {@link Expression} for the given {@code expression}.
|
||||
*/
|
||||
static Expression ofExpression(String expression) {
|
||||
static Expression ofExpression(ValueExpression expression) {
|
||||
return new Expression(expression);
|
||||
}
|
||||
|
||||
@@ -562,7 +563,7 @@ class ParameterBinding {
|
||||
* @author Mark Paluch
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public record Expression(String expression) implements ParameterOrigin {
|
||||
public record Expression(ValueExpression expression) implements ParameterOrigin {
|
||||
|
||||
@Override
|
||||
public boolean isMethodArgument() {
|
||||
|
||||
@@ -21,6 +21,10 @@ import jakarta.persistence.TemporalType;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.MethodInvocationArgument;
|
||||
@@ -28,9 +32,7 @@ import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.P
|
||||
import org.springframework.data.jpa.repository.query.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -89,14 +91,13 @@ abstract class QueryParameterSetterFactory {
|
||||
* @return a {@link QueryParameterSetterFactory} that can handle
|
||||
* {@link org.springframework.expression.spel.standard.SpelExpression}s.
|
||||
*/
|
||||
static QueryParameterSetterFactory parsing(SpelExpressionParser parser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, Parameters<?, ?> parameters) {
|
||||
static QueryParameterSetterFactory parsing(ValueExpressionParser parser,
|
||||
ValueEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(parser, "SpelExpressionParser must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
|
||||
Assert.notNull(parameters, "Parameters must not be null");
|
||||
Assert.notNull(parser, "ValueExpressionParser must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "ValueEvaluationContextProvider must not be null");
|
||||
|
||||
return new ExpressionBasedQueryParameterSetterFactory(parser, evaluationContextProvider, parameters);
|
||||
return new ExpressionBasedQueryParameterSetterFactory(parser, evaluationContextProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,25 +162,22 @@ abstract class QueryParameterSetterFactory {
|
||||
*/
|
||||
private static class ExpressionBasedQueryParameterSetterFactory extends QueryParameterSetterFactory {
|
||||
|
||||
private final SpelExpressionParser parser;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final Parameters<?, ?> parameters;
|
||||
private final ValueExpressionParser parser;
|
||||
private final ValueEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
/**
|
||||
* @param parser must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
*/
|
||||
ExpressionBasedQueryParameterSetterFactory(SpelExpressionParser parser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, Parameters<?, ?> parameters) {
|
||||
ExpressionBasedQueryParameterSetterFactory(ValueExpressionParser parser,
|
||||
ValueEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
|
||||
Assert.notNull(parser, "SpelExpressionParser must not be null");
|
||||
Assert.notNull(parameters, "Parameters must not be null");
|
||||
Assert.notNull(parser, "ValueExpressionParser must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "ValueEvaluationContextProvider must not be null");
|
||||
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.parser = parser;
|
||||
this.parameters = parameters;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -190,9 +188,7 @@ abstract class QueryParameterSetterFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
Expression expression = parser.parseExpression(e.expression());
|
||||
|
||||
return createSetter(values -> evaluateExpression(expression, values), binding, null);
|
||||
return createSetter(values -> evaluateExpression(e.expression(), values), binding, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,11 +199,10 @@ abstract class QueryParameterSetterFactory {
|
||||
* @return the result of the evaluation.
|
||||
*/
|
||||
@Nullable
|
||||
private Object evaluateExpression(Expression expression, JpaParametersParameterAccessor accessor) {
|
||||
private Object evaluateExpression(ValueExpression expression, JpaParametersParameterAccessor accessor) {
|
||||
|
||||
EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues());
|
||||
|
||||
return expression.getValue(context, Object.class);
|
||||
ValueEvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(accessor.getValues());
|
||||
return expression.evaluate(evaluationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,8 @@ import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Query;
|
||||
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -43,12 +42,11 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
* @param em must not be {@literal null}
|
||||
* @param countQueryString
|
||||
* @param queryRewriter must not be {@literal null}
|
||||
* @param evaluationContextProvider must not be {@literal null}
|
||||
* @param parser must not be {@literal null}
|
||||
* @param valueExpressionDelegate must not be {@literal null}
|
||||
*/
|
||||
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String countQueryString,
|
||||
QueryRewriter queryRewriter, QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
|
||||
this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, queryRewriter, evaluationContextProvider, parser);
|
||||
QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, queryRewriter, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,13 +57,12 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
* @param queryString must not be {@literal null} or empty
|
||||
* @param countQueryString
|
||||
* @param queryRewriter
|
||||
* @param evaluationContextProvider must not be {@literal null}
|
||||
* @param parser must not be {@literal null}
|
||||
* @param valueExpressionDelegate must not be {@literal null}
|
||||
*/
|
||||
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString, QueryRewriter queryRewriter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
super(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider, parser);
|
||||
super(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
|
||||
|
||||
validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s", method);
|
||||
|
||||
|
||||
@@ -26,13 +26,14 @@ import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.InParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.MethodInvocationArgument;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin;
|
||||
import org.springframework.data.repository.query.SpelQueryContext;
|
||||
import org.springframework.data.repository.query.SpelQueryContext.SpelExtractor;
|
||||
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -241,14 +242,15 @@ class StringQuery implements DeclaredQuery {
|
||||
greatestParameterIndex = 0;
|
||||
}
|
||||
|
||||
SpelExtractor spelExtractor = createSpelExtractor(query, parametersShouldBeAccessedByIndex,
|
||||
ValueExpressionQueryRewriter.ParsedQuery parsedQuery = createSpelExtractor(query,
|
||||
parametersShouldBeAccessedByIndex,
|
||||
greatestParameterIndex);
|
||||
|
||||
String resultingQuery = spelExtractor.getQueryString();
|
||||
String resultingQuery = parsedQuery.getQueryString();
|
||||
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(resultingQuery);
|
||||
|
||||
int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0;
|
||||
int syntheticParameterIndex = expressionParameterIndex + spelExtractor.size();
|
||||
int syntheticParameterIndex = expressionParameterIndex + parsedQuery.size();
|
||||
|
||||
ParameterBindings parameterBindings = new ParameterBindings(bindings, it -> checkAndRegister(it, bindings),
|
||||
syntheticParameterIndex);
|
||||
@@ -258,7 +260,7 @@ class StringQuery implements DeclaredQuery {
|
||||
|
||||
while (matcher.find()) {
|
||||
|
||||
if (spelExtractor.isQuoted(matcher.start())) {
|
||||
if (parsedQuery.isQuoted(matcher.start())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -282,7 +284,8 @@ class StringQuery implements DeclaredQuery {
|
||||
String typeSource = matcher.group(COMPARISION_TYPE_GROUP);
|
||||
Assert.isTrue(parameterIndexString != null || parameterName != null,
|
||||
() -> String.format("We need either a name or an index; Offending query string: %s", query));
|
||||
String expression = spelExtractor.getParameter(parameterName == null ? parameterIndexString : parameterName);
|
||||
ValueExpression expression = parsedQuery
|
||||
.getParameter(parameterName == null ? parameterIndexString : parameterName);
|
||||
String replacement = null;
|
||||
|
||||
expressionParameterIndex++;
|
||||
@@ -346,7 +349,8 @@ class StringQuery implements DeclaredQuery {
|
||||
return resultingQuery;
|
||||
}
|
||||
|
||||
private static SpelExtractor createSpelExtractor(String queryWithSpel, boolean parametersShouldBeAccessedByIndex,
|
||||
private static ValueExpressionQueryRewriter.ParsedQuery createSpelExtractor(String queryWithSpel,
|
||||
boolean parametersShouldBeAccessedByIndex,
|
||||
int greatestParameterIndex) {
|
||||
|
||||
/*
|
||||
@@ -362,8 +366,10 @@ class StringQuery implements DeclaredQuery {
|
||||
String fixedPrefix = parametersShouldBeAccessedByIndex ? "?" : ":";
|
||||
|
||||
BiFunction<String, String, String> parameterNameToReplacement = (prefix, name) -> fixedPrefix + name;
|
||||
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(ValueExpressionParser.create(),
|
||||
indexToParameterName, parameterNameToReplacement);
|
||||
|
||||
return SpelQueryContext.of(indexToParameterName, parameterNameToReplacement).parse(queryWithSpel);
|
||||
return rewriter.parse(queryWithSpel);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -56,10 +56,11 @@ import org.springframework.data.repository.core.support.QueryCreationListener;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
|
||||
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -236,12 +237,13 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
return Optional.of(JpaQueryLookupStrategy.create(entityManager, queryMethodFactory, key, evaluationContextProvider,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(JpaQueryLookupStrategy.create(entityManager, queryMethodFactory, key,
|
||||
new CachingValueExpressionDelegate(valueExpressionDelegate),
|
||||
queryRewriterProvider, escapeCharacter));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
|
||||
@@ -309,6 +309,20 @@ class UserRepositoryFinderTests {
|
||||
assertThat(userRepository.findContainingEscaped("att\\_")).containsExactly(withEscapedWildcard);
|
||||
}
|
||||
|
||||
@Test // GH-3619
|
||||
void propertyPlaceholderInQuery() {
|
||||
|
||||
User extra = new User("extra", "Matt_ew", "extra");
|
||||
|
||||
userRepository.save(extra);
|
||||
|
||||
System.setProperty("query.lastname", "%_ew");
|
||||
assertThat(userRepository.findWithPropertyPlaceholder()).containsOnly(extra);
|
||||
|
||||
System.clearProperty("query.lastname");
|
||||
assertThat(userRepository.findWithPropertyPlaceholder()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-829
|
||||
void translatesContainsToMemberOf() {
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -37,8 +38,7 @@ import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@@ -67,7 +67,7 @@ class AbstractStringBasedJpaQueryIntegrationTests {
|
||||
|
||||
JpaQueryMethod method = getMethod("findRolesByEmailAddress", String.class);
|
||||
AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock, null, QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, new SpelExpressionParser());
|
||||
ValueExpressionDelegate.create());
|
||||
|
||||
jpaQuery.createJpaQuery(method.getAnnotatedQuery(), Sort.unsorted(), null,
|
||||
method.getResultProcessor().getReturnedType());
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -40,9 +41,8 @@ import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.ParametersSource;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -116,8 +116,7 @@ class AbstractStringBasedJpaQueryUnitTests {
|
||||
|
||||
Query query = AnnotatedElementUtils.getMergedAnnotation(respositoryMethod, Query.class);
|
||||
|
||||
return new InvocationCapturingStringQueryStub(respositoryMethod, queryMethod, query.value(), query.countQuery(),
|
||||
new SpelExpressionParser());
|
||||
return new InvocationCapturingStringQueryStub(respositoryMethod, queryMethod, query.value(), query.countQuery());
|
||||
|
||||
}
|
||||
|
||||
@@ -127,7 +126,7 @@ class AbstractStringBasedJpaQueryUnitTests {
|
||||
private final MultiValueMap<String, Arguments> capturedArguments = new LinkedMultiValueMap<>(3);
|
||||
|
||||
InvocationCapturingStringQueryStub(Method targetMethod, JpaQueryMethod queryMethod, String queryString,
|
||||
@Nullable String countQueryString, SpelExpressionParser parser) {
|
||||
@Nullable String countQueryString) {
|
||||
super(queryMethod, new Supplier<EntityManager>() {
|
||||
|
||||
@Override
|
||||
@@ -142,7 +141,7 @@ class AbstractStringBasedJpaQueryUnitTests {
|
||||
return em;
|
||||
}
|
||||
}.get(), queryString, countQueryString, Mockito.mock(QueryRewriter.class),
|
||||
Mockito.mock(QueryMethodEvaluationContextProvider.class), parser);
|
||||
ValueExpressionDelegate.create());
|
||||
|
||||
this.targetMethod = targetMethod;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ExpressionBasedStringQuery}.
|
||||
@@ -46,7 +47,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
|
||||
private static final ValueExpressionParser PARSER = ValueExpressionParser.create();
|
||||
@Mock JpaEntityMetadata<?> metadata;
|
||||
|
||||
@BeforeEach
|
||||
@@ -58,14 +59,14 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void shouldReturnQueryWithDomainTypeExpressionReplacedWithSimpleDomainTypeName() {
|
||||
|
||||
String source = "select u from #{#entityName} u where u.firstname like :firstname";
|
||||
StringQuery query = new ExpressionBasedStringQuery(source, metadata, SPEL_PARSER, false);
|
||||
StringQuery query = new ExpressionBasedStringQuery(source, metadata, PARSER, false);
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u where u.firstname like :firstname");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-424
|
||||
void renderAliasInExpressionQueryCorrectly() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u", metadata, SPEL_PARSER, true);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u", metadata, PARSER, true);
|
||||
assertThat(query.getAlias()).isEqualTo("u");
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u");
|
||||
}
|
||||
@@ -78,7 +79,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "AND (LOWER(n.server) LIKE LOWER(:#{#networkRequest.server})) OR :#{#networkRequest.server} IS NULL "
|
||||
+ "AND (n.createdAt >= :#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=:#{#networkRequest.createdTime.endDateTime}) "
|
||||
+ "AND (n.updatedAt >= :#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=:#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER, false);
|
||||
metadata, PARSER, false);
|
||||
|
||||
assertThat(query.getParameterBindings()).hasSize(8);
|
||||
}
|
||||
@@ -91,7 +92,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)"
|
||||
+ "AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})"
|
||||
+ "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER, false);
|
||||
metadata, PARSER, false);
|
||||
|
||||
assertThat(query.getParameterBindings()).hasSize(8);
|
||||
}
|
||||
@@ -104,7 +105,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)"
|
||||
+ "AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})"
|
||||
+ "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER, true);
|
||||
metadata, PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
@@ -112,7 +113,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, SPEL_PARSER, true);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
@@ -120,7 +121,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithoutSpelAsNative() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, SPEL_PARSER, true);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isTrue();
|
||||
}
|
||||
@@ -129,7 +130,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void namedExpressionsShouldCreateLikeBindings() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery(
|
||||
"select u from User u where u.firstname like %:#{foo} or u.firstname like :#{foo}%", metadata, SPEL_PARSER,
|
||||
"select u from User u where u.firstname like %:#{foo} or u.firstname like :#{foo}%", metadata, PARSER,
|
||||
false);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
@@ -154,7 +155,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void indexedExpressionsShouldCreateLikeBindings() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery(
|
||||
"select u from User u where u.firstname like %?#{foo} or u.firstname like ?#{foo}%", metadata, SPEL_PARSER,
|
||||
"select u from User u where u.firstname like %?#{foo} or u.firstname like ?#{foo}%", metadata, PARSER,
|
||||
false);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
@@ -179,7 +180,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
public void doesTemplatingWhenEntityNameSpelIsPresent() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select #{#entityName + 'Hallo'} from #{#entityName} u",
|
||||
metadata, SPEL_PARSER, false);
|
||||
metadata, PARSER, false);
|
||||
|
||||
assertThat(query.getQueryString()).isEqualTo("select UserHallo from User u");
|
||||
}
|
||||
@@ -188,7 +189,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
public void doesNoTemplatingWhenEntityNameSpelIsNotPresent() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select #{#entityName + 'Hallo'} from User u", metadata,
|
||||
SPEL_PARSER, false);
|
||||
PARSER, false);
|
||||
|
||||
assertThat(query.getQueryString()).isEqualTo("select UserHallo from User u");
|
||||
}
|
||||
@@ -197,7 +198,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
public void doesTemplatingWhenEntityNameSpelIsPresentForBindParameter() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u where name = :#{#something}",
|
||||
metadata, SPEL_PARSER, false);
|
||||
metadata, PARSER, false);
|
||||
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u where name = :__$synthetic$__1");
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.jpa.repository.query;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin;
|
||||
@@ -29,44 +30,45 @@ import org.springframework.data.repository.query.parser.Part.Type;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class LikeBindingUnitTests {
|
||||
|
||||
private static void assertAugmentedValue(Type type, Object value) {
|
||||
|
||||
LikeParameterBinding binding = new LikeParameterBinding(BindingIdentifier.of("foo"),
|
||||
ParameterOrigin.ofExpression("foo"), type);
|
||||
ParameterOrigin.ofParameter(1), type);
|
||||
assertThat(binding.prepare("value")).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsNullName() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new LikeParameterBinding(null, ParameterOrigin.ofExpression(""), Type.CONTAINING));
|
||||
.isThrownBy(() -> new LikeParameterBinding(null, ParameterOrigin.ofParameter(0), Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsEmptyName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new LikeParameterBinding(BindingIdentifier.of(""), ParameterOrigin.ofExpression(""), Type.CONTAINING));
|
||||
() -> new LikeParameterBinding(BindingIdentifier.of(""), ParameterOrigin.ofParameter(0), Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsNullType() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new LikeParameterBinding(BindingIdentifier.of("foo"), ParameterOrigin.ofExpression("foo"), null));
|
||||
() -> new LikeParameterBinding(BindingIdentifier.of("foo"), ParameterOrigin.ofParameter(0), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidType() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding(BindingIdentifier.of("foo"),
|
||||
ParameterOrigin.ofExpression("foo"), Type.SIMPLE_PROPERTY));
|
||||
ParameterOrigin.ofParameter(0), Type.SIMPLE_PROPERTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidPosition() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new LikeParameterBinding(BindingIdentifier.of(0), ParameterOrigin.ofExpression(""), Type.CONTAINING));
|
||||
() -> new LikeParameterBinding(BindingIdentifier.of(0), ParameterOrigin.ofParameter(0), Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -39,8 +39,7 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
@@ -83,8 +82,7 @@ class NativeJpaQueryUnitTests {
|
||||
Query annotation = AnnotatedElementUtils.getMergedAnnotation(respositoryMethod, Query.class);
|
||||
|
||||
NativeJpaQuery query = new NativeJpaQuery(queryMethod, em, annotation.value(), annotation.countQuery(),
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
new SpelExpressionParser());
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,10 +52,9 @@ import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -77,8 +76,6 @@ import org.springframework.lang.Nullable;
|
||||
class SimpleJpaQueryUnitTests {
|
||||
|
||||
private static final String USER_QUERY = "select u from User u";
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
private static final QueryMethodEvaluationContextProvider EVALUATION_CONTEXT_PROVIDER = QueryMethodEvaluationContextProvider.DEFAULT;
|
||||
|
||||
private JpaQueryMethod method;
|
||||
|
||||
@@ -123,7 +120,7 @@ class SimpleJpaQueryUnitTests {
|
||||
when(em.createQuery("foo", Long.class)).thenReturn(typedQuery);
|
||||
|
||||
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", null,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, EVALUATION_CONTEXT_PROVIDER, PARSER);
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
|
||||
assertThat(jpaQuery.createCountQuery(new JpaParametersParameterAccessor(method.getParameters(), new Object[] {})))
|
||||
.isEqualTo(typedQuery);
|
||||
@@ -138,7 +135,7 @@ class SimpleJpaQueryUnitTests {
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
|
||||
|
||||
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", null,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, EVALUATION_CONTEXT_PROVIDER, PARSER);
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
jpaQuery.createCountQuery(
|
||||
new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[] { PageRequest.of(1, 10) }));
|
||||
|
||||
@@ -154,7 +151,7 @@ class SimpleJpaQueryUnitTests {
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
|
||||
AbstractJpaQuery jpaQuery = JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em,
|
||||
queryMethod.getAnnotatedQuery(), null, QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
EVALUATION_CONTEXT_PROVIDER);
|
||||
ValueExpressionDelegate.create());
|
||||
|
||||
assertThat(jpaQuery).isInstanceOf(NativeJpaQuery.class);
|
||||
|
||||
@@ -174,7 +171,7 @@ class SimpleJpaQueryUnitTests {
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
|
||||
AbstractJpaQuery jpaQuery = JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em,
|
||||
queryMethod.getAnnotatedQuery(), null, QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
EVALUATION_CONTEXT_PROVIDER);
|
||||
ValueExpressionDelegate.create());
|
||||
|
||||
assertThat(jpaQuery).isInstanceOf(NativeJpaQuery.class);
|
||||
|
||||
@@ -287,7 +284,7 @@ class SimpleJpaQueryUnitTests {
|
||||
|
||||
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u",
|
||||
"select count(u.id) from #{#entityName} u", QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
EVALUATION_CONTEXT_PROVIDER, PARSER);
|
||||
ValueExpressionDelegate.create());
|
||||
jpaQuery.createCountQuery(
|
||||
new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[] { PageRequest.of(1, 10) }));
|
||||
|
||||
@@ -302,7 +299,7 @@ class SimpleJpaQueryUnitTests {
|
||||
private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, @Nullable String queryString, @Nullable String countQueryString) {
|
||||
|
||||
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em, queryString, countQueryString,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, EVALUATION_CONTEXT_PROVIDER);
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
}
|
||||
|
||||
private AbstractJpaQuery createJpaQuery(Method method, @Nullable Optional<String> countQueryString) {
|
||||
|
||||
@@ -415,13 +415,15 @@ class StringQueryUnitTests {
|
||||
assertNamedBinding(InParameterBinding.class, "ab1babc생일233", bindings.get(0));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-712
|
||||
@Test // DATAJPA-712, GH-3619
|
||||
void shouldReplaceAllNamedExpressionParametersWithInClause() {
|
||||
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in :#{#bs} and a.c in :#{#cs}", true);
|
||||
StringQuery query = new StringQuery(
|
||||
"select a from A a where a.b in :#{#bs} and a.c in :#{#cs} and a.d in :${foo.bar}", true);
|
||||
String queryString = query.getQueryString();
|
||||
|
||||
assertThat(queryString).isEqualTo("select a from A a where a.b in :__$synthetic$__1 and a.c in :__$synthetic$__2");
|
||||
assertThat(queryString).isEqualTo(
|
||||
"select a from A a where a.b in :__$synthetic$__1 and a.c in :__$synthetic$__2 and a.d in :__$synthetic$__3");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-712
|
||||
@@ -435,15 +437,21 @@ class StringQueryUnitTests {
|
||||
.isEqualTo("select a from A a where a.b LIKE :__$synthetic$__1 and a.c LIKE :__$synthetic$__2");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-712
|
||||
@Test // DATAJPA-712, GH-3619
|
||||
void shouldReplaceAllPositionExpressionParametersWithInClause() {
|
||||
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}", true);
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs} and a.d in ?${foo}",
|
||||
true);
|
||||
String queryString = query.getQueryString();
|
||||
|
||||
assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2");
|
||||
assertThat(((Expression) query.getParameterBindings().get(0).getOrigin()).expression()).isEqualTo("#bs");
|
||||
assertThat(((Expression) query.getParameterBindings().get(1).getOrigin()).expression()).isEqualTo("#cs");
|
||||
assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2 and a.d in ?3");
|
||||
|
||||
assertThat(((Expression) query.getParameterBindings().get(0).getOrigin()).expression().getExpressionString())
|
||||
.isEqualTo("#bs");
|
||||
assertThat(((Expression) query.getParameterBindings().get(1).getOrigin()).expression().getExpressionString())
|
||||
.isEqualTo("#cs");
|
||||
assertThat(((Expression) query.getParameterBindings().get(2).getOrigin()).expression().getExpressionString())
|
||||
.isEqualTo("${foo}");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-864
|
||||
@@ -480,7 +488,7 @@ class StringQueryUnitTests {
|
||||
for (ParameterBinding binding : bindings) {
|
||||
assertThat(binding.getName()).isNotNull();
|
||||
assertThat(query.getQueryString()).contains(binding.getName());
|
||||
assertThat(((Expression) binding.getOrigin()).expression()).isEqualTo("#exp");
|
||||
assertThat(((Expression) binding.getOrigin()).expression().getExpressionString()).isEqualTo("#exp");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -617,6 +617,10 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
@Query("select u from User u where u.lastname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
|
||||
List<User> findContainingEscaped(String namePart);
|
||||
|
||||
// GH-3619
|
||||
@Query("select u from User u where u.lastname like ?${query.lastname:empty}")
|
||||
List<User> findWithPropertyPlaceholder();
|
||||
|
||||
// DATAJPA-1303
|
||||
List<User> findByAttributesIgnoreCaseIn(Collection<String> attributes);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
** xref:jpa/entity-persistence.adoc[]
|
||||
** xref:repositories/query-methods-details.adoc[]
|
||||
** xref:jpa/query-methods.adoc[]
|
||||
** xref:jpa/value-expressions.adoc[]
|
||||
** xref:repositories/projections.adoc[]
|
||||
** xref:jpa/stored-procedures.adoc[]
|
||||
** xref:jpa/specifications.adoc[]
|
||||
|
||||
@@ -12,7 +12,7 @@ Derived queries with the predicates `IsStartingWith`, `StartingWith`, `StartsWit
|
||||
`IsNotContaining`, `NotContaining`, `NotContains`, `IsContaining`, `Containing`, `Contains` the respective arguments for these queries will get sanitized.
|
||||
This means if the arguments actually contain characters recognized by `LIKE` as wildcards these will get escaped so they match only as literals.
|
||||
The escape character used can be configured by setting the `escapeCharacter` of the `@EnableJpaRepositories` annotation.
|
||||
Compare with xref:jpa/query-methods.adoc#jpa.query.spel-expressions[Using SpEL Expressions].
|
||||
Compare with xref:jpa/query-methods.adoc#jpa.query.spel-expressions[Using Value Expressions].
|
||||
|
||||
[[jpa.query-methods.declared-queries]]
|
||||
=== Declared Queries
|
||||
@@ -401,13 +401,23 @@ NOTE: The method parameters are switched according to their order in the defined
|
||||
NOTE: As of version 4, Spring fully supports Java 8’s parameter name discovery based on the `-parameters` compiler flag. By using this flag in your build as an alternative to debug information, you can omit the `@Param` annotation for named parameters.
|
||||
|
||||
[[jpa.query.spel-expressions]]
|
||||
== Using SpEL Expressions
|
||||
== Using Expressions
|
||||
|
||||
As of Spring Data JPA release 1.4, we support the usage of restricted SpEL template expressions in manually defined queries that are defined with `@Query`. Upon the query being run, these expressions are evaluated against a predefined set of variables. Spring Data JPA supports a variable called `entityName`. Its usage is `select x from #{#entityName} x`. It inserts the `entityName` of the domain type associated with the given repository. The `entityName` is resolved as follows: If the domain type has set the name property on the `@Entity` annotation, it is used. Otherwise, the simple class-name of the domain type is used.
|
||||
We support the usage of restricted expressions in manually defined queries that are defined with `@Query`.
|
||||
Upon the query being run, these expressions are evaluated against a predefined set of variables.
|
||||
|
||||
NOTE: If you are not familiar with Value Expressions, please refer to xref:jpa/value-expressions.adoc[] to learn about SpEL Expressions and Property Placeholders.
|
||||
|
||||
Spring Data JPA supports a variable called `entityName`.
|
||||
Its usage is `select x from #{#entityName} x`.
|
||||
It inserts the `entityName` of the domain type associated with the given repository.
|
||||
The `entityName` is resolved as follows:
|
||||
* If the domain type has set the name property on the `@Entity` annotation, it is used.
|
||||
* Otherwise, the simple class-name of the domain type is used.
|
||||
|
||||
The following example demonstrates one use case for the `+#{#entityName}+` expression in a query string where you want to define a repository interface with a query method and a manually defined query:
|
||||
|
||||
.Using SpEL expressions in repository query methods - entityName
|
||||
.Using SpEL expressions in repository query methods: entityName
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@@ -431,13 +441,16 @@ public interface UserRepository extends JpaRepository<User,Long> {
|
||||
|
||||
To avoid stating the actual entity name in the query string of a `@Query` annotation, you can use the `+#{#entityName}+` variable.
|
||||
|
||||
NOTE: The `entityName` can be customized by using the `@Entity` annotation. Customizations in `orm.xml` are not supported for the SpEL expressions.
|
||||
NOTE: The `entityName` can be customized by using the `@Entity` annotation.
|
||||
Customizations in `orm.xml` are not supported for the SpEL expressions.
|
||||
|
||||
Of course, you could have just used `User` in the query declaration directly, but that would require you to change the query as well. The reference to `#entityName` picks up potential future remappings of the `User` class to a different entity name (for example, by using `@Entity(name = "MyUser")`.
|
||||
Of course, you could have just used `User` in the query declaration directly, but that would require you to change the query as well.
|
||||
The reference to `#entityName` picks up potential future remappings of the `User` class to a different entity name (for example, by using `@Entity(name = "MyUser")`.
|
||||
|
||||
Another use case for the `#{#entityName}` expression in a query string is if you want to define a generic repository interface with specialized repository interfaces for a concrete domain type. To not repeat the definition of custom query methods on the concrete interfaces, you can use the entity name expression in the query string of the `@Query` annotation in the generic repository interface, as shown in the following example:
|
||||
Another use case for the `#{#entityName}` expression in a query string is if you want to define a generic repository interface with specialized repository interfaces for a concrete domain type.
|
||||
To not repeat the definition of custom query methods on the concrete interfaces, you can use the entity name expression in the query string of the `@Query` annotation in the generic repository interface, as shown in the following example:
|
||||
|
||||
.Using SpEL expressions in repository query methods - entityName with inheritance
|
||||
.Using SpEL expressions in Repository Query Methods: entityName with Inheritance
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@@ -463,13 +476,15 @@ public interface ConcreteRepository
|
||||
----
|
||||
====
|
||||
|
||||
In the preceding example, the `MappedTypeRepository` interface is the common parent interface for a few domain types extending `AbstractMappedType`. It also defines the generic `findAllByAttribute(…)` method, which can be used on instances of the specialized repository interfaces. If you now invoke `findByAllAttribute(…)` on `ConcreteRepository`, the query becomes `select t from ConcreteType t where t.attribute = ?1`.
|
||||
In the preceding example, the `MappedTypeRepository` interface is the common parent interface for a few domain types extending `AbstractMappedType`.
|
||||
It also defines the generic `findAllByAttribute(…)` method, which can be used on instances of the specialized repository interfaces.
|
||||
If you now invoke `findByAllAttribute(…)` on `ConcreteRepository`, the query becomes `select t from ConcreteType t where t.attribute = ?1`.
|
||||
|
||||
SpEL expressions to manipulate arguments may also be used to manipulate method arguments.
|
||||
In these SpEL expressions the entity name is not available, but the arguments are.
|
||||
You can also use Expressions to control arguments may also be used to control method arguments.
|
||||
In these expressions the entity name is not available, but the arguments are.
|
||||
They can be accessed by name or index as demonstrated in the following example.
|
||||
|
||||
.Using SpEL expressions in repository query methods - accessing arguments.
|
||||
.Using Value Expressions in Repository Query Methods: Accessing Arguments
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@@ -482,7 +497,7 @@ For `like`-conditions one often wants to append `%` to the beginning or the end
|
||||
This can be done by appending or prefixing a bind parameter marker or a SpEL expression with `%`.
|
||||
Again the following example demonstrates this.
|
||||
|
||||
.Using SpEL expressions in repository query methods - wildcard shortcut.
|
||||
.Using Value Expressions in Repository Query Methods: Wildcard shortcut
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@@ -496,8 +511,7 @@ For this purpose the `escape(String)` method is made available in the SpEL conte
|
||||
It prefixes all instances of `_` and `%` in the first argument with the single character from the second argument.
|
||||
In combination with the `escape` clause of the `like` expression available in JPQL and standard SQL this allows easy cleaning of bind parameters.
|
||||
|
||||
|
||||
.Using SpEL expressions in repository query methods - sanitizing input values.
|
||||
.Using Value Expressions in Repository Query Methods: Sanitizing Input Values
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@@ -511,6 +525,19 @@ The escape character used can be configured by setting the `escapeCharacter` of
|
||||
Note that the method `escape(String)` available in the SpEL context will only escape the SQL and JPQL standard wildcards `_` and `%`.
|
||||
If the underlying database or the JPA implementation supports additional wildcards these will not get escaped.
|
||||
|
||||
.Using Value Expressions in Repository Query Methods: Configuration Properties
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Query("select u from User u where u.applicationName = ?${spring.application.name:unknown}")
|
||||
List<User> findContainingEscaped(String namePart);
|
||||
----
|
||||
====
|
||||
|
||||
You can refer in your query methods also to configuration property names including fallbacks if you wish to resolve a property from `Environment` during runtime.
|
||||
The property is being evaluated upon query execution.
|
||||
Typically, property placeholders resolve to String-like values.
|
||||
|
||||
[[jpa.query.other-methods]]
|
||||
== Other Methods
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$value-expressions.adoc[]
|
||||
Reference in New Issue
Block a user