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:
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user