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:
Mark Paluch
2024-10-08 09:43:27 +02:00
parent d526cd3a22
commit 8c6364b4bf
10 changed files with 97 additions and 226 deletions

View File

@@ -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");
}
}
}

View File

@@ -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,

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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));
}