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 ) {
@@ -115,6 +126,23 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
115126 evaluationContextProvider );
116127 }
117128
129+ /**
130+ * Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
131+ * and {@link RowMapperFactory}.
132+ *
133+ * @param queryMethod must not be {@literal null}.
134+ * @param operations must not be {@literal null}.
135+ * @param rowMapperFactory must not be {@literal null}.
136+ * @param converter must not be {@literal null}.
137+ * @param delegate must not be {@literal null}.
138+ * @since 3.4
139+ */
140+ public StringBasedJdbcQuery (JdbcQueryMethod queryMethod , NamedParameterJdbcOperations operations ,
141+ RowMapperFactory rowMapperFactory , JdbcConverter converter ,
142+ ValueExpressionDelegate delegate ) {
143+ this (queryMethod .getRequiredQuery (), queryMethod , operations , rowMapperFactory , converter , delegate );
144+ }
145+
118146 /**
119147 * Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
120148 * and {@link RowMapperFactory}.
@@ -124,15 +152,13 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
124152 * @param operations must not be {@literal null}.
125153 * @param rowMapperFactory must not be {@literal null}.
126154 * @param converter must not be {@literal null}.
127- * @param evaluationContextProvider must not be {@literal null}.
155+ * @param delegate must not be {@literal null}.
128156 * @since 3.4
129157 */
130158 public StringBasedJdbcQuery (String query , JdbcQueryMethod queryMethod , NamedParameterJdbcOperations operations ,
131159 RowMapperFactory rowMapperFactory , JdbcConverter converter ,
132- QueryMethodEvaluationContextProvider evaluationContextProvider ) {
133-
160+ ValueExpressionDelegate delegate ) {
134161 super (queryMethod , operations );
135-
136162 Assert .hasText (query , "Query must not be null or empty" );
137163 Assert .notNull (rowMapperFactory , "RowMapperFactory must not be null" );
138164
@@ -159,13 +185,40 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
159185 this .cachedResultSetExtractorFactory = new CachedResultSetExtractorFactory (
160186 this .cachedRowMapperFactory ::getRowMapper );
161187
162- SpelQueryContext .EvaluatingSpelQueryContext queryContext = SpelQueryContext
163- .of ((counter , expression ) -> String .format ("__$synthetic$__%d" , counter + 1 ), String ::concat )
164- .withEvaluationContextProvider (evaluationContextProvider );
188+ this .parameterBindings = new ArrayList <>();
189+
190+ ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter .of (delegate , (counter , expression ) -> {
191+ String newName = String .format ("__$synthetic$__%d" , counter + 1 );
192+ parameterBindings .add (new AbstractMap .SimpleEntry <>(newName , expression ));
193+ return newName ;
194+ }, String ::concat );
165195
166196 this .query = query ;
167- this .spelEvaluator = queryContext .parse (this .query , getQueryMethod ().getParameters ());
168- this .containsSpelExpressions = !this .spelEvaluator .getQueryString ().equals (this .query );
197+ this .parsedQuery = rewriter .parse (this .query );
198+ this .containsSpelExpressions = !this .parsedQuery .getQueryString ().equals (this .query );
199+ this .delegate = delegate ;
200+ }
201+
202+ /**
203+ * Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
204+ * and {@link RowMapperFactory}.
205+ *
206+ * @param query must not be {@literal null} or empty.
207+ * @param queryMethod must not be {@literal null}.
208+ * @param operations must not be {@literal null}.
209+ * @param rowMapperFactory must not be {@literal null}.
210+ * @param converter must not be {@literal null}.
211+ * @param evaluationContextProvider must not be {@literal null}.
212+ * @since 3.4
213+ * @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
214+ */
215+ @ Deprecated (since = "3.4" )
216+ public StringBasedJdbcQuery (String query , JdbcQueryMethod queryMethod , NamedParameterJdbcOperations operations ,
217+ RowMapperFactory rowMapperFactory , JdbcConverter converter ,
218+ QueryMethodEvaluationContextProvider evaluationContextProvider ) {
219+ this (query , queryMethod , operations , rowMapperFactory , converter , new CachingValueExpressionDelegate (new QueryMethodValueEvaluationContextAccessor (null ,
220+ rootObject -> evaluationContextProvider .getEvaluationContext (queryMethod .getParameters (), new Object [] { rootObject })), ValueExpressionParser .create (
221+ SpelExpressionParser ::new )));
169222 }
170223
171224 @ Override
@@ -177,15 +230,19 @@ public Object execute(Object[] objects) {
177230 JdbcQueryExecution <?> queryExecution = createJdbcQueryExecution (accessor , processor );
178231 MapSqlParameterSource parameterMap = this .bindParameters (accessor );
179232
180- return queryExecution .execute (processSpelExpressions (objects , parameterMap ), parameterMap );
233+ return queryExecution .execute (processSpelExpressions (objects , accessor . getBindableParameters (), parameterMap ), parameterMap );
181234 }
182235
183- private String processSpelExpressions (Object [] objects , MapSqlParameterSource parameterMap ) {
236+ private String processSpelExpressions (Object [] objects , Parameters <?, ?> bindableParameters , MapSqlParameterSource parameterMap ) {
184237
185238 if (containsSpelExpressions ) {
186-
187- spelEvaluator .evaluate (objects ).forEach (parameterMap ::addValue );
188- return spelEvaluator .getQueryString ();
239+ ValueEvaluationContext evaluationContext = delegate .createValueContextProvider (bindableParameters )
240+ .getEvaluationContext (objects );
241+ for (Map .Entry <String , String > entry : parameterBindings ) {
242+ parameterMap .addValue (
243+ entry .getKey (), delegate .parse (entry .getValue ()).evaluate (evaluationContext ));
244+ }
245+ return parsedQuery .getQueryString ();
189246 }
190247
191248 return this .query ;
0 commit comments