Skip to content

Commit ecbb8d8

Browse files
committed
#414 - Adopt SpEL support to use ReactiveEvaluationContextProvider.
We now defer query creation to obtain and resolve SpEL expression dependencies using reactive SpEL context extensions.
1 parent d3276b9 commit ecbb8d8

13 files changed

+335
-172
lines changed

src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java

+8-24
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
*/
1616
package org.springframework.data.r2dbc.repository.query;
1717

18-
import kotlin.Unit;
19-
2018
import reactor.core.publisher.Flux;
2119
import reactor.core.publisher.Mono;
2220

2321
import org.reactivestreams.Publisher;
24-
import org.springframework.core.KotlinDetector;
22+
2523
import org.springframework.data.mapping.model.EntityInstantiators;
2624
import org.springframework.data.r2dbc.convert.EntityRowMapper;
2725
import org.springframework.data.r2dbc.convert.R2dbcConverter;
@@ -34,10 +32,10 @@
3432
import org.springframework.data.repository.query.RepositoryQuery;
3533
import org.springframework.data.repository.query.ResultProcessor;
3634
import org.springframework.data.repository.query.ReturnedType;
35+
import org.springframework.data.util.ReflectionUtils;
3736
import org.springframework.r2dbc.core.DatabaseClient;
3837
import org.springframework.r2dbc.core.FetchSpec;
3938
import org.springframework.r2dbc.core.RowsFetchSpec;
40-
import org.springframework.data.util.ReflectionUtils;
4139
import org.springframework.util.Assert;
4240

4341
/**
@@ -86,29 +84,15 @@ public R2dbcQueryMethod getQueryMethod() {
8684
*/
8785
public Object execute(Object[] parameters) {
8886

89-
return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
90-
: execute(new RelationalParametersParameterAccessor(method, parameters));
91-
}
92-
93-
@SuppressWarnings("unchecked")
94-
private Object executeDeferred(Object[] parameters) {
95-
96-
R2dbcParameterAccessor parameterAccessor = new R2dbcParameterAccessor(method, parameters);
87+
RelationalParameterAccessor parameterAccessor = new RelationalParametersParameterAccessor(method, parameters);
9788

98-
if (getQueryMethod().isCollectionQuery()) {
99-
return Flux.defer(() -> (Publisher<Object>) execute(parameterAccessor));
100-
}
101-
102-
return Mono.defer(() -> (Mono<Object>) execute(parameterAccessor));
89+
return createQuery(parameterAccessor).flatMapMany(it -> executeQuery(parameterAccessor, it));
10390
}
10491

