Skip to content

Commit d526cd3

Browse files
marcingrzejszczakmp911de
authored andcommitted
Add support for Value Expressions for Repository Query methods.
Closes #1904 Original pull request: #1906
1 parent fd4aedc commit d526cd3

File tree

13 files changed

+218
-164
lines changed

13 files changed

+218
-164
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,38 @@
2020
import java.lang.reflect.Array;
2121
import java.lang.reflect.Constructor;
2222
import java.sql.SQLType;
23+
import java.util.AbstractMap;
2324
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.LinkedHashMap;
2627
import java.util.List;
28+
import java.util.Map;
2729
import java.util.function.Function;
2830
import java.util.function.Supplier;
2931

3032
import org.springframework.beans.BeanInstantiationException;
3133
import org.springframework.beans.BeanUtils;
3234
import org.springframework.beans.factory.BeanFactory;
35+
import org.springframework.data.expression.ValueEvaluationContext;
36+
import org.springframework.data.expression.ValueExpressionParser;
3337
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
3438
import org.springframework.data.jdbc.core.convert.JdbcConverter;
3539
import org.springframework.data.jdbc.core.mapping.JdbcValue;
3640
import org.springframework.data.jdbc.support.JdbcUtil;
3741
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3842
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
3943
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
44+
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
4045
import org.springframework.data.repository.query.Parameter;
4146
import org.springframework.data.repository.query.Parameters;
4247
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
48+
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
4349
import org.springframework.data.repository.query.ResultProcessor;
44-
import org.springframework.data.repository.query.SpelEvaluator;
45-
import org.springframework.data.repository.query.SpelQueryContext;
50+
import org.springframework.data.repository.query.ValueExpressionDelegate;
51+
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
4652
import org.springframework.data.util.Lazy;
4753
import org.springframework.data.util.TypeInformation;
54+
import org.springframework.expression.spel.standard.SpelExpressionParser;
4855
import org.springframework.jdbc.core.ResultSetExtractor;
4956
import org.springframework.jdbc.core.RowMapper;
5057
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -74,12 +81,14 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
7481
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";
7582
private final JdbcConverter converter;
7683
private final RowMapperFactory rowMapperFactory;
77-
private final SpelEvaluator spelEvaluator;
84+
private final ValueExpressionQueryRewriter.ParsedQuery parsedQuery;
7885
private final boolean containsSpelExpressions;
7986
private final String query;
8087

8188
private final CachedRowMapperFactory cachedRowMapperFactory;
8289
private final CachedResultSetExtractorFactory cachedResultSetExtractorFactory;
90+
private final ValueExpressionDelegate delegate;
91+
private final List<Map.Entry<String, String>> parameterBindings;
8392

8493
/**
8594
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
@@ -88,7 +97,9 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
8897
* @param queryMethod must not be {@literal null}.
8998
* @param operations must not be {@literal null}.
9099
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
100+
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
91101
*/
102+
@Deprecated(since = "3.4")
92103
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
93104
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter,
94105
QueryMethodEvaluationContextProvider evaluationContextProvider) {
@@ -116,6 +127,23 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
116127
evaluationContextProvider);
117128
}
118129

