Polishing.
Simplify R2DBC expression handling. Use new ValueExpression API instead of holding parameter binding duplicates. Reformat code. Add author tags. See #1904 Original pull request: #1906
This commit is contained in:
@@ -20,18 +20,17 @@ import static org.springframework.data.jdbc.repository.query.JdbcQueryExecution.
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.sql.SQLType;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
|
||||
@@ -51,7 +50,6 @@ import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
@@ -74,6 +72,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Chirag Tailor
|
||||
* @author Christopher Klein
|
||||
* @author Mikhail Polivakha
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 2.0
|
||||
*/
|
||||
public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
@@ -82,13 +81,11 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
private final JdbcConverter converter;
|
||||
private final RowMapperFactory rowMapperFactory;
|
||||
private final ValueExpressionQueryRewriter.ParsedQuery parsedQuery;
|
||||
private final boolean containsSpelExpressions;
|
||||
private final String query;
|
||||
|
||||
private final CachedRowMapperFactory cachedRowMapperFactory;
|
||||
private final CachedResultSetExtractorFactory cachedResultSetExtractorFactory;
|
||||
private final ValueExpressionDelegate delegate;
|
||||
private final List<Map.Entry<String, String>> parameterBindings;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
|
||||
@@ -186,17 +183,11 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
this.cachedResultSetExtractorFactory = new CachedResultSetExtractorFactory(
|
||||
this.cachedRowMapperFactory::getRowMapper);
|
||||
|
||||
this.parameterBindings = new ArrayList<>();
|
||||
|
||||
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(delegate, (counter, expression) -> {
|
||||
String newName = String.format("__$synthetic$__%d", counter + 1);
|
||||
parameterBindings.add(new AbstractMap.SimpleEntry<>(newName, expression));
|
||||
return newName;
|
||||
}, String::concat);
|
||||
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(delegate,
|
||||
(counter, expression) -> String.format("__$synthetic$__%d", counter + 1), String::concat);
|
||||
|
||||
this.query = query;
|
||||
this.parsedQuery = rewriter.parse(this.query);
|
||||
this.containsSpelExpressions = !this.parsedQuery.getQueryString().equals(this.query);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@@ -217,9 +208,10 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
|
||||
RowMapperFactory rowMapperFactory, JdbcConverter converter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(null,
|
||||
rootObject -> evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(), new Object[] { rootObject })), ValueExpressionParser.create(
|
||||
SpelExpressionParser::new)));
|
||||
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(
|
||||
new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), rootObject -> evaluationContextProvider
|
||||
.getEvaluationContext(queryMethod.getParameters(), new Object[] { rootObject })),
|
||||
ValueExpressionParser.create()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,18 +223,22 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
JdbcQueryExecution<?> queryExecution = createJdbcQueryExecution(accessor, processor);
|
||||
MapSqlParameterSource parameterMap = this.bindParameters(accessor);
|
||||
|
||||
return queryExecution.execute(processSpelExpressions(objects, accessor.getBindableParameters(), parameterMap), parameterMap);
|
||||
return queryExecution.execute(evaluateExpressions(objects, accessor.getBindableParameters(), parameterMap),
|
||||
parameterMap);
|
||||
}
|
||||
|
||||
private String processSpelExpressions(Object[] objects, Parameters<?, ?> bindableParameters, MapSqlParameterSource parameterMap) {
|
||||
private String evaluateExpressions(Object[] objects, Parameters<?, ?> bindableParameters,
|
||||
MapSqlParameterSource parameterMap) {
|
||||
|
||||
if (parsedQuery.hasParameterBindings()) {
|
||||
|
||||
if (containsSpelExpressions) {
|
||||
ValueEvaluationContext evaluationContext = delegate.createValueContextProvider(bindableParameters)
|
||||
.getEvaluationContext(objects);
|
||||
for (Map.Entry<String, String> entry : parameterBindings) {
|
||||
parameterMap.addValue(
|
||||
entry.getKey(), delegate.parse(entry.getValue()).evaluate(evaluationContext));
|
||||
}
|
||||
|
||||
parsedQuery.getParameterMap().forEach((paramName, valueExpression) -> {
|
||||
parameterMap.addValue(paramName, valueExpression.evaluate(evaluationContext));
|
||||
});
|
||||
|
||||
return parsedQuery.getQueryString();
|
||||
}
|
||||
|
||||
@@ -254,13 +250,12 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
|
||||
if (getQueryMethod().isModifyingQuery()) {
|
||||
return createModifyingQueryExecutor();
|
||||
} else {
|
||||
|
||||
Supplier<RowMapper<?>> rowMapper = () -> determineRowMapper(processor, accessor.findDynamicProjection() != null);
|
||||
ResultSetExtractor<Object> resultSetExtractor = determineResultSetExtractor(rowMapper);
|
||||
|
||||
return createReadingQueryExecution(resultSetExtractor, rowMapper);
|
||||
}
|
||||
|
||||
Supplier<RowMapper<?>> rowMapper = () -> determineRowMapper(processor, accessor.findDynamicProjection() != null);
|
||||
ResultSetExtractor<Object> resultSetExtractor = determineResultSetExtractor(rowMapper);
|
||||
|
||||
return createReadingQueryExecution(resultSetExtractor, rowMapper);
|
||||
}
|
||||
|
||||
private MapSqlParameterSource bindParameters(RelationalParameterAccessor accessor) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
@@ -48,6 +49,7 @@ import org.springframework.util.Assert;
|
||||
* @author Hebert Coelho
|
||||
* @author Diego Krupitza
|
||||
* @author Christopher Klein
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@@ -57,7 +59,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
private final Dialect dialect;
|
||||
@Nullable private BeanFactory beanFactory;
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
|
||||
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
|
||||
private EntityCallbacks entityCallbacks;
|
||||
@@ -132,10 +134,12 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
return SimpleJdbcRepository.class;
|
||||
}
|
||||
|
||||
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(JdbcQueryLookupStrategy.create(key, publisher, entityCallbacks, context, converter, dialect,
|
||||
queryMappingConfiguration, operations, beanFactory, valueExpressionDelegate));
|
||||
queryMappingConfiguration, operations, beanFactory,
|
||||
new CachingValueExpressionDelegate(valueExpressionDelegate)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
@@ -79,6 +78,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Dennis Effing
|
||||
* @author Chirag Tailor
|
||||
* @author Christopher Klein
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class StringBasedJdbcQueryUnitTests {
|
||||
|
||||
@@ -95,8 +95,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
this.operations = mock(NamedParameterJdbcOperations.class);
|
||||
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
|
||||
this.converter = new MappingJdbcConverter(context, mock(RelationResolver.class));
|
||||
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), new StaticApplicationContext());
|
||||
this.delegate = new ValueExpressionDelegate(accessor, ValueExpressionParser.create());
|
||||
this.delegate = ValueExpressionDelegate.create();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-165
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.r2dbc.repository.query;
|
||||
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Simple {@link R2dbcSpELExpressionEvaluator} implementation using {@link ExpressionParser} and
|
||||
* {@link EvaluationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
class DefaultR2dbcSpELExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
|
||||
|
||||
private final ValueExpressionDelegate delegate;
|
||||
|
||||
private final ValueEvaluationContext context;
|
||||
|
||||
DefaultR2dbcSpELExpressionEvaluator(ValueExpressionDelegate delegate, ValueEvaluationContext context) {
|
||||
this.delegate = delegate;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
|
||||
*
|
||||
* @return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
|
||||
*/
|
||||
public static R2dbcSpELExpressionEvaluator unsupported() {
|
||||
return NoOpExpressionEvaluator.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameter evaluate(String expression) {
|
||||
|
||||
ValueExpression expr = delegate.parse(expression);
|
||||
|
||||
Object value = expr.evaluate(context);
|
||||
Class<?> valueType = value != null ? value.getClass() : Object.class;
|
||||
|
||||
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SpELExpressionEvaluator} that does not support SpEL evaluation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
enum NoOpExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Parameter evaluate(String expression) {
|
||||
throw new UnsupportedOperationException("Expression evaluation not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.r2dbc.repository.query;
|
||||
|
||||
import static org.springframework.data.r2dbc.repository.query.ExpressionQuery.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.dialect.BindTargetBinder;
|
||||
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
|
||||
@@ -64,26 +64,40 @@ class ExpressionEvaluatingParameterBinder {
|
||||
* @param evaluator must not be {@literal null}.
|
||||
*/
|
||||
void bind(BindTarget bindTarget,
|
||||
RelationalParameterAccessor parameterAccessor, R2dbcSpELExpressionEvaluator evaluator) {
|
||||
RelationalParameterAccessor parameterAccessor, ValueEvaluationContext evaluationContext) {
|
||||
|
||||
Object[] values = parameterAccessor.getValues();
|
||||
Parameters<?, ?> bindableParameters = parameterAccessor.getBindableParameters();
|
||||
|
||||
bindExpressions(bindTarget, evaluator);
|
||||
bindExpressions(bindTarget, evaluationContext);
|
||||
bindParameters(bindTarget, parameterAccessor.hasBindableNullValue(), values, bindableParameters);
|
||||
}
|
||||
|
||||
private void bindExpressions(BindTarget bindSpec,
|
||||
R2dbcSpELExpressionEvaluator evaluator) {
|
||||
ValueEvaluationContext evaluationContext) {
|
||||
|
||||
BindTargetBinder binder = new BindTargetBinder(bindSpec);
|
||||
for (ParameterBinding binding : expressionQuery.getBindings()) {
|
||||
|
||||
expressionQuery.getBindings().forEach((paramName, valueExpression) -> {
|
||||
|
||||
org.springframework.r2dbc.core.Parameter valueForBinding = getBindValue(
|
||||
evaluator.evaluate(binding.getExpression()));
|
||||
evaluate(valueExpression, evaluationContext));
|
||||
|
||||
binder.bind(binding.getParameterName(), valueForBinding);
|
||||
binder.bind(paramName, valueForBinding);
|
||||
});
|
||||
}
|
||||
|
||||
private org.springframework.r2dbc.core.Parameter evaluate(ValueExpression expression,
|
||||
ValueEvaluationContext context) {
|
||||
|
||||
Object value = expression.evaluate(context);
|
||||
Class<?> valueType = value != null ? value.getClass() : null;
|
||||
|
||||
if (valueType == null) {
|
||||
valueType = expression.getValueType(context);
|
||||
}
|
||||
|
||||
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType == null ? Object.class : valueType);
|
||||
}
|
||||
|
||||
private void bindParameters(BindTarget bindSpec,
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.r2dbc.repository.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
|
||||
|
||||
@@ -34,13 +34,11 @@ class ExpressionQuery {
|
||||
private static final String SYNTHETIC_PARAMETER_TEMPLATE = "__synthetic_%d__";
|
||||
|
||||
private final String query;
|
||||
private final Map<String, ValueExpression> parameterMap;
|
||||
|
||||
private final List<ParameterBinding> parameterBindings;
|
||||
|
||||
private ExpressionQuery(String query, List<ParameterBinding> parameterBindings) {
|
||||
|
||||
private ExpressionQuery(String query, Map<String, ValueExpression> parameterMap) {
|
||||
this.query = query;
|
||||
this.parameterBindings = parameterBindings;
|
||||
this.parameterMap = parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,55 +49,25 @@ class ExpressionQuery {
|
||||
*/
|
||||
public static ExpressionQuery create(ValueExpressionParser parser, String query) {
|
||||
|
||||
List<ParameterBinding> parameterBindings = new ArrayList<>();
|
||||
|
||||
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(parser, (counter, expression) -> {
|
||||
String parameterName = String.format(SYNTHETIC_PARAMETER_TEMPLATE, counter);
|
||||
parameterBindings.add(new ParameterBinding(parameterName, expression));
|
||||
return parameterName;
|
||||
}, String::concat);
|
||||
|
||||
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(parser,
|
||||
(counter, expression) -> String.format(SYNTHETIC_PARAMETER_TEMPLATE, counter), String::concat);
|
||||
ValueExpressionQueryRewriter.ParsedQuery parsed = rewriter.parse(query);
|
||||
|
||||
return new ExpressionQuery(parsed.getQueryString(), parameterBindings);
|
||||
return new ExpressionQuery(parsed.getQueryString(), parsed.getParameterMap());
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public List<ParameterBinding> getBindings() {
|
||||
return parameterBindings;
|
||||
public Map<String, ValueExpression> getBindings() {
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* A SpEL parameter binding.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class ParameterBinding {
|
||||
|
||||
private final String parameterName;
|
||||
private final String expression;
|
||||
|
||||
private ParameterBinding(String parameterName, String expression) {
|
||||
|
||||
this.expression = expression;
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
String getParameterName() {
|
||||
return parameterName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.r2dbc.repository.query;
|
||||
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* SPI for components that can evaluate Spring EL expressions and return {@link Parameter}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
interface R2dbcSpELExpressionEvaluator {
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
*
|
||||
* @param expression
|
||||
* @return
|
||||
*/
|
||||
Parameter evaluate(String expression);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.expression.ReactiveValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
@@ -53,6 +54,7 @@ import org.springframework.util.Assert;
|
||||
* named parameters (if enabled on {@link DatabaseClient}) and SpEL expressions enclosed with {@code :#{…}}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
|
||||
@@ -60,8 +62,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
private final ExpressionEvaluatingParameterBinder binder;
|
||||
private final ExpressionDependencies expressionDependencies;
|
||||
private final ReactiveDataAccessStrategy dataAccessStrategy;
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
private final ValueEvaluationContextProvider valueContextProvider;
|
||||
private final ReactiveValueEvaluationContextProvider valueContextProvider;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedR2dbcQuery} for the given {@link StringBasedR2dbcQuery}, {@link DatabaseClient},
|
||||
@@ -132,17 +133,22 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
super(method, entityOperations, converter);
|
||||
this.valueExpressionDelegate = valueExpressionDelegate;
|
||||
|
||||
Assert.hasText(query, "Query must not be empty");
|
||||
|
||||
this.dataAccessStrategy = dataAccessStrategy;
|
||||
this.expressionQuery = ExpressionQuery.create(valueExpressionDelegate, query);
|
||||
this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery, dataAccessStrategy);
|
||||
this.valueContextProvider = valueExpressionDelegate.createValueContextProvider(
|
||||
method.getParameters());
|
||||
|
||||
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate
|
||||
.createValueContextProvider(method.getParameters());
|
||||
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider,
|
||||
"ValueEvaluationContextProvider must be reactive");
|
||||
|
||||
this.valueContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
|
||||
this.expressionDependencies = createExpressionDependencies();
|
||||
|
||||
|
||||
if (method.isSliceQuery()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Slice queries are not supported using string-based queries; Offending method: " + method);
|
||||
@@ -167,9 +173,8 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
|
||||
List<ExpressionDependencies> dependencies = new ArrayList<>();
|
||||
|
||||
for (ExpressionQuery.ParameterBinding binding : expressionQuery.getBindings()) {
|
||||
dependencies.add(valueExpressionDelegate.parse(binding.getExpression()).getExpressionDependencies());
|
||||
}
|
||||
expressionQuery.getBindings()
|
||||
.forEach((s, valueExpression) -> dependencies.add(valueExpression.getExpressionDependencies()));
|
||||
|
||||
return ExpressionDependencies.merged(dependencies);
|
||||
}
|
||||
@@ -191,7 +196,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
|
||||
@Override
|
||||
protected Mono<PreparedOperation<?>> createQuery(RelationalParameterAccessor accessor) {
|
||||
return getSpelEvaluator(accessor).map(evaluator -> new ExpandedQuery(accessor, evaluator));
|
||||
return getExpressionEvaluator(accessor).map(evaluator -> new ExpandedQuery(accessor, evaluator));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,19 +206,13 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
return !returnedType.isInterface() ? returnedType : super.resolveResultType(resultProcessor);
|
||||
}
|
||||
|
||||
private Mono<R2dbcSpELExpressionEvaluator> getSpelEvaluator(RelationalParameterAccessor accessor) {
|
||||
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
|
||||
return ((ReactiveValueEvaluationContextProvider) valueContextProvider)
|
||||
.getEvaluationContextLater(accessor.getValues(), expressionDependencies)
|
||||
.<R2dbcSpELExpressionEvaluator> map(
|
||||
context -> new DefaultR2dbcSpELExpressionEvaluator(valueExpressionDelegate, context))
|
||||
.defaultIfEmpty(DefaultR2dbcSpELExpressionEvaluator.unsupported());
|
||||
private Mono<ValueEvaluationContext> getExpressionEvaluator(RelationalParameterAccessor accessor) {
|
||||
return valueContextProvider.getEvaluationContextLater(accessor.getValues(), expressionDependencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String sb = getClass().getSimpleName() + " [" + expressionQuery.getQuery() + ']';
|
||||
return sb;
|
||||
return getClass().getSimpleName() + " [" + expressionQuery.getQuery() + ']';
|
||||
}
|
||||
|
||||
private class ExpandedQuery implements PreparedOperation<String> {
|
||||
@@ -226,10 +225,10 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
|
||||
private final Map<Integer, Parameter> remainderByIndex;
|
||||
|
||||
public ExpandedQuery(RelationalParameterAccessor accessor, R2dbcSpELExpressionEvaluator evaluator) {
|
||||
public ExpandedQuery(RelationalParameterAccessor accessor, ValueEvaluationContext evaluationContext) {
|
||||
|
||||
this.recordedBindings = new BindTargetRecorder();
|
||||
binder.bind(recordedBindings, accessor, evaluator);
|
||||
binder.bind(recordedBindings, accessor, evaluationContext);
|
||||
|
||||
remainderByName = new LinkedHashMap<>(recordedBindings.byName);
|
||||
remainderByIndex = new LinkedHashMap<>(recordedBindings.byIndex);
|
||||
|
||||
@@ -52,6 +52,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
|
||||
@@ -113,7 +114,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(new R2dbcQueryLookupStrategy(operations, new CachingValueExpressionDelegate(valueExpressionDelegate), converter, dataAccessStrategy));
|
||||
}
|
||||
|
||||
@@ -17,8 +17,11 @@ package org.springframework.data.r2dbc.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
|
||||
/**
|
||||
@@ -26,11 +29,12 @@ import org.springframework.data.expression.ValueExpressionParser;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class ExpressionQueryUnitTests {
|
||||
|
||||
@Test // gh-373
|
||||
void bindsMultipleSpelParametersCorrectly() {
|
||||
@Test // gh-373, gh-1904
|
||||
void bindsMultipleExpressionParametersCorrectly() {
|
||||
|
||||
ExpressionQuery query = ExpressionQuery
|
||||
.create(ValueExpressionParser.create(), "INSERT IGNORE INTO table (x, y) VALUES (:#{#point.x}, :${point.y})");
|
||||
@@ -38,10 +42,10 @@ class ExpressionQueryUnitTests {
|
||||
assertThat(query.getQuery())
|
||||
.isEqualTo("INSERT IGNORE INTO table (x, y) VALUES (:__synthetic_0__, :__synthetic_1__)");
|
||||
|
||||
assertThat(query.getBindings()).hasSize(2);
|
||||
assertThat(query.getBindings().get(0).getExpression()).isEqualTo("#{#point.x}");
|
||||
assertThat(query.getBindings().get(0).getParameterName()).isEqualTo("__synthetic_0__");
|
||||
assertThat(query.getBindings().get(1).getExpression()).isEqualTo("${point.y}");
|
||||
assertThat(query.getBindings().get(1).getParameterName()).isEqualTo("__synthetic_1__");
|
||||
Map<String, ValueExpression> bindings = query.getBindings();
|
||||
assertThat(bindings).hasSize(2);
|
||||
|
||||
assertThat(bindings.get("__synthetic_0__").getExpressionString()).isEqualTo("#point.x");
|
||||
assertThat(bindings.get("__synthetic_1__").getExpressionString()).isEqualTo("${point.y}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user