105-
private Object execute(RelationalParameterAccessor parameterAccessor) {
106-
107-
// TODO: ConvertingParameterAccessor
108-
BindableQuery query = createQuery(parameterAccessor);
92+
private Publisher<?> executeQuery(RelationalParameterAccessor parameterAccessor, BindableQuery it) {
10993

11094
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
111-
DatabaseClient.GenericExecuteSpec boundQuery = query.bind(databaseClient.sql(query));
95+
DatabaseClient.GenericExecuteSpec boundQuery = it.bind(databaseClient.sql(it));
11296

11397
FetchSpec<?> fetchSpec;
11498
if (requiresMapping()) {
@@ -178,9 +162,9 @@ private R2dbcQueryExecution getExecutionToWrap(ReturnedType returnedType) {
178162
* Creates a {@link BindableQuery} instance using the given {@link ParameterAccessor}
179163
*
180164
* @param accessor must not be {@literal null}.
181-
* @return the {@link BindableQuery}.
165+
* @return a mono emitting a {@link BindableQuery}.
182166
*/
183-
protected abstract BindableQuery createQuery(RelationalParameterAccessor accessor);
167+
protected abstract Mono<BindableQuery> createQuery(RelationalParameterAccessor accessor);
184168

185169
private static class FetchSpecAdapter<T> implements FetchSpec<T> {
186170

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.repository.query;
17+
18+
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
19+
import org.springframework.expression.EvaluationContext;
20+
import org.springframework.expression.Expression;
21+
import org.springframework.expression.ExpressionParser;
22+
import org.springframework.r2dbc.core.Parameter;
23+
24+
/**
25+
* Simple {@link R2dbcSpELExpressionEvaluator} implementation using {@link ExpressionParser} and
26+
* {@link EvaluationContext}.
27+
*
28+
* @author Mark Paluch
29+
* @since 1.2
30+
*/
31+
class DefaultR2dbcSpELExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
32+
33+
private final ExpressionParser parser;
34+
35+
private final EvaluationContext context;
36+
37+
DefaultR2dbcSpELExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
38+
this.parser = parser;
39+
this.context = context;
40+
}
41+
42+
/**
43+
* Return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
44+
*
45+
* @return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
46+
*/
47+
public static R2dbcSpELExpressionEvaluator unsupported() {
48+
return NoOpExpressionEvaluator.INSTANCE;
49+
}
50+
51+
/*
52+
* (non-Javadoc)
53+
* @see org.springframework.data.mapping.model.R2dbcSpELExpressionEvaluator#evaluate(java.lang.String)
54+
*/
55+
@Override
56+
public Parameter evaluate(String expression) {
57+
58+
Expression expr = parser.parseExpression(expression);
59+
60+
Object value = expr.getValue(context, Object.class);
61+
Class<?> valueType = expr.getValueType(context);
62+
63+
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class);
64+
}
65+
66+
/**
67+
* {@link SpELExpressionEvaluator} that does not support SpEL evaluation.
68+
*
69+
* @author Mark Paluch
70+
*/
71+
enum NoOpExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
72+
73+
INSTANCE;
74+
75+
@Override
76+
public Parameter evaluate(String expression) {
77+
throw new UnsupportedOperationException("Expression evaluation not supported");
78+
}
79+
}
80+
}

src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java

+9-63
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,7 @@
2525
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
2626
import org.springframework.data.repository.query.Parameter;
2727
import org.springframework.data.repository.query.Parameters;
28-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
29-
import org.springframework.expression.EvaluationContext;
30-
import org.springframework.expression.Expression;
31-
import org.springframework.expression.spel.standard.SpelExpressionParser;
3228
import org.springframework.r2dbc.core.DatabaseClient;
33-
import org.springframework.util.Assert;
3429

3530
/**
3631
* {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a
@@ -41,30 +36,16 @@
4136
*/
4237
class ExpressionEvaluatingParameterBinder {
4338

44-
private final SpelExpressionParser expressionParser;
45-
46-
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
47-
4839
private final ExpressionQuery expressionQuery;
4940

5041
private final Map<String, Boolean> namedParameters = new ConcurrentHashMap<>();
5142

5243
/**
5344
* Creates new {@link ExpressionEvaluatingParameterBinder}
5445
*
55-
* @param expressionParser must not be {@literal null}.
56-
* @param evaluationContextProvider must not be {@literal null}.
5746
* @param expressionQuery must not be {@literal null}.
5847
*/
59-
ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser,
60-
QueryMethodEvaluationContextProvider evaluationContextProvider, ExpressionQuery expressionQuery) {
61-
62-
Assert.notNull(expressionParser, "ExpressionParser must not be null");
63-
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
64-
Assert.notNull(expressionQuery, "ExpressionQuery must not be null");
65-
66-
this.expressionParser = expressionParser;
67-
this.evaluationContextProvider = evaluationContextProvider;
48+
ExpressionEvaluatingParameterBinder(ExpressionQuery expressionQuery) {
6849
this.expressionQuery = expressionQuery;
6950
}
7051

@@ -74,28 +55,28 @@ class ExpressionEvaluatingParameterBinder {
7455
*
7556
* @param bindSpec must not be {@literal null}.
7657
* @param parameterAccessor must not be {@literal null}.
58+
* @param evaluator must not be {@literal null}.
7759
*/
78-
public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec,
79-
RelationalParameterAccessor parameterAccessor) {
60+
DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec,
61+
RelationalParameterAccessor parameterAccessor, R2dbcSpELExpressionEvaluator evaluator) {
8062

8163
Object[] values = parameterAccessor.getValues();
8264
Parameters<?, ?> bindableParameters = parameterAccessor.getBindableParameters();
8365

84-
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, values, bindableParameters);
66+
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, evaluator);
8567
bindSpecToUse = bindParameters(bindSpecToUse, parameterAccessor.hasBindableNullValue(), values, bindableParameters);
8668

8769
return bindSpecToUse;
8870
}
8971

90-
private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec, Object[] values,
91-
Parameters<?, ?> bindableParameters) {
72+
private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec,
73+
R2dbcSpELExpressionEvaluator evaluator) {
9274

9375
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec;
9476

9577
for (ParameterBinding binding : expressionQuery.getBindings()) {
9678

97-
org.springframework.r2dbc.core.Parameter valueForBinding = getParameterValueForBinding(bindableParameters, values,
98-
binding);
79+
org.springframework.r2dbc.core.Parameter valueForBinding = evaluator.evaluate(binding.getExpression());
9980

10081
if (valueForBinding.isEmpty()) {
10182
bindSpecToUse = bindSpecToUse.bindNull(binding.getParameterName(), valueForBinding.getType());
@@ -108,13 +89,11 @@ private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.Generic
10889
}
10990

11091
private DatabaseClient.GenericExecuteSpec bindParameters(DatabaseClient.GenericExecuteSpec bindSpec,
111-
boolean bindableNull, Object[] values,
112-
Parameters<?, ?> bindableParameters) {
92+
boolean bindableNull, Object[] values, Parameters<?, ?> bindableParameters) {
11393

11494
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec;
11595
int bindingIndex = 0;
11696

117-
11897
for (Parameter bindableParameter : bindableParameters) {
11998

12099
Object value = values[bindableParameter.getIndex()];
@@ -161,37 +140,4 @@ private boolean isNamedParameterUsed(Optional<String> name) {
161140
});
162141
}
163142

164-
/**
165-
* Returns the value to be used for the given {@link ParameterBinding}.
166-
*
167-
* @param parameters must not be {@literal null}.
168-
* @param binding must not be {@literal null}.
169-
* @return the value used for the given {@link ParameterBinding}.
170-
*/
171-
private org.springframework.r2dbc.core.Parameter getParameterValueForBinding(Parameters<?, ?> parameters,
172-
Object[] values,
173-
ParameterBinding binding) {
174-
return evaluateExpression(binding.getExpression(), parameters, values);
175-
}
176-
177-
/**
178-
* Evaluates the given {@code expressionString}.
179-
*
180-
* @param expressionString must not be {@literal null} or empty.
181-
* @param parameters must not be {@literal null}.
182-
* @param parameterValues must not be {@literal null}.
183-
* @return the value of the {@code expressionString} evaluation.
184-
*/
185-
private org.springframework.r2dbc.core.Parameter evaluateExpression(String expressionString,
186-
Parameters<?, ?> parameters,
187-
Object[] parameterValues) {
188-
189-
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(parameters, parameterValues);
190-
Expression expression = expressionParser.parseExpression(expressionString);
191-
192-
Object value = expression.getValue(evaluationContext, Object.class);
193-
Class<?> valueType = expression.getValueType(evaluationContext);
194-
195-
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class);
196-
}
197143
}