130+
/**
131+
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
132+
* and {@link RowMapperFactory}.
133+
*
134+
* @param queryMethod must not be {@literal null}.
135+
* @param operations must not be {@literal null}.
136+
* @param rowMapperFactory must not be {@literal null}.
137+
* @param converter must not be {@literal null}.
138+
* @param delegate must not be {@literal null}.
139+
* @since 3.4
140+
*/
141+
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
142+
RowMapperFactory rowMapperFactory, JdbcConverter converter,
143+
ValueExpressionDelegate delegate) {
144+
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter, delegate);
145+
}
146+
119147
/**
120148
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
121149
* and {@link RowMapperFactory}.
@@ -125,15 +153,13 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
125153
* @param operations must not be {@literal null}.
126154
* @param rowMapperFactory must not be {@literal null}.
127155
* @param converter must not be {@literal null}.
128-
* @param evaluationContextProvider must not be {@literal null}.
156+
* @param delegate must not be {@literal null}.
129157
* @since 3.4
130158
*/
131159
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
132160
RowMapperFactory rowMapperFactory, JdbcConverter converter,
133-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
134-
161+
ValueExpressionDelegate delegate) {
135162
super(queryMethod, operations);
136-
137163
Assert.hasText(query, "Query must not be null or empty");
138164
Assert.notNull(rowMapperFactory, "RowMapperFactory must not be null");
139165

@@ -160,13 +186,40 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
160186
this.cachedResultSetExtractorFactory = new CachedResultSetExtractorFactory(
161187
this.cachedRowMapperFactory::getRowMapper);
162188

163-
SpelQueryContext.EvaluatingSpelQueryContext queryContext = SpelQueryContext
164-
.of((counter, expression) -> String.format("__$synthetic$__%d", counter + 1), String::concat)
165-
.withEvaluationContextProvider(evaluationContextProvider);
189+
this.parameterBindings = new ArrayList<>();
190+
191+
ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(delegate, (counter, expression) -> {
192+
String newName = String.format("__$synthetic$__%d", counter + 1);
193+
parameterBindings.add(new AbstractMap.SimpleEntry<>(newName, expression));
194+
return newName;
195+
}, String::concat);
166196

167197
this.query = query;
168-
this.spelEvaluator = queryContext.parse(this.query, getQueryMethod().getParameters());
169-
this.containsSpelExpressions = !this.spelEvaluator.getQueryString().equals(this.query);
198+
this.parsedQuery = rewriter.parse(this.query);
199+
this.containsSpelExpressions = !this.parsedQuery.getQueryString().equals(this.query);
200+
this.delegate = delegate;
201+
}
202+
203+
/**
204+
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
205+
* and {@link RowMapperFactory}.
206+
*
207+
* @param query must not be {@literal null} or empty.
208+
* @param queryMethod must not be {@literal null}.
209+
* @param operations must not be {@literal null}.
210+
* @param rowMapperFactory must not be {@literal null}.
211+
* @param converter must not be {@literal null}.
212+
* @param evaluationContextProvider must not be {@literal null}.
213+
* @since 3.4
214+
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
215+
*/
216+
@Deprecated(since = "3.4")
217+
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
218+
RowMapperFactory rowMapperFactory, JdbcConverter converter,
219+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
220+
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(null,
221+
rootObject -> evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(), new Object[] { rootObject })), ValueExpressionParser.create(
222+
SpelExpressionParser::new)));
170223
}
171224

172225
@Override
@@ -178,15 +231,19 @@ public Object execute(Object[] objects) {
178231
JdbcQueryExecution<?> queryExecution = createJdbcQueryExecution(accessor, processor);
179232
MapSqlParameterSource parameterMap = this.bindParameters(accessor);
180233

181-
return queryExecution.execute(processSpelExpressions(objects, parameterMap), parameterMap);
234+
return queryExecution.execute(processSpelExpressions(objects, accessor.getBindableParameters(), parameterMap), parameterMap);
182235
}
183236

184-
private String processSpelExpressions(Object[] objects, MapSqlParameterSource parameterMap) {
237+
private String processSpelExpressions(Object[] objects, Parameters<?, ?> bindableParameters, MapSqlParameterSource parameterMap) {
185238

186239
if (containsSpelExpressions) {
187-
188-
spelEvaluator.evaluate(objects).forEach(parameterMap::addValue);
189-
return spelEvaluator.getQueryString();
240+
ValueEvaluationContext evaluationContext = delegate.createValueContextProvider(bindableParameters)
241+
.getEvaluationContext(objects);
242+
for (Map.Entry<String, String> entry : parameterBindings) {
243+
parameterMap.addValue(
244+
entry.getKey(), delegate.parse(entry.getValue()).evaluate(evaluationContext));
245+
}
246+
return parsedQuery.getQueryString();
190247
}
191248

192249
return this.query;

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
import org.springframework.data.repository.core.NamedQueries;
4242
import org.springframework.data.repository.core.RepositoryMetadata;
4343
import org.springframework.data.repository.query.QueryLookupStrategy;
44-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
4544
import org.springframework.data.repository.query.RepositoryQuery;
45+
import org.springframework.data.repository.query.ValueExpressionDelegate;
4646
import org.springframework.jdbc.core.ResultSetExtractor;
4747
import org.springframework.jdbc.core.RowMapper;
4848
import org.springframework.jdbc.core.SingleColumnRowMapper;
@@ -73,28 +73,28 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
7373
private final JdbcConverter converter;
7474
private final QueryMappingConfiguration queryMappingConfiguration;
7575
private final NamedParameterJdbcOperations operations;
76-
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
76+
protected final ValueExpressionDelegate delegate;
7777

7878
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
7979
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
8080
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
81-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
81+
ValueExpressionDelegate delegate) {
8282

8383
super(context, dialect);
8484

8585
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
8686
Assert.notNull(converter, "JdbcConverter must not be null");
8787
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
8888
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
89-
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null");
89+
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
9090

9191
this.context = context;
9292
this.publisher = publisher;
9393
this.callbacks = callbacks;
9494
this.converter = converter;
9595
this.queryMappingConfiguration = queryMappingConfiguration;
9696
this.operations = operations;
97-
this.evaluationContextProvider = evaluationContextProvider;
97+
this.delegate = delegate;
9898
}
9999

100100
public RelationalMappingContext getMappingContext() {
@@ -112,10 +112,10 @@ static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
112112
CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
113113
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
114114
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
115-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
115+
ValueExpressionDelegate delegate) {
116116

117117
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
118-
evaluationContextProvider);
118+
delegate);
119119
}
120120

