Skip to content

Commit f045352

Browse files
committed
Add support for Value Expressions.
Closes #3619 Original pull request: #3627
1 parent 61e6d36 commit f045352

23 files changed

+250
-188
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java

+13-15
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222

2323
import org.springframework.data.domain.Pageable;
2424
import org.springframework.data.domain.Sort;
25+
import org.springframework.data.expression.ValueEvaluationContextProvider;
2526
import org.springframework.data.jpa.repository.QueryRewriter;
26-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2727
import org.springframework.data.repository.query.ResultProcessor;
2828
import org.springframework.data.repository.query.ReturnedType;
29+
import org.springframework.data.repository.query.ValueExpressionDelegate;
2930
import org.springframework.data.util.Lazy;
30-
import org.springframework.expression.spel.standard.SpelExpressionParser;
3131
import org.springframework.lang.Nullable;
3232
import org.springframework.util.Assert;
3333
import org.springframework.util.ConcurrentLruCache;
@@ -50,12 +50,12 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
5050

5151
private final DeclaredQuery query;
5252
private final Lazy<DeclaredQuery> countQuery;
53-
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
54-
private final SpelExpressionParser parser;
53+
private final ValueExpressionDelegate valueExpressionDelegate;
5554
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
5655
private final QueryRewriter queryRewriter;
5756
private final QuerySortRewriter querySortRewriter;
5857
private final Lazy<ParameterBinder> countParameterBinder;
58+
private final ValueEvaluationContextProvider valueExpressionContextProvider;
5959

6060
/**
6161
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
@@ -65,30 +65,29 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6565
* @param em must not be {@literal null}.
6666
* @param queryString must not be {@literal null}.
6767
* @param countQueryString must not be {@literal null}.
68-
* @param evaluationContextProvider must not be {@literal null}.
69-
* @param parser must not be {@literal null}.
7068
* @param queryRewriter must not be {@literal null}.
69+
* @param valueExpressionDelegate must not be {@literal null}.
7170
*/
7271
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
7372
@Nullable String countQueryString, QueryRewriter queryRewriter,
74-
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
73+
ValueExpressionDelegate valueExpressionDelegate) {
7574

7675
super(method, em);
7776

7877
Assert.hasText(queryString, "Query string must not be null or empty");
79-
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
80-
Assert.notNull(parser, "Parser must not be null");
78+
Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null");
8179
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
8280

83-
this.evaluationContextProvider = evaluationContextProvider;
84-
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
81+
this.valueExpressionDelegate = valueExpressionDelegate;
82+
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
83+
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate,
8584
method.isNativeQuery());
8685

8786
this.countQuery = Lazy.of(() -> {
8887

8988
if (StringUtils.hasText(countQueryString)) {
9089

91-
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), parser,
90+
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate,
9291
method.isNativeQuery());
9392
}
9493

@@ -99,7 +98,6 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
9998
return this.createBinder(this.countQuery.get());
10099
});
101100

102-
this.parser = parser;
103101
this.queryRewriter = queryRewriter;
104102

105103
JpaParameters parameters = method.getParameters();
@@ -140,8 +138,8 @@ protected ParameterBinder createBinder() {
140138
}
141139

142140
protected ParameterBinder createBinder(DeclaredQuery query) {
143-
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query, parser,
144-
evaluationContextProvider);
141+
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
142+
valueExpressionDelegate, valueExpressionContextProvider);
145143
}
146144

147145
@Override

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java

+9-8
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import java.util.Objects;
1819
import java.util.regex.Pattern;
1920

21+
import org.springframework.data.expression.ValueEvaluationContext;
22+
import org.springframework.data.expression.ValueExpression;
23+
import org.springframework.data.expression.ValueExpressionParser;
2024
import org.springframework.data.repository.core.EntityMetadata;
21-
import org.springframework.expression.Expression;
22-
import org.springframework.expression.ParserContext;
23-
import org.springframework.expression.spel.standard.SpelExpressionParser;
2425
import org.springframework.expression.spel.support.StandardEvaluationContext;
2526
import org.springframework.util.Assert;
2627

@@ -59,7 +60,7 @@ class ExpressionBasedStringQuery extends StringQuery {
5960
* @param parser must not be {@literal null}.
6061
* @param nativeQuery is a given query is native or not
6162
*/
62-
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
63+
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, ValueExpressionParser parser,
6364
boolean nativeQuery) {
6465
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
6566
}
@@ -74,7 +75,7 @@ public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, S
7475
* @return A query supporting SpEL expressions.
7576
*/
7677
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
77-
SpelExpressionParser parser, boolean nativeQuery) {
78+
ValueExpressionParser parser, boolean nativeQuery) {
7879
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
7980
}
8081