src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020

2121
import org.springframework.data.repository.query.SpelQueryContext;
22+
import org.springframework.data.spel.ExpressionDependencies;
2223

2324
/**
2425
* Query using Spring Expression Language to indicate parameter bindings. Queries using SpEL use {@code :#{…}} to

src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java

+16-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.r2dbc.repository.query;
1717

18+
import reactor.core.publisher.Mono;
19+
1820
import java.util.ArrayList;
1921
import java.util.Collections;
2022
import java.util.List;
@@ -85,21 +87,24 @@ protected boolean isModifyingQuery() {
8587
* @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor)
8688
*/
8789
@Override
88-
protected BindableQuery createQuery(RelationalParameterAccessor accessor) {
90+
protected Mono<BindableQuery> createQuery(RelationalParameterAccessor accessor) {
8991

90-
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
91-
List<String> projectedProperties = Collections.emptyList();
92+
return Mono.fromSupplier(() -> {
9293

93-
if (returnedType.needsCustomConstruction()) {
94-
projectedProperties = new ArrayList<>(returnedType.getInputProperties());
95-
}
94+
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
95+
List<String> projectedProperties = Collections.emptyList();
96+
97+
if (returnedType.needsCustomConstruction()) {
98+
projectedProperties = new ArrayList<>(returnedType.getInputProperties());
99+
}
96100

97-
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
98-
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor,
99-
projectedProperties);
100-
PreparedOperation<?> preparedQuery = queryCreator.createQuery(getDynamicSort(accessor));
101+
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
102+
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor,
103+
projectedProperties);
104+
PreparedOperation<?> preparedQuery = queryCreator.createQuery(getDynamicSort(accessor));
101105

102-
return new PreparedOperationBindableQuery(preparedQuery);
106+
return new PreparedOperationBindableQuery(preparedQuery);
107+
});
103108
}
104109

105110
private Sort getDynamicSort(RelationalParameterAccessor accessor) {

src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
*/
4242
interface R2dbcQueryExecution {
4343

44-
Object execute(FetchSpec<?> query, Class<?> type, SqlIdentifier tableName);
44+
Publisher<?> execute(FetchSpec<?> query, Class<?> type, SqlIdentifier tableName);
4545

4646
/**
4747
* An {@link R2dbcQueryExecution} that wraps the results of the given delegate with the given result processing.
@@ -60,8 +60,8 @@ final class ResultProcessingExecution implements R2dbcQueryExecution {
6060
* @see org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.r2dbc.function.FetchSpec, java.lang.Class, java.lang.String)
6161
*/
6262
@Override
63-
public Object execute(FetchSpec<?> query, Class<?> type, SqlIdentifier tableName) {
64-
return this.converter.convert(this.delegate.execute(query, type, tableName));
63+
public Publisher<?> execute(FetchSpec<?> query, Class<?> type, SqlIdentifier tableName) {
64+
return (Publisher<?>) this.converter.convert(this.delegate.execute(query, type, tableName));
6565
}
6666
}
6767

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.repository.query;
17+
18+
import org.springframework.r2dbc.core.Parameter;
19+
20+
/**
21+
* SPI for components that can evaluate Spring EL expressions and return {@link Parameter}.
22+
*
23+
* @author Mark Paluch
24+
* @since 1.2
25+
*/
26+
interface R2dbcSpELExpressionEvaluator {
27+
28+
/**
29+
* Evaluates the given expression.
30+
*
31+
* @param expression
32+
* @return
33+
*/
34+
Parameter evaluate(String expression);
35+
}

0 commit comments

Comments
 (0)