diff --git a/pom.xml b/pom.xml
index 2311b378e6..93a502784e 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
pom
Spring Data JPA Parent
@@ -41,7 +41,7 @@
5.0
8.0.33
42.6.0
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3049-SNAPSHOT
0.10.3
org.hibernate
diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml
index 8b836ae2f3..60fc0a4b6a 100755
--- a/spring-data-envers/pom.xml
+++ b/spring-data-envers/pom.xml
@@ -5,12 +5,12 @@
org.springframework.data
spring-data-envers
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml
index e9e1754bc3..c04612c867 100644
--- a/spring-data-jpa-distribution/pom.xml
+++ b/spring-data-jpa-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa-performance/pom.xml b/spring-data-jpa-performance/pom.xml
index fa836f34c6..d6f3df8a50 100644
--- a/spring-data-jpa-performance/pom.xml
+++ b/spring-data-jpa-performance/pom.xml
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml
index 3006702796..df991fc501 100644
--- a/spring-data-jpa/pom.xml
+++ b/spring-data-jpa/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-jpa
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3619-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
index c60da04424..0d257fe5a2 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
@@ -22,12 +22,12 @@
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 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 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 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
return this.createBinder(this.countQuery.get());
});
- this.parser = parser;
this.queryRewriter = queryRewriter;
JpaParameters parameters = method.getParameters();
@@ -140,8 +138,8 @@ protected ParameterBinder createBinder() {
}
protected ParameterBinder createBinder(DeclaredQuery query) {
- return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query, parser,
- evaluationContextProvider);
+ return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
+ valueExpressionDelegate, valueExpressionContextProvider);
}
@Override
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java
index 411c2662d5..8066504ca3 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java
@@ -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 @@ public ExpressionBasedStringQuery(String query, JpaEntityMetadata> metadata, S
* @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 @@ static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata>
* @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 @@ private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEnti
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;
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java
index 8f1d3247df..a998e1a086 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java
@@ -19,9 +19,8 @@
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);
}
/**
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java
index 8cf8d12125..136a16b271 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java
@@ -21,6 +21,8 @@
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.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 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
*/
private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
- private final QueryMethodEvaluationContextProvider evaluationContextProvider;
+ private final ValueExpressionDelegate valueExpressionDelegate;
/**
* Creates a new {@link DeclaredQueryLookupStrategy}.
@@ -145,11 +149,11 @@ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStra
* @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 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
}
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 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
* @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");
-
- 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));
- }
+ Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
+
+ 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));
+ };
}
/**
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
index 7b8a018a8c..61c4c6ef19 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
@@ -25,10 +25,9 @@
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 annotation = annotations.get(NativeQuery.class);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java
index 898cd73936..e5122e93d3 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java
@@ -18,12 +18,12 @@
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 @@ static ParameterBinder createCriteriaBinder(JpaParameters parameters, List bindings = query.getParameterBindings();
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
- evaluationContextProvider, parameters);
+ evaluationContextProvider);
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java
index 4778943f68..493f474f6f 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java
@@ -23,6 +23,7 @@
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 @@ public String toString() {
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 @@ static MethodInvocationArgument ofParameter(BindingIdentifier identifier) {
* @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() {
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java
index 9e27184582..294ec89c88 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java
@@ -21,6 +21,10 @@
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.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 @@ static QueryParameterSetterFactory forCriteriaQuery(JpaParameters parameters, Li
* @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 @@ static JpaParameter findParameterForBinding(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 @@ public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery decla
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 @@ public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery decla
* @return the result of the evaluation.
*/
@Nullable
- private Object evaluateExpression(Expression expression, JpaParametersParameterAccessor accessor) {
-
- EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues());
+ private Object evaluateExpression(ValueExpression expression, JpaParametersParameterAccessor accessor) {
- return expression.getValue(context, Object.class);
+ ValueEvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(accessor.getValues());
+ return expression.evaluate(evaluationContext);
}
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java
index 5174168501..16fa3c30e0 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java
@@ -19,9 +19,8 @@
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 @@ public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String
* @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);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java
index 3b365926db..471ede0403 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java
@@ -26,13 +26,14 @@
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 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
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 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
while (matcher.find()) {
- if (spelExtractor.isQuoted(matcher.start())) {
+ if (parsedQuery.isQuoted(matcher.start())) {
continue;
}
@@ -282,7 +284,8 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
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 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
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 @@ private static SpelExtractor createSpelExtractor(String queryWithSpel, boolean p
String fixedPrefix = parametersShouldBeAccessedByIndex ? "?" : ":";
BiFunction 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
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
index bffdafd46b..7ecd163244 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
@@ -56,10 +56,11 @@
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 @@ protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFa
@Override
protected Optional 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 JpaEntityInformation getEntityInformation(Class domainClass) {
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java
index 6a8c06789d..259420a09a 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java
@@ -309,6 +309,20 @@ void escapingInLikeSpelsInThePresenceOfEscapedWildcards() {
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() {
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java
index 6e6c605114..9cb74dcaa0 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java
@@ -26,6 +26,7 @@
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.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 @@ void createsNormalQueryForJpaManagedReturnTypes() throws Exception {
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());
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java
index 5a33a4bf59..b2e6ba4fce 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java
@@ -29,6 +29,7 @@
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.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 @@ static InvocationCapturingStringQueryStub forMethod(Class> repository, String
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 @@ static class InvocationCapturingStringQueryStub extends AbstractStringBasedJpaQu
private final MultiValueMap capturedArguments = new LinkedMultiValueMap<>(3);
InvocationCapturingStringQueryStub(Method targetMethod, JpaQueryMethod queryMethod, String queryString,
- @Nullable String countQueryString, SpelExpressionParser parser) {
+ @Nullable String countQueryString) {
super(queryMethod, new Supplier() {
@Override
@@ -142,7 +141,7 @@ public EntityManager get() {
return em;
}
}.get(), queryString, countQueryString, Mockito.mock(QueryRewriter.class),
- Mockito.mock(QueryMethodEvaluationContextProvider.class), parser);
+ ValueExpressionDelegate.create());
this.targetMethod = targetMethod;
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java
index e92c16eb88..d651db1393 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java
@@ -27,9 +27,10 @@
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 @@
@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 @@ void setUp() {
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 @@ void shouldDetectBindParameterCountCorrectly() {
+ "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 @@ void shouldDetectBindParameterCountCorrectlyWithJDBCStyleParameters() {
+ "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 @@ void shouldDetectComplexNativeQueriesWithSpelAsNonNative() {
+ "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 @@ void shouldDetectComplexNativeQueriesWithSpelAsNonNative() {
@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 @@ void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() {
@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 @@ void shouldDetectSimpleNativeQueriesWithoutSpelAsNative() {
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 @@ void namedExpressionsShouldCreateLikeBindings() {
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 @@ void indexedExpressionsShouldCreateLikeBindings() {
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 @@ public void doesTemplatingWhenEntityNameSpelIsPresent() {
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 @@ public void doesNoTemplatingWhenEntityNameSpelIsNotPresent() {
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");
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java
index 54b2e8bad3..c67b1ce836 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java
@@ -18,6 +18,7 @@
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 @@
* @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
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java
index 67c61b9305..7a9cf35d1f 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java
@@ -39,8 +39,7 @@
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 @@ private NativeJpaQuery getQuery(Class> repository, String method, Class>...
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;
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
index 014dee96f7..e5a6b04334 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
@@ -52,10 +52,9 @@
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 @@
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 @@ void prefersDeclaredCountQueryOverCreatingOne() throws Exception {
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 @@ void doesNotApplyPaginationToCountQuery() throws Exception {
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 @@ void discoversNativeQuery() throws Exception {
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 @@ void discoversNativeQueryFromNativeQueryInterface() throws Exception {
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 @@ void resolvesExpressionInCountQuery() throws Exception {
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 @@ private AbstractJpaQuery createJpaQuery(Method method) {
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 countQueryString) {
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java
index f54272b0a7..b7cb49db74 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java
@@ -415,13 +415,15 @@ void detectsInBindingWithSpecialCharactersAndWordCharactersMixedInParentheses()
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 @@ void shouldReplaceExpressionWithLikeParameters() {
.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 @@ void bindingsMatchQueryForIdenticalSpelExpressions() {
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");
}
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java
index 86d97ea40c..01526cd0bf 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java
@@ -617,6 +617,10 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity
@Query("select u from User u where u.lastname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List findContainingEscaped(String namePart);
+ // GH-3619
+ @Query("select u from User u where u.lastname like ?${query.lastname:empty}")
+ List findWithPropertyPlaceholder();
+
// DATAJPA-1303
List findByAttributesIgnoreCaseIn(Collection attributes);
diff --git a/src/main/antora/modules/ROOT/nav.adoc b/src/main/antora/modules/ROOT/nav.adoc
index 501232f800..1e44d61f58 100644
--- a/src/main/antora/modules/ROOT/nav.adoc
+++ b/src/main/antora/modules/ROOT/nav.adoc
@@ -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[]
diff --git a/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc b/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc
index 33cdd8bd08..80737f73ad 100644
--- a/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc
+++ b/src/main/antora/modules/ROOT/pages/jpa/query-methods.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
@@ -390,13 +390,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]
----
@@ -420,13 +430,16 @@ public interface UserRepository extends JpaRepository {
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]
----
@@ -452,13 +465,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]
----
@@ -471,7 +486,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]
----
@@ -485,8 +500,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]
----
@@ -500,6 +514,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 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
diff --git a/src/main/antora/modules/ROOT/pages/jpa/value-expressions.adoc b/src/main/antora/modules/ROOT/pages/jpa/value-expressions.adoc
new file mode 100644
index 0000000000..6356a46265
--- /dev/null
+++ b/src/main/antora/modules/ROOT/pages/jpa/value-expressions.adoc
@@ -0,0 +1 @@
+include::{commons}@data-commons::page$value-expressions.adoc[]