@@ -84,7 +85,7 @@ static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?>
8485
* @param parser Must not be {@literal null}.
8586
*/
8687
private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata<?> metadata,
87-
SpelExpressionParser parser) {
88+
ValueExpressionParser parser) {
8889

8990
Assert.notNull(query, "query must not be null");
9091
Assert.notNull(metadata, "metadata must not be null");
@@ -99,9 +100,9 @@ private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEnti
99100

100101
query = potentiallyQuoteExpressionsParameter(query);
101102

102-
Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION);
103+
ValueExpression expr = parser.parse(query);
103104

104-
String result = expr.getValue(evalContext, String.class);
105+
String result = Objects.toString(expr.evaluate(ValueEvaluationContext.of(null, evalContext)));
105106

106107
if (result == null) {
107108
return query;

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java

+4-16
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919

2020
import org.springframework.data.jpa.repository.QueryRewriter;
2121
import org.springframework.data.repository.query.QueryCreationException;
22-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2322
import org.springframework.data.repository.query.RepositoryQuery;
24-
import org.springframework.expression.spel.standard.SpelExpressionParser;
23+
import org.springframework.data.repository.query.ValueExpressionDelegate;
2524
import org.springframework.lang.Nullable;
2625

2726
/**
@@ -34,31 +33,20 @@ enum JpaQueryFactory {
3433

3534
INSTANCE;
3635

37-
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
38-
3936
/**
4037
* Creates a {@link RepositoryQuery} from the given {@link String} query.
41-
*
42-
* @param method must not be {@literal null}.
43-
* @param em must not be {@literal null}.
44-
* @param countQueryString
45-
* @param queryString must not be {@literal null}.
46-
* @param evaluationContextProvider
47-
* @return
4838
*/
4939
AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,
5040
@Nullable String countQueryString, QueryRewriter queryRewriter,
51-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
41+
ValueExpressionDelegate valueExpressionDelegate) {
5242

5343
if (method.isScrollQuery()) {
5444
throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries");
5545
}
5646

5747
return method.isNativeQuery()
58-
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,
59-
PARSER)
60-
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,
61-
PARSER);
48+
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate)
49+
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
6250
}
6351

6452
/**

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java

+44-21
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
24+
25+
import org.springframework.core.env.StandardEnvironment;
2426
import org.springframework.data.jpa.repository.Query;
2527
import org.springframework.data.jpa.repository.QueryRewriter;
2628
import org.springframework.data.projection.ProjectionFactory;
@@ -30,7 +32,9 @@
3032
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
3133
import org.springframework.data.repository.query.QueryMethod;
3234
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
35+
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
3336
import org.springframework.data.repository.query.RepositoryQuery;
37+
import org.springframework.data.repository.query.ValueExpressionDelegate;
3438
import org.springframework.lang.Nullable;
3539
import org.springframework.util.Assert;
3640
import org.springframework.util.StringUtils;
@@ -135,7 +139,7 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
135139
*/
136140
private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
137141

138-
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
142+
private final ValueExpressionDelegate valueExpressionDelegate;
139143

140144
/**
141145
* Creates a new {@link DeclaredQueryLookupStrategy}.
@@ -145,11 +149,11 @@ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStra
145149
* @param evaluationContextProvider must not be {@literal null}.
146150
*/
147151
public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
148-
QueryMethodEvaluationContextProvider evaluationContextProvider, QueryRewriterProvider queryRewriterProvider) {
152+
ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider) {
149153

150154
super(em, queryMethodFactory, queryRewriterProvider);
151155

152-
this.evaluationContextProvider = evaluationContextProvider;
156+
this.valueExpressionDelegate = delegate;
153157
}
154158

155159
@Override
@@ -168,13 +172,13 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
168172
}
169173

170174
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),
171-
getCountQuery(method, namedQueries, em), queryRewriter, evaluationContextProvider);
175+
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
172176
}
173177

174178
String name = method.getNamedQueryName();
175179
if (namedQueries.hasQuery(name)) {
176180
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
177-
getCountQuery(method, namedQueries, em), queryRewriter, evaluationContextProvider);
181+
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
178182
}
179183

