2020import java .lang .reflect .Array ;
2121import java .lang .reflect .Constructor ;
2222import java .sql .SQLType ;
23+ import java .util .AbstractMap ;
2324import java .util .ArrayList ;
2425import java .util .Collection ;
2526import java .util .LinkedHashMap ;
2627import java .util .List ;
28+ import java .util .Map ;
2729import java .util .function .Function ;
2830import java .util .function .Supplier ;
2931
3032import org .springframework .beans .BeanInstantiationException ;
3133import org .springframework .beans .BeanUtils ;
3234import org .springframework .beans .factory .BeanFactory ;
35+ import org .springframework .data .expression .ValueEvaluationContext ;
36+ import org .springframework .data .expression .ValueExpressionParser ;
3337import org .springframework .data .jdbc .core .convert .JdbcColumnTypes ;
3438import org .springframework .data .jdbc .core .convert .JdbcConverter ;
3539import org .springframework .data .jdbc .core .mapping .JdbcValue ;
3640import org .springframework .data .jdbc .support .JdbcUtil ;
3741import org .springframework .data .relational .core .mapping .RelationalMappingContext ;
3842import org .springframework .data .relational .repository .query .RelationalParameterAccessor ;
3943import org .springframework .data .relational .repository .query .RelationalParametersParameterAccessor ;
44+ import org .springframework .data .repository .query .CachingValueExpressionDelegate ;
4045import org .springframework .data .repository .query .Parameter ;
4146import org .springframework .data .repository .query .Parameters ;
4247import org .springframework .data .repository .query .QueryMethodEvaluationContextProvider ;
48+ import org .springframework .data .repository .query .QueryMethodValueEvaluationContextAccessor ;
4349import 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 ;
4652import org .springframework .data .util .Lazy ;
4753import org .springframework .data .util .TypeInformation ;
54+ import org .springframework .expression .spel .standard .SpelExpressionParser ;
4855import org .springframework .jdbc .core .ResultSetExtractor ;
4956import org .springframework .jdbc .core .RowMapper ;
5057import 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 ;
0 commit comments