Add support for Value Expressions for Repository Query methods.
Closes #1904 Original pull request: #1906
This commit is contained in:
committed by
Mark Paluch
parent
fd4aedc760
commit
d526cd3a22
@@ -20,16 +20,20 @@ import static org.springframework.data.jdbc.repository.query.JdbcQueryExecution.
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.sql.SQLType;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcValue;
|
||||
@@ -37,14 +41,17 @@ import org.springframework.data.jdbc.support.JdbcUtil;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
|
||||
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.SpelEvaluator;
|
||||
import org.springframework.data.repository.query.SpelQueryContext;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
@@ -74,12 +81,14 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or use the javac flag -parameters";
|
||||
private final JdbcConverter converter;
|
||||
private final RowMapperFactory rowMapperFactory;
|
||||
private final SpelEvaluator spelEvaluator;
|
||||
private final ValueExpressionQueryRewriter.ParsedQuery parsedQuery;
|
||||
private final boolean containsSpelExpressions;
|
||||
private final String query;
|
||||
|
||||
private final CachedRowMapperFactory cachedRowMapperFactory;
|
||||
private final CachedResultSetExtractorFactory cachedResultSetExtractorFactory;
|
||||
private final ValueExpressionDelegate delegate;
|
||||
private final List<Map.Entry<String, String>> parameterBindings;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
|
||||
@@ -88,7 +97,9 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
|
||||
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
|
||||
*/
|
||||
@Deprecated(since = "3.4")
|
||||
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
|
||||
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
@@ -116,6 +127,23 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
evaluationContextProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
|
||||
* and {@link RowMapperFactory}.
|
||||
*
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param rowMapperFactory must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @since 3.4
|
||||
*/
|
||||
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
|
||||
RowMapperFactory rowMapperFactory, JdbcConverter converter,
|
||||
ValueExpressionDelegate delegate) {
|
||||
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter, delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
|
||||
* and {@link RowMapperFactory}.
|
||||
@@ -125,15 +153,13 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param rowMapperFactory must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @since 3.4
|
||||
*/
|
||||
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
|
||||
RowMapperFactory rowMapperFactory, JdbcConverter converter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
ValueExpressionDelegate delegate) {
|
||||
super(queryMethod, operations);
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty");
|
||||
Assert.notNull(rowMapperFactory, "RowMapperFactory must not be null");
|
||||
|
||||
@@ -160,13 +186,40 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
this.cachedResultSetExtractorFactory = new CachedResultSetExtractorFactory(
|
||||
this.cachedRowMapperFactory::getRowMapper);
|
||||
|
||||
SpelQueryContext.EvaluatingSpelQueryContext queryContext = SpelQueryContext
|
||||
.of((counter, expression) -> String.format("__$synthetic$__%d", counter + 1), String::concat)
|
||||
.withEvaluationContextProvider(evaluationContextProvider);
|
||||
this.parameterBindings = new ArrayList<>();
|
||||
|
||||
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(delegate, (counter, expression) -> {
|
||||
String newName = String.format("__$synthetic$__%d", counter + 1);
|
||||
parameterBindings.add(new AbstractMap.SimpleEntry<>(newName, expression));
|
||||
return newName;
|
||||
}, String::concat);
|
||||
|
||||
this.query = query;
|
||||
this.spelEvaluator = queryContext.parse(this.query, getQueryMethod().getParameters());
|
||||
this.containsSpelExpressions = !this.spelEvaluator.getQueryString().equals(this.query);
|
||||
this.parsedQuery = rewriter.parse(this.query);
|
||||
this.containsSpelExpressions = !this.parsedQuery.getQueryString().equals(this.query);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
|
||||
* and {@link RowMapperFactory}.
|
||||
*
|
||||
* @param query must not be {@literal null} or empty.
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param rowMapperFactory must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @since 3.4
|
||||
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
|
||||
*/
|
||||
@Deprecated(since = "3.4")
|
||||
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
|
||||
RowMapperFactory rowMapperFactory, JdbcConverter converter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(null,
|
||||
rootObject -> evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(), new Object[] { rootObject })), ValueExpressionParser.create(
|
||||
SpelExpressionParser::new)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,15 +231,19 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
JdbcQueryExecution<?> queryExecution = createJdbcQueryExecution(accessor, processor);
|
||||
MapSqlParameterSource parameterMap = this.bindParameters(accessor);
|
||||
|
||||
return queryExecution.execute(processSpelExpressions(objects, parameterMap), parameterMap);
|
||||
return queryExecution.execute(processSpelExpressions(objects, accessor.getBindableParameters(), parameterMap), parameterMap);
|
||||
}
|
||||
|
||||
private String processSpelExpressions(Object[] objects, MapSqlParameterSource parameterMap) {
|
||||
private String processSpelExpressions(Object[] objects, Parameters<?, ?> bindableParameters, MapSqlParameterSource parameterMap) {
|
||||
|
||||
if (containsSpelExpressions) {
|
||||
|
||||
spelEvaluator.evaluate(objects).forEach(parameterMap::addValue);
|
||||
return spelEvaluator.getQueryString();
|
||||
ValueEvaluationContext evaluationContext = delegate.createValueContextProvider(bindableParameters)
|
||||
.getEvaluationContext(objects);
|
||||
for (Map.Entry<String, String> entry : parameterBindings) {
|
||||
parameterMap.addValue(
|
||||
entry.getKey(), delegate.parse(entry.getValue()).evaluate(evaluationContext));
|
||||
}
|
||||
return parsedQuery.getQueryString();
|
||||
}
|
||||
|
||||
return this.query;
|
||||
|
||||
@@ -41,8 +41,8 @@ import org.springframework.data.relational.repository.support.RelationalQueryLoo
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.SingleColumnRowMapper;
|
||||
@@ -73,12 +73,12 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
private final JdbcConverter converter;
|
||||
private final QueryMappingConfiguration queryMappingConfiguration;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
protected final ValueExpressionDelegate delegate;
|
||||
|
||||
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
|
||||
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
|
||||
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ValueExpressionDelegate delegate) {
|
||||
|
||||
super(context, dialect);
|
||||
|
||||
@@ -86,7 +86,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
Assert.notNull(converter, "JdbcConverter must not be null");
|
||||
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
|
||||
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null");
|
||||
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
|
||||
|
||||
this.context = context;
|
||||
this.publisher = publisher;
|
||||
@@ -94,7 +94,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
this.converter = converter;
|
||||
this.queryMappingConfiguration = queryMappingConfiguration;
|
||||
this.operations = operations;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public RelationalMappingContext getMappingContext() {
|
||||
@@ -112,10 +112,10 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
|
||||
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
|
||||
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ValueExpressionDelegate delegate) {
|
||||
|
||||
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
|
||||
evaluationContextProvider);
|
||||
delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,9 +143,9 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
|
||||
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
|
||||
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
|
||||
@Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
@Nullable BeanFactory beanfactory, ValueExpressionDelegate delegate) {
|
||||
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
|
||||
evaluationContextProvider);
|
||||
delegate);
|
||||
|
||||
this.rowMapperFactory = new BeanFactoryRowMapperFactory(beanfactory);
|
||||
}
|
||||
@@ -166,7 +166,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
String queryString = evaluateTableExpressions(repositoryMetadata, queryMethod.getRequiredQuery());
|
||||
|
||||
return new StringBasedJdbcQuery(queryString, queryMethod, getOperations(), rowMapperFactory, getConverter(),
|
||||
evaluationContextProvider);
|
||||
delegate);
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
@@ -235,10 +235,10 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
|
||||
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
|
||||
CreateQueryLookupStrategy createStrategy,
|
||||
DeclaredQueryLookupStrategy lookupStrategy, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
DeclaredQueryLookupStrategy lookupStrategy, ValueExpressionDelegate delegate) {
|
||||
|
||||
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
|
||||
evaluationContextProvider);
|
||||
delegate);
|
||||
|
||||
Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null");
|
||||
Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null");
|
||||
@@ -284,20 +284,20 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPublisher publisher,
|
||||
@Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
|
||||
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
|
||||
@Nullable BeanFactory beanFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
@Nullable BeanFactory beanFactory, ValueExpressionDelegate delegate) {
|
||||
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
|
||||
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
|
||||
Assert.notNull(converter, "JdbcConverter must not be null");
|
||||
Assert.notNull(dialect, "Dialect must not be null");
|
||||
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
|
||||
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
|
||||
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
|
||||
|
||||
CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context,
|
||||
converter, dialect, queryMappingConfiguration, operations, evaluationContextProvider);
|
||||
converter, dialect, queryMappingConfiguration, operations, delegate);
|
||||
|
||||
DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks,
|
||||
context, converter, dialect, queryMappingConfiguration, operations, beanFactory, evaluationContextProvider);
|
||||
context, converter, dialect, queryMappingConfiguration, operations, beanFactory, delegate);
|
||||
|
||||
Key keyToUse = key != null ? key : Key.CREATE_IF_NOT_FOUND;
|
||||
|
||||
@@ -311,7 +311,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
case CREATE_IF_NOT_FOUND:
|
||||
return new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, context, converter, dialect,
|
||||
queryMappingConfiguration, operations, createQueryLookupStrategy, declaredQueryLookupStrategy,
|
||||
evaluationContextProvider);
|
||||
delegate);
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -132,12 +132,10 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
return SimpleJdbcRepository.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(JdbcQueryLookupStrategy.create(key, publisher, entityCallbacks, context, converter, dialect,
|
||||
queryMappingConfiguration, operations, beanFactory, evaluationContextProvider));
|
||||
queryMappingConfiguration, operations, beanFactory, valueExpressionDelegate));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,10 @@ import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
@@ -41,6 +44,7 @@ import org.springframework.data.domain.Limit;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
|
||||
@@ -53,9 +57,9 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
|
||||
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
@@ -82,7 +86,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
NamedParameterJdbcOperations operations;
|
||||
RelationalMappingContext context;
|
||||
JdbcConverter converter;
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
ValueExpressionDelegate delegate;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
@@ -91,7 +95,8 @@ class StringBasedJdbcQueryUnitTests {
|
||||
this.operations = mock(NamedParameterJdbcOperations.class);
|
||||
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
|
||||
this.converter = new MappingJdbcConverter(context, mock(RelationResolver.class));
|
||||
this.evaluationContextProvider = mock(QueryMethodEvaluationContextProvider.class);
|
||||
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), new StaticApplicationContext());
|
||||
this.delegate = new ValueExpressionDelegate(accessor, ValueExpressionParser.create());
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-165
|
||||
@@ -248,7 +253,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
JdbcQueryMethod queryMethod = createMethod("sliceAll", Pageable.class);
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, evaluationContextProvider))
|
||||
() -> new StringBasedJdbcQuery(queryMethod, operations, result -> defaultRowMapper, converter, delegate))
|
||||
.isInstanceOf(UnsupportedOperationException.class)
|
||||
.hasMessageContaining("Slice queries are not supported using string-based queries");
|
||||
}
|
||||
@@ -259,7 +264,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
JdbcQueryMethod queryMethod = createMethod("pageAll", Pageable.class);
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, evaluationContextProvider))
|
||||
() -> new StringBasedJdbcQuery(queryMethod, operations, result -> defaultRowMapper, converter, delegate))
|
||||
.isInstanceOf(UnsupportedOperationException.class)
|
||||
.hasMessageContaining("Page queries are not supported using string-based queries");
|
||||
}
|
||||
@@ -270,7 +275,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
JdbcQueryMethod queryMethod = createMethod("unsupportedLimitQuery", String.class, Limit.class);
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, evaluationContextProvider))
|
||||
() -> new StringBasedJdbcQuery(queryMethod, operations, result -> defaultRowMapper, converter, delegate))
|
||||
.isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@@ -350,11 +355,11 @@ class StringBasedJdbcQueryUnitTests {
|
||||
|
||||
List<EvaluationContextExtension> list = new ArrayList<>();
|
||||
list.add(new MyEvaluationContextProvider());
|
||||
QueryMethodEvaluationContextProvider evaluationContextProviderImpl = new ExtensionAwareQueryMethodEvaluationContextProvider(
|
||||
list);
|
||||
|
||||
StringBasedJdbcQuery sut = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter,
|
||||
evaluationContextProviderImpl);
|
||||
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), list);
|
||||
this.delegate = new ValueExpressionDelegate(accessor, ValueExpressionParser.create());
|
||||
|
||||
StringBasedJdbcQuery sut = new StringBasedJdbcQuery(queryMethod, operations, result -> defaultRowMapper, converter, delegate);
|
||||
|
||||
ArgumentCaptor<SqlParameterSource> paramSource = ArgumentCaptor.forClass(SqlParameterSource.class);
|
||||
ArgumentCaptor<String> query = ArgumentCaptor.forClass(String.class);
|
||||
@@ -398,7 +403,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
: this.converter;
|
||||
|
||||
StringBasedJdbcQuery query = new StringBasedJdbcQuery(method.getDeclaredQuery(), method, operations, result -> mock(RowMapper.class),
|
||||
converter, evaluationContextProvider);
|
||||
converter, delegate);
|
||||
|
||||
query.execute(arguments);
|
||||
|
||||
@@ -434,7 +439,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
}
|
||||
|
||||
private StringBasedJdbcQuery createQuery(JdbcQueryMethod queryMethod, String preparedReference, Object value) {
|
||||
return new StringBasedJdbcQuery(queryMethod, operations, new StubRowMapperFactory(preparedReference, value), converter, evaluationContextProvider);
|
||||
return new StringBasedJdbcQuery(queryMethod, operations, new StubRowMapperFactory(preparedReference, value), converter, delegate);
|
||||
}
|
||||
|
||||
interface MyRepository extends Repository<Object, Long> {
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
@@ -137,7 +138,7 @@ class JdbcQueryLookupStrategyUnitTests {
|
||||
.registerRowMapper(NumberFormat.class, numberFormatMapper);
|
||||
|
||||
QueryLookupStrategy queryLookupStrategy = JdbcQueryLookupStrategy.create(key, publisher, callbacks, mappingContext,
|
||||
converter, H2Dialect.INSTANCE, mappingConfiguration, operations, null, evaluationContextProvider);
|
||||
converter, H2Dialect.INSTANCE, mappingConfiguration, operations, null, ValueExpressionDelegate.create());
|
||||
|
||||
assertThat(queryLookupStrategy).isInstanceOf(expectedClass);
|
||||
}
|
||||
@@ -157,7 +158,7 @@ class JdbcQueryLookupStrategyUnitTests {
|
||||
QueryMappingConfiguration mappingConfiguration) {
|
||||
|
||||
QueryLookupStrategy queryLookupStrategy = JdbcQueryLookupStrategy.create(key, publisher, callbacks, mappingContext,
|
||||
converter, H2Dialect.INSTANCE, mappingConfiguration, operations, null, evaluationContextProvider);
|
||||
converter, H2Dialect.INSTANCE, mappingConfiguration, operations, null, ValueExpressionDelegate.create());
|
||||
|
||||
Method method = ReflectionUtils.findMethod(MyRepository.class, name);
|
||||
return queryLookupStrategy.resolveQuery(method, metadata, projectionFactory, namedQueries);
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
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.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
@@ -30,12 +32,12 @@ import org.springframework.r2dbc.core.Parameter;
|
||||
*/
|
||||
class DefaultR2dbcSpELExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
|
||||
|
||||
private final ExpressionParser parser;
|
||||
private final ValueExpressionDelegate delegate;
|
||||
|
||||
private final EvaluationContext context;
|
||||
private final ValueEvaluationContext context;
|
||||
|
||||
DefaultR2dbcSpELExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
|
||||
this.parser = parser;
|
||||
DefaultR2dbcSpELExpressionEvaluator(ValueExpressionDelegate delegate, ValueEvaluationContext context) {
|
||||
this.delegate = delegate;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@@ -51,12 +53,12 @@ class DefaultR2dbcSpELExpressionEvaluator implements R2dbcSpELExpressionEvaluato
|
||||
@Override
|
||||
public Parameter evaluate(String expression) {
|
||||
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
ValueExpression expr = delegate.parse(expression);
|
||||
|
||||
Object value = expr.getValue(context, Object.class);
|
||||
Class<?> valueType = expr.getValueType(context);
|
||||
Object value = expr.evaluate(context);
|
||||
Class<?> valueType = value != null ? value.getClass() : Object.class;
|
||||
|
||||
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class);
|
||||
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,8 @@ package org.springframework.data.r2dbc.repository.query;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.query.SpelQueryContext;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
|
||||
|
||||
/**
|
||||
* Query using Spring Expression Language to indicate parameter bindings. Queries using SpEL use {@code :#{…}} to
|
||||
@@ -48,18 +49,17 @@ class ExpressionQuery {
|
||||
* @param query the query string to parse.
|
||||
* @return the parsed {@link ExpressionQuery}.
|
||||
*/
|
||||
public static ExpressionQuery create(String query) {
|
||||
public static ExpressionQuery create(ValueExpressionParser parser, String query) {
|
||||
|
||||
List<ParameterBinding> parameterBindings = new ArrayList<>();
|
||||
|
||||
SpelQueryContext queryContext = SpelQueryContext.of((counter, expression) -> {
|
||||
|
||||
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);
|
||||
|
||||
SpelQueryContext.SpelExtractor parsed = queryContext.parse(query);
|
||||
ValueExpressionQueryRewriter.ParsedQuery parsed = rewriter.parse(query);
|
||||
|
||||
return new ExpressionQuery(parsed.getQueryString(), parameterBindings);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.expression.ReactiveValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
@@ -29,8 +33,10 @@ import org.springframework.data.r2dbc.dialect.BindTargetBinder;
|
||||
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.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@@ -52,10 +58,10 @@ 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;
|
||||
private final ReactiveDataAccessStrategy dataAccessStrategy;
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
private final ValueEvaluationContextProvider valueContextProvider;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedR2dbcQuery} for the given {@link StringBasedR2dbcQuery}, {@link DatabaseClient},
|
||||
@@ -67,7 +73,9 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
|
||||
*/
|
||||
@Deprecated(since = "3.4")
|
||||
public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, R2dbcEntityOperations entityOperations,
|
||||
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
@@ -79,26 +87,60 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
* Create a new {@link StringBasedR2dbcQuery} for the given {@code query}, {@link R2dbcQueryMethod},
|
||||
* {@link DatabaseClient}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @param entityOperations must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
|
||||
*/
|
||||
@Deprecated(since = "3.4")
|
||||
public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, R2dbcEntityOperations entityOperations,
|
||||
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(query, method, entityOperations, converter, dataAccessStrategy, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link StringBasedR2dbcQuery} for the given {@code query}, {@link R2dbcQueryMethod},
|
||||
* {@link DatabaseClient}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param entityOperations must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedR2dbcQuery(R2dbcQueryMethod method, R2dbcEntityOperations entityOperations,
|
||||
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
this(method.getRequiredAnnotatedQuery(), method, entityOperations, converter, dataAccessStrategy, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link StringBasedR2dbcQuery} for the given {@code query}, {@link R2dbcQueryMethod},
|
||||
* {@link DatabaseClient}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param entityOperations must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, R2dbcEntityOperations entityOperations,
|
||||
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
super(method, entityOperations, converter);
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.valueExpressionDelegate = valueExpressionDelegate;
|
||||
|
||||
Assert.hasText(query, "Query must not be empty");
|
||||
|
||||
this.dataAccessStrategy = dataAccessStrategy;
|
||||
this.expressionQuery = ExpressionQuery.create(query);
|
||||
this.expressionQuery = ExpressionQuery.create(valueExpressionDelegate, query);
|
||||
this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery, dataAccessStrategy);
|
||||
this.valueContextProvider = valueExpressionDelegate.createValueContextProvider(
|
||||
method.getParameters());
|
||||
this.expressionDependencies = createExpressionDependencies();
|
||||
|
||||
if (method.isSliceQuery()) {
|
||||
@@ -126,7 +168,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
List<ExpressionDependencies> dependencies = new ArrayList<>();
|
||||
|
||||
for (ExpressionQuery.ParameterBinding binding : expressionQuery.getBindings()) {
|
||||
dependencies.add(ExpressionDependencies.discover(expressionParser.parseExpression(binding.getExpression())));
|
||||
dependencies.add(valueExpressionDelegate.parse(binding.getExpression()).getExpressionDependencies());
|
||||
}
|
||||
|
||||
return ExpressionDependencies.merged(dependencies);
|
||||
@@ -160,11 +202,11 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
|
||||
}
|
||||
|
||||
private Mono<R2dbcSpELExpressionEvaluator> getSpelEvaluator(RelationalParameterAccessor accessor) {
|
||||
|
||||
return evaluationContextProvider
|
||||
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), expressionDependencies)
|
||||
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
|
||||
return ((ReactiveValueEvaluationContextProvider) valueContextProvider)
|
||||
.getEvaluationContextLater(accessor.getValues(), expressionDependencies)
|
||||
.<R2dbcSpELExpressionEvaluator> map(
|
||||
context -> new DefaultR2dbcSpELExpressionEvaluator(expressionParser, context))
|
||||
context -> new DefaultR2dbcSpELExpressionEvaluator(valueExpressionDelegate, context))
|
||||
.defaultIfEmpty(DefaultR2dbcSpELExpressionEvaluator.unsupported());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +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.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<String, Expression> cache = new ConcurrentHashMap<>();
|
||||
|
||||
CachingExpressionParser(ExpressionParser delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression parseExpression(String expressionString) throws ParseException {
|
||||
return cache.computeIfAbsent(expressionString, delegate::parseExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression parseExpression(String expressionString, ParserContext context) throws ParseException {
|
||||
throw new UnsupportedOperationException("Parsing using ParserContext is not supported");
|
||||
}
|
||||
}
|
||||
@@ -37,13 +37,12 @@ import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
|
||||
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.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -56,8 +55,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
|
||||
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
private final DatabaseClient databaseClient;
|
||||
private final ReactiveDataAccessStrategy dataAccessStrategy;
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
|
||||
@@ -116,11 +113,9 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new R2dbcQueryLookupStrategy(this.operations,
|
||||
(ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, this.converter,
|
||||
this.dataAccessStrategy));
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(new R2dbcQueryLookupStrategy(operations, new CachingValueExpressionDelegate(valueExpressionDelegate), converter, dataAccessStrategy));
|
||||
}
|
||||
|
||||
public <T, ID> RelationalEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
@@ -145,19 +140,17 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
private static class R2dbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
|
||||
|
||||
private final R2dbcEntityOperations entityOperations;
|
||||
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final R2dbcConverter converter;
|
||||
private final ValueExpressionDelegate delegate;
|
||||
private final ReactiveDataAccessStrategy dataAccessStrategy;
|
||||
private final ExpressionParser parser = new CachingExpressionParser(EXPRESSION_PARSER);
|
||||
|
||||
R2dbcQueryLookupStrategy(R2dbcEntityOperations entityOperations,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter,
|
||||
ValueExpressionDelegate delegate, R2dbcConverter converter,
|
||||
ReactiveDataAccessStrategy dataAccessStrategy) {
|
||||
|
||||
super(converter.getMappingContext(), dataAccessStrategy.getDialect());
|
||||
|
||||
this.delegate = delegate;
|
||||
this.entityOperations = entityOperations;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.converter = converter;
|
||||
this.dataAccessStrategy = dataAccessStrategy;
|
||||
}
|
||||
@@ -175,8 +168,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
: queryMethod.getRequiredAnnotatedQuery();
|
||||
query = evaluateTableExpressions(metadata, query);
|
||||
|
||||
return new StringBasedR2dbcQuery(query, queryMethod, this.entityOperations, this.converter,
|
||||
this.dataAccessStrategy, parser, this.evaluationContextProvider);
|
||||
return new StringBasedR2dbcQuery(query, queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy, this.delegate);
|
||||
|
||||
} else {
|
||||
return new PartTreeR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy);
|
||||
|
||||
@@ -17,9 +17,10 @@ package org.springframework.data.r2dbc.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ExpressionQuery}.
|
||||
*
|
||||
@@ -32,18 +33,15 @@ class ExpressionQueryUnitTests {
|
||||
void bindsMultipleSpelParametersCorrectly() {
|
||||
|
||||
ExpressionQuery query = ExpressionQuery
|
||||
.create("INSERT IGNORE INTO table (x, y) VALUES (:#{#point.x}, :#{#point.y})");
|
||||
.create(ValueExpressionParser.create(), "INSERT IGNORE INTO table (x, y) VALUES (:#{#point.x}, :${point.y})");
|
||||
|
||||
assertThat(query.getQuery())
|
||||
.isEqualTo("INSERT IGNORE INTO table (x, y) VALUES (:__synthetic_0__, :__synthetic_1__)");
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(query.getBindings()).hasSize(2);
|
||||
softly.assertThat(query.getBindings().get(0).getExpression()).isEqualTo("#point.x");
|
||||
softly.assertThat(query.getBindings().get(0).getParameterName()).isEqualTo("__synthetic_0__");
|
||||
softly.assertThat(query.getBindings().get(1).getExpression()).isEqualTo("#point.y");
|
||||
softly.assertThat(query.getBindings().get(1).getParameterName()).isEqualTo("__synthetic_1__");
|
||||
});
|
||||
assertThat(query.getBindings()).hasSize(2);
|
||||
assertThat(query.getBindings().get(0).getExpression()).isEqualTo("#{#point.x}");
|
||||
assertThat(query.getBindings().get(0).getParameterName()).isEqualTo("__synthetic_0__");
|
||||
assertThat(query.getBindings().get(1).getExpression()).isEqualTo("${point.y}");
|
||||
assertThat(query.getBindings().get(1).getParameterName()).isEqualTo("__synthetic_1__");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import reactor.test.StepVerifier;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -33,8 +34,10 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.data.domain.Limit;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
@@ -51,8 +54,10 @@ 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.Param;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
@@ -67,7 +72,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class StringBasedR2dbcQueryUnitTests {
|
||||
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
|
||||
|
||||
@Mock private R2dbcEntityOperations entityOperations;
|
||||
@Mock private BindTarget bindTarget;
|
||||
@@ -77,6 +82,7 @@ public class StringBasedR2dbcQueryUnitTests {
|
||||
private ReactiveDataAccessStrategy accessStrategy;
|
||||
private ProjectionFactory factory;
|
||||
private RepositoryMetadata metadata;
|
||||
private MockEnvironment environment;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -86,6 +92,7 @@ public class StringBasedR2dbcQueryUnitTests {
|
||||
this.accessStrategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE, converter);
|
||||
this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class);
|
||||
this.factory = new SpelAwareProxyProjectionFactory();
|
||||
this.environment = new MockEnvironment();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -322,8 +329,10 @@ public class StringBasedR2dbcQueryUnitTests {
|
||||
|
||||
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext());
|
||||
|
||||
return new StringBasedR2dbcQuery(queryMethod, entityOperations, converter, accessStrategy, PARSER,
|
||||
ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(
|
||||
environment, Collections.emptySet());
|
||||
|
||||
return new StringBasedR2dbcQuery(queryMethod, entityOperations, converter, accessStrategy, new ValueExpressionDelegate(accessor, PARSER));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -24,7 +24,8 @@ import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.data.r2dbc.dialect.H2Dialect;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ReactiveExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -49,8 +50,8 @@ class R2dbcRepositoryFactoryBeanUnitTests {
|
||||
Object factory = ReflectionTestUtils.getField(factoryBean, "factory");
|
||||
Object evaluationContextProvider = ReflectionTestUtils.getField(factory, "evaluationContextProvider");
|
||||
|
||||
assertThat(evaluationContextProvider).isInstanceOf(ReactiveExtensionAwareQueryMethodEvaluationContextProvider.class)
|
||||
.isNotEqualTo(ReactiveExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
assertThat(evaluationContextProvider).isInstanceOf(ReactiveExtensionAwareEvaluationContextProvider.class)
|
||||
.isNotEqualTo(EvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
|
||||
static class Person {}
|
||||
|
||||
Reference in New Issue
Block a user