diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java index 2bc3b2b5..10aaeb85 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java @@ -15,13 +15,11 @@ */ package org.springframework.data.r2dbc.repository.query; -import kotlin.Unit; - import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.reactivestreams.Publisher; -import org.springframework.core.KotlinDetector; + import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.r2dbc.convert.EntityRowMapper; import org.springframework.data.r2dbc.convert.R2dbcConverter; @@ -34,10 +32,10 @@ import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; +import org.springframework.data.util.ReflectionUtils; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.FetchSpec; import org.springframework.r2dbc.core.RowsFetchSpec; -import org.springframework.data.util.ReflectionUtils; import org.springframework.util.Assert; /** @@ -86,29 +84,15 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { */ public Object execute(Object[] parameters) { - return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) - : execute(new RelationalParametersParameterAccessor(method, parameters)); + RelationalParameterAccessor parameterAccessor = new RelationalParametersParameterAccessor(method, parameters); + + return createQuery(parameterAccessor).flatMapMany(it -> executeQuery(parameterAccessor, it)); } - @SuppressWarnings("unchecked") - private Object executeDeferred(Object[] parameters) { - - R2dbcParameterAccessor parameterAccessor = new R2dbcParameterAccessor(method, parameters); - - if (getQueryMethod().isCollectionQuery()) { - return Flux.defer(() -> (Publisher) execute(parameterAccessor)); - } - - return Mono.defer(() -> (Mono) execute(parameterAccessor)); - } - - private Object execute(RelationalParameterAccessor parameterAccessor) { - - // TODO: ConvertingParameterAccessor - BindableQuery query = createQuery(parameterAccessor); + private Publisher executeQuery(RelationalParameterAccessor parameterAccessor, BindableQuery it) { ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); - DatabaseClient.GenericExecuteSpec boundQuery = query.bind(databaseClient.sql(query)); + DatabaseClient.GenericExecuteSpec boundQuery = it.bind(databaseClient.sql(it)); FetchSpec fetchSpec; if (requiresMapping()) { @@ -178,9 +162,9 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { * Creates a {@link BindableQuery} instance using the given {@link ParameterAccessor} * * @param accessor must not be {@literal null}. - * @return the {@link BindableQuery}. + * @return a mono emitting a {@link BindableQuery}. */ - protected abstract BindableQuery createQuery(RelationalParameterAccessor accessor); + protected abstract Mono createQuery(RelationalParameterAccessor accessor); private static class FetchSpecAdapter implements FetchSpec { diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/DefaultR2dbcSpELExpressionEvaluator.java b/src/main/java/org/springframework/data/r2dbc/repository/query/DefaultR2dbcSpELExpressionEvaluator.java new file mode 100644 index 00000000..c454e57d --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/DefaultR2dbcSpELExpressionEvaluator.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 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.mapping.model.SpELExpressionEvaluator; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +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 ExpressionParser parser; + + private final EvaluationContext context; + + DefaultR2dbcSpELExpressionEvaluator(ExpressionParser parser, EvaluationContext context) { + this.parser = parser; + 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; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.R2dbcSpELExpressionEvaluator#evaluate(java.lang.String) + */ + @Override + public Parameter evaluate(String expression) { + + Expression expr = parser.parseExpression(expression); + + Object value = expr.getValue(context, Object.class); + Class valueType = expr.getValueType(context); + + return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class); + } + + /** + * {@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"); + } + } +} diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java index 9ac8c77f..66eb6a80 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java @@ -25,12 +25,7 @@ import java.util.regex.Pattern; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.util.Assert; /** * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a @@ -41,10 +36,6 @@ import org.springframework.util.Assert; */ class ExpressionEvaluatingParameterBinder { - private final SpelExpressionParser expressionParser; - - private final QueryMethodEvaluationContextProvider evaluationContextProvider; - private final ExpressionQuery expressionQuery; private final Map namedParameters = new ConcurrentHashMap<>(); @@ -52,19 +43,9 @@ class ExpressionEvaluatingParameterBinder { /** * Creates new {@link ExpressionEvaluatingParameterBinder} * - * @param expressionParser must not be {@literal null}. - * @param evaluationContextProvider must not be {@literal null}. * @param expressionQuery must not be {@literal null}. */ - ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser, - QueryMethodEvaluationContextProvider evaluationContextProvider, ExpressionQuery expressionQuery) { - - Assert.notNull(expressionParser, "ExpressionParser must not be null"); - Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null"); - Assert.notNull(expressionQuery, "ExpressionQuery must not be null"); - - this.expressionParser = expressionParser; - this.evaluationContextProvider = evaluationContextProvider; + ExpressionEvaluatingParameterBinder(ExpressionQuery expressionQuery) { this.expressionQuery = expressionQuery; } @@ -74,28 +55,28 @@ class ExpressionEvaluatingParameterBinder { * * @param bindSpec must not be {@literal null}. * @param parameterAccessor must not be {@literal null}. + * @param evaluator must not be {@literal null}. */ - public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec, - RelationalParameterAccessor parameterAccessor) { + DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec, + RelationalParameterAccessor parameterAccessor, R2dbcSpELExpressionEvaluator evaluator) { Object[] values = parameterAccessor.getValues(); Parameters bindableParameters = parameterAccessor.getBindableParameters(); - DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, values, bindableParameters); + DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, evaluator); bindSpecToUse = bindParameters(bindSpecToUse, parameterAccessor.hasBindableNullValue(), values, bindableParameters); return bindSpecToUse; } - private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec, Object[] values, - Parameters bindableParameters) { + private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec, + R2dbcSpELExpressionEvaluator evaluator) { DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec; for (ParameterBinding binding : expressionQuery.getBindings()) { - org.springframework.r2dbc.core.Parameter valueForBinding = getParameterValueForBinding(bindableParameters, values, - binding); + org.springframework.r2dbc.core.Parameter valueForBinding = evaluator.evaluate(binding.getExpression()); if (valueForBinding.isEmpty()) { bindSpecToUse = bindSpecToUse.bindNull(binding.getParameterName(), valueForBinding.getType()); @@ -108,13 +89,11 @@ class ExpressionEvaluatingParameterBinder { } private DatabaseClient.GenericExecuteSpec bindParameters(DatabaseClient.GenericExecuteSpec bindSpec, - boolean bindableNull, Object[] values, - Parameters bindableParameters) { + boolean bindableNull, Object[] values, Parameters bindableParameters) { DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec; int bindingIndex = 0; - for (Parameter bindableParameter : bindableParameters) { Object value = values[bindableParameter.getIndex()]; @@ -161,37 +140,4 @@ class ExpressionEvaluatingParameterBinder { }); } - /** - * Returns the value to be used for the given {@link ParameterBinding}. - * - * @param parameters must not be {@literal null}. - * @param binding must not be {@literal null}. - * @return the value used for the given {@link ParameterBinding}. - */ - private org.springframework.r2dbc.core.Parameter getParameterValueForBinding(Parameters parameters, - Object[] values, - ParameterBinding binding) { - return evaluateExpression(binding.getExpression(), parameters, values); - } - - /** - * Evaluates the given {@code expressionString}. - * - * @param expressionString must not be {@literal null} or empty. - * @param parameters must not be {@literal null}. - * @param parameterValues must not be {@literal null}. - * @return the value of the {@code expressionString} evaluation. - */ - private org.springframework.r2dbc.core.Parameter evaluateExpression(String expressionString, - Parameters parameters, - Object[] parameterValues) { - - EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(parameters, parameterValues); - Expression expression = expressionParser.parseExpression(expressionString); - - Object value = expression.getValue(evaluationContext, Object.class); - Class valueType = expression.getValueType(evaluationContext); - - return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class); - } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java index 64047631..17c2da33 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import org.springframework.data.repository.query.SpelQueryContext; +import org.springframework.data.spel.ExpressionDependencies; /** * Query using Spring Expression Language to indicate parameter bindings. Queries using SpEL use {@code :#{…}} to diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java index 3a6c6d15..6319ef2c 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java @@ -15,6 +15,8 @@ */ package org.springframework.data.r2dbc.repository.query; +import reactor.core.publisher.Mono; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -85,21 +87,24 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery { * @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor) */ @Override - protected BindableQuery createQuery(RelationalParameterAccessor accessor) { + protected Mono createQuery(RelationalParameterAccessor accessor) { - ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType(); - List projectedProperties = Collections.emptyList(); + return Mono.fromSupplier(() -> { - if (returnedType.needsCustomConstruction()) { - projectedProperties = new ArrayList<>(returnedType.getInputProperties()); - } + ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType(); + List projectedProperties = Collections.emptyList(); - RelationalEntityMetadata entityMetadata = getQueryMethod().getEntityInformation(); - R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor, - projectedProperties); - PreparedOperation preparedQuery = queryCreator.createQuery(getDynamicSort(accessor)); + if (returnedType.needsCustomConstruction()) { + projectedProperties = new ArrayList<>(returnedType.getInputProperties()); + } - return new PreparedOperationBindableQuery(preparedQuery); + RelationalEntityMetadata entityMetadata = getQueryMethod().getEntityInformation(); + R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor, + projectedProperties); + PreparedOperation preparedQuery = queryCreator.createQuery(getDynamicSort(accessor)); + + return new PreparedOperationBindableQuery(preparedQuery); + }); } private Sort getDynamicSort(RelationalParameterAccessor accessor) { diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java index 9877ee9f..5067dcdb 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java @@ -41,7 +41,7 @@ import org.springframework.util.ClassUtils; */ interface R2dbcQueryExecution { - Object execute(FetchSpec query, Class type, SqlIdentifier tableName); + Publisher execute(FetchSpec query, Class type, SqlIdentifier tableName); /** * An {@link R2dbcQueryExecution} that wraps the results of the given delegate with the given result processing. @@ -60,8 +60,8 @@ interface R2dbcQueryExecution { * @see org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.r2dbc.function.FetchSpec, java.lang.Class, java.lang.String) */ @Override - public Object execute(FetchSpec query, Class type, SqlIdentifier tableName) { - return this.converter.convert(this.delegate.execute(query, type, tableName)); + public Publisher execute(FetchSpec query, Class type, SqlIdentifier tableName) { + return (Publisher) this.converter.convert(this.delegate.execute(query, type, tableName)); } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcSpELExpressionEvaluator.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcSpELExpressionEvaluator.java new file mode 100644 index 00000000..535fb7b6 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcSpELExpressionEvaluator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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); +} diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java index ded9bfef..3dec9636 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java @@ -15,10 +15,18 @@ */ package org.springframework.data.r2dbc.repository.query; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.List; + import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.repository.Query; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; +import org.springframework.data.spel.ExpressionDependencies; +import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; @@ -35,6 +43,9 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { private final ExpressionQuery expressionQuery; private final ExpressionEvaluatingParameterBinder binder; + private final ExpressionParser expressionParser; + private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider; + private final ExpressionDependencies expressionDependencies; /** * Creates a new {@link StringBasedR2dbcQuery} for the given {@link StringBasedR2dbcQuery}, {@link DatabaseClient}, @@ -47,8 +58,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { * @param evaluationContextProvider must not be {@literal null}. */ public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, DatabaseClient databaseClient, R2dbcConverter converter, - SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - + ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, databaseClient, converter, expressionParser, evaluationContextProvider); } @@ -64,15 +74,33 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { * @param evaluationContextProvider must not be {@literal null}. */ public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, DatabaseClient databaseClient, - R2dbcConverter converter, SpelExpressionParser expressionParser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + R2dbcConverter converter, ExpressionParser expressionParser, + ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { super(method, databaseClient, converter); + this.expressionParser = expressionParser; + this.evaluationContextProvider = evaluationContextProvider; Assert.hasText(query, "Query must not be empty"); this.expressionQuery = ExpressionQuery.create(query); - this.binder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider, expressionQuery); + this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery); + this.expressionDependencies = createExpressionDependencies(); + } + + private ExpressionDependencies createExpressionDependencies() { + + if (expressionQuery.getBindings().isEmpty()) { + return ExpressionDependencies.none(); + } + + List dependencies = new ArrayList<>(); + + for (ExpressionQuery.ParameterBinding binding : expressionQuery.getBindings()) { + dependencies.add(ExpressionDependencies.discover(expressionParser.parseExpression(binding.getExpression()))); + } + + return ExpressionDependencies.merged(dependencies); } /* @@ -89,19 +117,28 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { * @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor) */ @Override - protected BindableQuery createQuery(RelationalParameterAccessor accessor) { + protected Mono createQuery(RelationalParameterAccessor accessor) { - return new BindableQuery() { + return getSpelEvaluator(accessor).map(evaluator -> new BindableQuery() { @Override public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec) { - return binder.bind(bindSpec, accessor); + return binder.bind(bindSpec, accessor, evaluator); } @Override public String get() { return expressionQuery.getQuery(); } - }; + }); + } + + private Mono getSpelEvaluator(RelationalParameterAccessor accessor) { + + return evaluationContextProvider + .getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), expressionDependencies) + . map( + context -> new DefaultR2dbcSpELExpressionEvaluator(expressionParser, context)) + .defaultIfEmpty(DefaultR2dbcSpELExpressionEvaluator.unsupported()); } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/CachingExpressionParser.java b/src/main/java/org/springframework/data/r2dbc/repository/support/CachingExpressionParser.java new file mode 100644 index 00000000..b99cc59a --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/CachingExpressionParser.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 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.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParseException; +import org.springframework.expression.ParserContext; + +/** + * Caching variant of {@link ExpressionParser}. This implementation does not support + * {@link #parseExpression(String, ParserContext) parsing with ParseContext}. + * + * @author Mark Paluch + * @since 1.2 + */ +class CachingExpressionParser implements ExpressionParser { + + private final ExpressionParser delegate; + private final Map cache = new ConcurrentHashMap<>(); + + CachingExpressionParser(ExpressionParser delegate) { + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.ExpressionParser#parseExpression(java.lang.String) + */ + @Override + public Expression parseExpression(String expressionString) throws ParseException { + return cache.computeIfAbsent(expressionString, delegate::parseExpression); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.ExpressionParser#parseExpression(java.lang.String, org.springframework.expression.ParserContext) + */ + @Override + public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { + throw new UnsupportedOperationException("Parsing using ParserContext is not supported"); + } +} diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java index 4c71dc8b..f1381b0e 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java @@ -39,7 +39,9 @@ import org.springframework.data.repository.core.support.ReactiveRepositoryFactor import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; @@ -74,6 +76,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { this.dataAccessStrategy = dataAccessStrategy; this.converter = dataAccessStrategy.getConverter(); this.mappingContext = this.converter.getMappingContext(); + setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT); } /** @@ -90,6 +93,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { this.dataAccessStrategy = operations.getDataAccessStrategy(); this.converter = dataAccessStrategy.getConverter(); this.mappingContext = this.converter.getMappingContext(); + setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT); } /* @@ -122,7 +126,8 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new R2dbcQueryLookupStrategy(this.databaseClient, evaluationContextProvider, this.converter, + return Optional.of(new R2dbcQueryLookupStrategy(this.databaseClient, + (ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, this.converter, this.dataAccessStrategy)); } @@ -151,17 +156,19 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy { private final DatabaseClient databaseClient; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider; private final R2dbcConverter converter; private final ReactiveDataAccessStrategy dataAccessStrategy; + private final ExpressionParser parser = new CachingExpressionParser(EXPRESSION_PARSER); R2dbcQueryLookupStrategy(DatabaseClient databaseClient, - QueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter, + ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy) { this.databaseClient = databaseClient; this.evaluationContextProvider = evaluationContextProvider; this.converter = converter; this.dataAccessStrategy = dataAccessStrategy; + } /* @@ -179,9 +186,9 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.databaseClient, this.converter, - EXPRESSION_PARSER, this.evaluationContextProvider); + parser, this.evaluationContextProvider); } else if (queryMethod.hasAnnotatedQuery()) { - return new StringBasedR2dbcQuery(queryMethod, this.databaseClient, this.converter, EXPRESSION_PARSER, + return new StringBasedR2dbcQuery(queryMethod, this.databaseClient, this.converter, parser, this.evaluationContextProvider); } else { return new PartTreeR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy); diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java index b4042512..3ec5db2b 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java @@ -23,6 +23,7 @@ import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; @@ -52,6 +53,7 @@ public class R2dbcRepositoryFactoryBean, S, ID exten */ public R2dbcRepositoryFactoryBean(Class repositoryInterface) { super(repositoryInterface); + setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT); } /** diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java index 986fa4d0..a523ac3a 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java @@ -38,7 +38,6 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.dialect.DialectResolver; @@ -49,6 +48,7 @@ import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.r2dbc.core.DatabaseClient; /** * Unit tests for {@link PartTreeR2dbcQuery}. @@ -97,7 +97,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" })); + BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, "John"); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); @@ -109,7 +109,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = r2dbcQuery.createQuery((getAccessor(queryMethod, new Object[] { null }))); + BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, new Object[] { null }); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name IS NULL"); @@ -121,7 +121,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("existsByFirstName", String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery query = r2dbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" }))); + BindableQuery query = createQuery(queryMethod, r2dbcQuery, "John"); assertThat(query.get()) .isEqualTo("SELECT " + TABLE + ".id FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1"); @@ -133,7 +133,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameAndFirstName", String.class, String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" })); + BindableQuery bindableQuery = createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { "Doe", "John" })); assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name = $1 AND (" + TABLE + ".first_name = $2)"); @@ -145,7 +145,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameOrFirstName", String.class, String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" })); + BindableQuery bindableQuery = createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { "Doe", "John" })); assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name = $1 OR (" + TABLE + ".first_name = $2)"); @@ -160,7 +160,7 @@ public class PartTreeR2dbcQueryUnitTests { Date from = new Date(); Date to = new Date(); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { from, to }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth BETWEEN $1 AND $2"); @@ -180,7 +180,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age < $1"); @@ -193,7 +193,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age <= $1"); @@ -206,7 +206,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age > $1"); @@ -219,7 +219,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age >= $1"); @@ -232,7 +232,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth > $1"); @@ -244,7 +244,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth < $1"); @@ -257,7 +257,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NULL"); @@ -270,7 +270,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NOT NULL"); @@ -283,7 +283,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); @@ -296,7 +296,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1"); @@ -309,7 +309,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); @@ -323,7 +323,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); @@ -337,7 +337,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); @@ -351,7 +351,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); @@ -365,7 +365,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); @@ -379,7 +379,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); @@ -393,7 +393,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1"); @@ -407,7 +407,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); @@ -421,7 +421,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name DESC"); @@ -433,7 +433,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name ASC"); @@ -445,7 +445,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Doe" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name != $1"); @@ -459,7 +459,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { Collections.singleton(25) }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IN ($1)"); @@ -472,7 +472,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { Collections.singleton(25) }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age NOT IN ($1)"); @@ -485,7 +485,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = TRUE"); @@ -498,7 +498,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = FALSE"); @@ -511,7 +511,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE UPPER(" + TABLE + ".first_name) = UPPER($1)"); @@ -525,7 +525,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); assertThatIllegalStateException() - .isThrownBy(() -> r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L }))); + .isThrownBy(() -> createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { 1L }))); } @Test // gh-282 @@ -554,7 +554,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); assertThatIllegalArgumentException() - .isThrownBy(() -> r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[0]))); + .isThrownBy(() -> createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[0]))); } @Test // gh-282 @@ -565,7 +565,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); assertThatIllegalArgumentException() - .isThrownBy(() -> r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[0]))); + .isThrownBy(() -> createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[0]))); } @Test // gh-282 @@ -575,7 +575,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 3"); @@ -588,7 +588,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1"); @@ -601,7 +601,7 @@ public class PartTreeR2dbcQueryUnitTests { PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); + BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); assertThat(bindableQuery.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); } @@ -612,7 +612,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findDistinctByFirstName", String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" })); + BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, "John"); assertThat(bindableQuery.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE + ".foo FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); @@ -624,12 +624,20 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("countByFirstName", String.class); PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy); - BindableQuery query = r2dbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" }))); + BindableQuery query = createQuery(queryMethod, r2dbcQuery, "John"); assertThat(query.get()) .isEqualTo("SELECT COUNT(users.id) FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); } + private BindableQuery createQuery(R2dbcQueryMethod queryMethod, PartTreeR2dbcQuery r2dbcQuery, Object... parameters) { + return createQuery(r2dbcQuery, getAccessor(queryMethod, parameters)); + } + + private BindableQuery createQuery(PartTreeR2dbcQuery r2dbcQuery, RelationalParametersParameterAccessor accessor) { + return r2dbcQuery.createQuery(accessor).block(); + } + private R2dbcQueryMethod getQueryMethod(String methodName, Class... parameterTypes) throws Exception { Method method = UserRepository.class.getMethod(methodName, parameterTypes); return new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class), diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java index 66b68f2f..a507d606 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java @@ -28,21 +28,20 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.domain.Sort; -import org.springframework.data.geo.Point; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; import org.springframework.data.r2dbc.repository.Query; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; -import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.Param; +import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec; import org.springframework.util.ReflectionUtils; /** @@ -81,7 +80,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByLastname", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -95,7 +94,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByLastnamePositional", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -109,7 +108,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByNamedParameter", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :lastname"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -123,7 +122,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByNamedBindMarker", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = @lastname"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -137,7 +136,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findNotByNamedBindMarker", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :unknown"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -151,7 +150,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simpleSpel"); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod()); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -165,7 +164,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simpleIndexedSpel", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -180,7 +179,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simplePositionalSpel", String.class, String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White", "Walter"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()) .isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__ and firstname = :firstname"); @@ -197,7 +196,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simpleNamedSpel", String.class, String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White", "Walter"); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()) .isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__ and firstname = :firstname"); @@ -214,7 +213,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("queryWithSpelObject", Person.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), new Person("Walter")); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -229,7 +228,7 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("queryWithUnusedParameter", String.class, Sort.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "Walter", null); - BindableQuery stringQuery = query.createQuery(accessor); + BindableQuery stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :name"); assertThat(stringQuery.bind(bindSpec)).isNotNull(); @@ -245,7 +244,7 @@ public class StringBasedR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext()); return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, PARSER, - ExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT); + ReactiveQueryMethodEvaluationContextProvider.DEFAULT); } @SuppressWarnings("unused")