180184
RepositoryQuery query = NamedQuery.lookupFrom(method, em);
@@ -267,28 +271,47 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
267271
* @param key may be {@literal null}.
268272
* @param evaluationContextProvider must not be {@literal null}.
269273
* @param escape must not be {@literal null}.
274+
* @deprecated since 3.4, use
275+
* {@link #create(EntityManager, JpaQueryMethodFactory, Key, ValueExpressionDelegate, QueryRewriterProvider, EscapeCharacter)}
276+
* instead.
270277
*/
278+
@Deprecated(since = "3.4")
271279
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
272280
@Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider,
273281
QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape) {
282+
return create(em, queryMethodFactory, key,
283+
new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(),
284+
evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionDelegate.create()),
285+
queryRewriterProvider, escape);
286+
}
287+
288+
/**
289+
* Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}.
290+
*
291+
* @param em must not be {@literal null}.
292+
* @param queryMethodFactory must not be {@literal null}.
293+
* @param key may be {@literal null}.
294+
* @param delegate must not be {@literal null}.
295+
* @param queryRewriterProvider must not be {@literal null}.
296+
* @param escape must not be {@literal null}.
297+
*/
298+
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
299+
@Nullable Key key, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider,
300+
EscapeCharacter escape) {
274301

275302
Assert.notNull(em, "EntityManager must not be null");
276-
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
277-
278-
switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
279-
case CREATE:
280-
return new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
281-
case USE_DECLARED_QUERY:
282-
return new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider,
283-
queryRewriterProvider);
284-
case CREATE_IF_NOT_FOUND:
285-
return new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
286-
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
287-
new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider, queryRewriterProvider),
288-
queryRewriterProvider);
289-
default:
290-
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
291-
}
303+
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
304+
305+
return switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
306+
case CREATE -> new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
307+
case USE_DECLARED_QUERY ->
308+
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider);
309+
case CREATE_IF_NOT_FOUND -> new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
310+
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
311+
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider),
312+
queryRewriterProvider);
313+
default -> throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
314+
};
292315
}
293316

294317
/**

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@
2525
import org.springframework.data.domain.Sort;
2626
import org.springframework.data.jpa.repository.NativeQuery;
2727
import org.springframework.data.jpa.repository.QueryRewriter;
28-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2928
import org.springframework.data.repository.query.RepositoryQuery;
3029
import org.springframework.data.repository.query.ReturnedType;
31-
import org.springframework.expression.spel.standard.SpelExpressionParser;
30+
import org.springframework.data.repository.query.ValueExpressionDelegate;
3231
import org.springframework.lang.Nullable;
3332
import org.springframework.util.ObjectUtils;
3433

@@ -57,12 +56,12 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
5756
* @param queryString must not be {@literal null} or empty.
5857
* @param countQueryString must not be {@literal null} or empty.
5958
* @param rewriter the query rewriter to use.
59+
* @param valueExpressionDelegate must not be {@literal null}.
6060
*/
6161
public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString,
62-
QueryRewriter rewriter, QueryMethodEvaluationContextProvider evaluationContextProvider,
63-
SpelExpressionParser parser) {
62+
QueryRewriter rewriter, ValueExpressionDelegate valueExpressionDelegate) {
6463

65-
super(method, em, queryString, countQueryString, rewriter, evaluationContextProvider, parser);
64+
super(method, em, queryString, countQueryString, rewriter, valueExpressionDelegate);
6665

6766
MergedAnnotations annotations = MergedAnnotations.from(method.getMethod());
6867
MergedAnnotation<NativeQuery> annotation = annotations.get(NativeQuery.class);

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
import java.util.ArrayList;
1919
import java.util.List;
2020

21+
import org.springframework.data.expression.ValueEvaluationContextProvider;
22+
import org.springframework.data.expression.ValueExpressionParser;
2123
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
2224
import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier;
2325
import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin;
2426
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
25-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
26-
import org.springframework.expression.spel.standard.SpelExpressionParser;
2727
import org.springframework.util.Assert;
2828

2929
/**
@@ -86,7 +86,7 @@ static ParameterBinder createCriteriaBinder(JpaParameters parameters, List<Param
8686
* {@link jakarta.persistence.Query} while processing SpEL expressions where applicable.
8787
*/
8888
static ParameterBinder createQueryAwareBinder(JpaParameters parameters, DeclaredQuery query,
89-
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
89+
ValueExpressionParser parser, ValueEvaluationContextProvider evaluationContextProvider) {
9090

9191
Assert.notNull(parameters, "JpaParameters must not be null");
9292
Assert.notNull(query, "StringQuery must not be null");
@@ -95,7 +95,7 @@ static ParameterBinder createQueryAwareBinder(JpaParameters parameters, Declared
9595

9696
List<ParameterBinding> bindings = query.getParameterBindings();
9797
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
98-
evaluationContextProvider, parameters);
98+
evaluationContextProvider);
9999

100100
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
101101

0 commit comments

Comments
 (0)