Skip to content

Commit 9abaa5a

Browse files
Christopher Kleinschauder
authored andcommitted
Support for SpEL inside @query annotations.
Constructs like the following work now. ``` @query("select u from User u where u.firstname = :#{#customer.firstname}") List<User> findUsersByCustomersFirstname(@param("customer") Customer customer); ``` Closes #619 Original pull request #229 See https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions
1 parent f326897 commit 9abaa5a

File tree

11 files changed

+1002
-43
lines changed

11 files changed

+1002
-43
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* @author Milan Milanov
6060
* @author Myeonghyeon Lee
6161
* @author Chirag Tailor
62+
* @author Christopher Klein
6263
*/
6364
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
6465

@@ -286,7 +287,6 @@ public Iterable<Object> findAllByPath(Identifier identifier,
286287

287288
return sqlSession().selectList(statementName,
288289
new MyBatisContext(identifier, null, path.getRequiredLeafProperty().getType()));
289-
290290
}
291291

292292
@Override

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

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,28 @@
2929
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
3030
import org.springframework.data.jdbc.core.convert.JdbcConverter;
3131
import org.springframework.data.jdbc.core.mapping.JdbcValue;
32+
import org.springframework.data.jdbc.repository.query.parameter.ParameterBindingParser;
33+
import org.springframework.data.jdbc.repository.query.parameter.ParameterBindings.Metadata;
34+
import org.springframework.data.jdbc.repository.query.parameter.ParameterBindings.ParameterBinding;
3235
import org.springframework.data.jdbc.support.JdbcUtil;
3336
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3437
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
3538
import org.springframework.data.relational.repository.query.RelationalParameters;
3639
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
3740
import org.springframework.data.repository.query.Parameter;
3841
import org.springframework.data.repository.query.Parameters;
42+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3943
import org.springframework.data.repository.query.ResultProcessor;
44+
import org.springframework.data.repository.query.SpelQueryContext;
45+
import org.springframework.expression.EvaluationContext;
46+
import org.springframework.expression.Expression;
47+
import org.springframework.expression.ExpressionParser;
48+
import org.springframework.expression.spel.standard.SpelExpressionParser;
4049
import org.springframework.jdbc.core.ResultSetExtractor;
4150
import org.springframework.jdbc.core.RowMapper;
4251
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
4352
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
53+
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
4454
import org.springframework.lang.Nullable;
4555
import org.springframework.util.Assert;
4656
import org.springframework.util.ClassUtils;
@@ -57,6 +67,7 @@
5767
* @author Mark Paluch
5868
* @author Hebert Coelho
5969
* @author Chirag Tailor
70+
* @author Christopher Klein
6071
* @since 2.0
6172
*/
6273
public class StringBasedJdbcQuery extends AbstractJdbcQuery {
@@ -67,6 +78,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
6778
private final JdbcConverter converter;
6879
private final RowMapperFactory rowMapperFactory;
6980
private BeanFactory beanFactory;
81+
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
7082

7183
/**
7284
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
@@ -77,8 +89,9 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
7789
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
7890
*/
7991
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
80-
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter) {
81-
this(queryMethod, operations, result -> (RowMapper<Object>) defaultRowMapper, converter);
92+
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter,
93+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
94+
this(queryMethod, operations, result -> (RowMapper<Object>) defaultRowMapper, converter, evaluationContextProvider);
8295
}
8396

8497
/**
@@ -91,7 +104,8 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
91104
* @since 2.3
92105
*/
93106
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
94-
RowMapperFactory rowMapperFactory, JdbcConverter converter) {
107+
RowMapperFactory rowMapperFactory, JdbcConverter converter,
108+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
95109

96110
super(queryMethod, operations);
97111

@@ -100,6 +114,7 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
100114
this.queryMethod = queryMethod;
101115
this.converter = converter;
102116
this.rowMapperFactory = rowMapperFactory;
117+
this.evaluationContextProvider = evaluationContextProvider;
103118

104119
if (queryMethod.isSliceQuery()) {
105120
throw new UnsupportedOperationException(
@@ -115,6 +130,16 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
115130
@Override
116131
public Object execute(Object[] objects) {
117132

133+
// List<ParameterBinding> parameterBindings = new ArrayList<>();
134+
// SpelQueryContext queryContext = SpelQueryContext.of((counter, expression) -> {
135+
//
136+
// String parameterName = String.format("__synthetic_%d__", counter);
137+
// parameterBindings.add(new ParameterBinding(parameterName, expression));
138+
// return parameterName;
139+
// }, String::concat);
140+
//
141+
// SpelQueryContext.SpelExtractor parsed = queryContext.parse(query);
142+
118143
RelationalParameterAccessor accessor = new RelationalParametersParameterAccessor(getQueryMethod(), objects);
119144
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
120145
ResultProcessingConverter converter = new ResultProcessingConverter(processor, this.converter.getMappingContext(),
@@ -128,7 +153,51 @@ public Object execute(Object[] objects) {
128153
determineResultSetExtractor(rowMapper), //
129154
rowMapper);
130155

131-
return queryExecution.execute(determineQuery(), this.bindParameters(accessor));
156+
Metadata queryMeta = new Metadata();
157+
158+
String query = determineQuery();
159+
160+
if (ObjectUtils.isEmpty(query)) {
161+
throw new IllegalStateException(String.format("No query specified on %s", queryMethod.getName()));
162+
}
163+
List<ParameterBinding> bindings = new ArrayList<>();
164+
165+
query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
166+
bindings, queryMeta);
167+
168+
SqlParameterSource parameterMap = this.bindParameters(accessor);
169+
extendParametersFromSpELEvaluation((MapSqlParameterSource) parameterMap, bindings, objects);
170+
return queryExecution.execute(query, parameterMap);
171+
}
172+
173+
/**
174+
* Extend the {@link MapSqlParameterSource} by evaluating each detected SpEL parameter in the original query. This is
175+
* basically a simple variant of Spring Data JPA's SPeL implementation.
176+
*
177+
* @param parameterMap
178+
* @param bindings
179+
* @param values
180+
*/
181+
void extendParametersFromSpELEvaluation(MapSqlParameterSource parameterMap, List<ParameterBinding> bindings,
182+
Object[] values) {
183+
184+
if (bindings.size() == 0) {
185+
return;
186+
}
187+
188+
ExpressionParser parser = new SpelExpressionParser();
189+
190+
bindings.forEach(binding -> {
191+
if (!binding.isExpression()) {
192+
return;
193+
}
194+
195+
Expression expression = parser.parseExpression(binding.getExpression());
196+
EvaluationContext context = evaluationContextProvider.getEvaluationContext(this.queryMethod.getParameters(),
197+
values);
198+
199+
parameterMap.addValue(binding.getName(), expression.getValue(context, Object.class));
200+
});
132201
}
133202

134203
@Override

0 commit comments

Comments
 (0)