121121
@Override
@@ -143,9 +143,9 @@ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
143143
DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
144144
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
145145
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
146-
@Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
146+
@Nullable BeanFactory beanfactory, ValueExpressionDelegate delegate) {
147147
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
148-
evaluationContextProvider);
148+
delegate);
149149

150150
this.rowMapperFactory = new BeanFactoryRowMapperFactory(beanfactory);
151151
}
@@ -166,7 +166,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
166166
String queryString = evaluateTableExpressions(repositoryMetadata, queryMethod.getRequiredQuery());
167167

168168
return new StringBasedJdbcQuery(queryString, queryMethod, getOperations(), rowMapperFactory, getConverter(),
169-
evaluationContextProvider);
169+
delegate);
170170
}
171171

172172
throw new IllegalStateException(
@@ -235,10 +235,10 @@ static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy
235235
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
236236
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
237237
CreateQueryLookupStrategy createStrategy,
238-
DeclaredQueryLookupStrategy lookupStrategy, QueryMethodEvaluationContextProvider evaluationContextProvider) {
238+
DeclaredQueryLookupStrategy lookupStrategy, ValueExpressionDelegate delegate) {
239239

240240
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
241-
evaluationContextProvider);
241+
delegate);
242242

243243
Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null");
244244
Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null");
@@ -284,20 +284,20 @@ JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryM
284284
public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPublisher publisher,
285285
@Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
286286
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
287-
@Nullable BeanFactory beanFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
288-
287+
@Nullable BeanFactory beanFactory, ValueExpressionDelegate delegate) {
289288
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
290289
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
291290
Assert.notNull(converter, "JdbcConverter must not be null");
292291
Assert.notNull(dialect, "Dialect must not be null");
293292
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
294293
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
294+
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
295295

296296
CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context,
297-
converter, dialect, queryMappingConfiguration, operations, evaluationContextProvider);
297+
converter, dialect, queryMappingConfiguration, operations, delegate);
298298

299299
DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks,
300-
context, converter, dialect, queryMappingConfiguration, operations, beanFactory, evaluationContextProvider);
300+
context, converter, dialect, queryMappingConfiguration, operations, beanFactory, delegate);
301301

302302
Key keyToUse = key != null ? key : Key.CREATE_IF_NOT_FOUND;
303303

@@ -311,7 +311,7 @@ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPubl
311311
case CREATE_IF_NOT_FOUND:
312312
return new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, context, converter, dialect,
313313
queryMappingConfiguration, operations, createQueryLookupStrategy, declaredQueryLookupStrategy,
314-
evaluationContextProvider);
314+
delegate);
315315
default:
316316
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
317317
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import org.springframework.data.repository.core.support.PersistentEntityInformation;
3434
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
3535
import org.springframework.data.repository.query.QueryLookupStrategy;
36-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
36+
import org.springframework.data.repository.query.ValueExpressionDelegate;
3737
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
3838
import org.springframework.lang.Nullable;
3939
import org.springframework.util.Assert;
@@ -132,12 +132,10 @@ protected Class<?> getRepositoryBaseClass(RepositoryMetadata repositoryMetadata)
132132
return SimpleJdbcRepository.class;
133133
}
134134

135-
@Override
136-
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
137-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
138-
135+
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
136+
ValueExpressionDelegate valueExpressionDelegate) {
139137
return Optional.of(JdbcQueryLookupStrategy.create(key, publisher, entityCallbacks, context, converter, dialect,
140-
queryMappingConfiguration, operations, beanFactory, evaluationContextProvider));
138+
queryMappingConfiguration, operations, beanFactory, valueExpressionDelegate));
141139
}
142140

143141
/**

0 commit comments

Comments
 (0)