Support for table names in SpEL expressions.

SpEL expressions in queries get processed in two steps:

1. First SpEL expressions outside parameters are detected and processed.
This is done with a `StandardEvaluationContext` with the variables `tableName` and `qualifiedTableName` added.
This step is introduced by this commit.

2. Parameters made up by SpEL expressions are processed as usual.

Closes #1856
Original pull request #1863
This commit is contained in:
Jens Schauder
2024-08-20 11:08:08 +02:00
committed by Mark Paluch
parent 4221840538
commit f937738038
16 changed files with 574 additions and 24 deletions

View File

@@ -128,7 +128,7 @@ public class JdbcQueryMethod extends QueryMethod {
return StringUtils.hasText(annotatedValue) ? annotatedValue : getNamedQuery();
}
String getRequiredQuery() {
public String getRequiredQuery() {
String query = getDeclaredQuery();

View File

@@ -35,6 +35,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.repository.query.QueryPreprocessor;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.query.Parameter;
@@ -103,11 +104,33 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param rowMapperFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @since 2.3
* @deprecated use alternative constructor
*/
@Deprecated(since = "3.4")
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod, operations, rowMapperFactory, converter, evaluationContextProvider, QueryPreprocessor.NOOP.transform(queryMethod.getRequiredQuery()));
}
/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapperFactory}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param rowMapperFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @param query
* @since 3.4
*/
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter,
QueryMethodEvaluationContextProvider evaluationContextProvider, String query) {
super(queryMethod, operations);
@@ -116,6 +139,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
this.converter = converter;
this.rowMapperFactory = rowMapperFactory;
if (queryMethod.isSliceQuery()) {
throw new UnsupportedOperationException(
"Slice queries are not supported using string-based queries; Offending method: " + queryMethod);
@@ -140,9 +164,9 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
.of((counter, expression) -> String.format("__$synthetic$__%d", counter + 1), String::concat)
.withEvaluationContextProvider(evaluationContextProvider);
this.query = queryMethod.getRequiredQuery();
this.spelEvaluator = queryContext.parse(query, getQueryMethod().getParameters());
this.containsSpelExpressions = !this.spelEvaluator.getQueryString().equals(query);
this.query = query;
this.spelEvaluator = queryContext.parse(this.query, getQueryMethod().getParameters());
this.containsSpelExpressions = !this.spelEvaluator.getQueryString().equals(this.query);
}
@Override

View File

@@ -36,6 +36,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
import org.springframework.data.relational.repository.support.RelationalQueryLookupStrategy;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
@@ -60,7 +61,7 @@ import org.springframework.util.Assert;
* @author Diego Krupitza
* @author Christopher Klein
*/
abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
private static final Log LOG = LogFactory.getLog(JdbcQueryLookupStrategy.class);
@@ -79,8 +80,10 @@ abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
super(context, dialect);
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
Assert.notNull(context, "RelationalMappingContext must not be null");
Assert.notNull(converter, "JdbcConverter must not be null");
Assert.notNull(dialect, "Dialect must not be null");
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
@@ -156,8 +159,10 @@ abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
"Query method %s is annotated with both, a query and a query name; Using the declared query", method));
}
String queryString = evaluateTableExpressions(repositoryMetadata, queryMethod.getRequiredQuery());
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, getOperations(), this::createMapper,
getConverter(), evaluationContextProvider);
getConverter(), evaluationContextProvider, queryString);
query.setBeanFactory(getBeanFactory());
return query;
}