Skip to content

Add support for Value Expressions for Repository Query methods #3627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data JPA Parent</name>
Expand Down Expand Up @@ -41,7 +41,7 @@
<jsqlparser>5.0</jsqlparser>
<mysql-connector-java>8.0.33</mysql-connector-java>
<postgresql>42.6.0</postgresql>
<springdata.commons>3.4.0-SNAPSHOT</springdata.commons>
<springdata.commons>3.4.0-GH-3049-SNAPSHOT</springdata.commons>
<vavr>0.10.3</vavr>

<hibernate.groupId>org.hibernate</hibernate.groupId>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-envers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-jpa-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-jpa-performance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3619-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -50,12 +50,12 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {

private final DeclaredQuery query;
private final Lazy<DeclaredQuery> countQuery;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
private final ValueExpressionDelegate valueExpressionDelegate;
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
private final QueryRewriter queryRewriter;
private final QuerySortRewriter querySortRewriter;
private final Lazy<ParameterBinder> countParameterBinder;
private final ValueEvaluationContextProvider valueExpressionContextProvider;

/**
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
Expand All @@ -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());
}

Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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));
}
Expand All @@ -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);
}

Expand All @@ -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");
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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}.
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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));
};
}

/**
Expand Down
Loading