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

@@ -41,6 +41,7 @@ import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.r2dbc.query.UpdateMapper;
import org.springframework.data.r2dbc.support.ArrayUtils;
import org.springframework.data.relational.core.dialect.ArrayColumns;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.RenderContextFactory;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -310,6 +311,14 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra
return dialect.renderForGeneratedValues(identifier);
}
/**
* @since 3.4
*/
@Override
public Dialect getDialect() {
return dialect;
}
private RelationalPersistentEntity<?> getRequiredPersistentEntity(Class<?> typeToRead) {
return this.mappingContext.getRequiredPersistentEntity(typeToRead);
}

View File

@@ -25,6 +25,8 @@ import java.util.function.BiFunction;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.relational.core.dialect.AnsiDialect;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.domain.RowDocument;
@@ -154,6 +156,14 @@ public interface ReactiveDataAccessStrategy {
return identifier.toSql(IdentifierProcessing.NONE);
}
/**
* @return the {@link Dialect} used by this strategy.
* @since 3.4
*/
default Dialect getDialect() {
return AnsiDialect.INSTANCE;
}
/**
* Interface to retrieve parameters for named parameter processing.
*/

View File

@@ -32,6 +32,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEnti
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation;
import org.springframework.data.relational.repository.support.RelationalQueryLookupStrategy;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -51,6 +52,7 @@ import org.springframework.util.Assert;
* Factory to create {@link R2dbcRepository} instances.
*
* @author Mark Paluch
* @author Jens Schauder
*/
public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
@@ -139,8 +141,9 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
* {@link QueryLookupStrategy} to create R2DBC queries..
*
* @author Mark Paluch
* @author Jens Schauder
*/
private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy {
private static class R2dbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
private final R2dbcEntityOperations entityOperations;
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
@@ -151,30 +154,34 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
R2dbcQueryLookupStrategy(R2dbcEntityOperations entityOperations,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter,
ReactiveDataAccessStrategy dataAccessStrategy) {
super(converter.getMappingContext(), dataAccessStrategy.getDialect());
this.entityOperations = entityOperations;
this.evaluationContextProvider = evaluationContextProvider;
this.converter = converter;
this.dataAccessStrategy = dataAccessStrategy;
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext = this.converter.getMappingContext();
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory,
this.converter.getMappingContext());
mappingContext);
String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.entityOperations, this.converter,
if (namedQueries.hasQuery(namedQueryName) || queryMethod.hasAnnotatedQuery()) {
String query = namedQueries.hasQuery(namedQueryName) ? namedQueries.getQuery(namedQueryName) : queryMethod.getRequiredAnnotatedQuery();
query = evaluateTableExpressions(metadata, query);
return new StringBasedR2dbcQuery(query, queryMethod, this.entityOperations, this.converter,
this.dataAccessStrategy,
parser, this.evaluationContextProvider);
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy,
this.parser,
this.evaluationContextProvider);
} else {
return new PartTreeR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy);
}