2424import org .springframework .data .jpa .repository .query .JpaParameters .JpaParameter ;
2525import org .springframework .data .jpa .repository .query .ParameterMetadataProvider .ParameterMetadata ;
2626import org .springframework .data .jpa .repository .query .QueryParameterSetter .NamedOrIndexedQueryParameterSetter ;
27+ import org .springframework .data .jpa .repository .query .StringQuery .LikeParameterBinding ;
2728import org .springframework .data .jpa .repository .query .StringQuery .ParameterBinding ;
2829import org .springframework .data .repository .query .Parameter ;
2930import org .springframework .data .repository .query .Parameters ;
@@ -62,6 +63,20 @@ static QueryParameterSetterFactory basic(JpaParameters parameters) {
6263 return new BasicQueryParameterSetterFactory (parameters );
6364 }
6465
66+ /**
67+ * Creates a new {@link QueryParameterSetterFactory} for the given {@link JpaParameters} applying LIKE rewrite for
68+ * renamed {@code :foo%} or {@code %:bar} bindings.
69+ *
70+ * @param parameters must not be {@literal null}.
71+ * @return a basic {@link QueryParameterSetterFactory} that can handle named parameters.
72+ */
73+ static QueryParameterSetterFactory forLikeRewrite (JpaParameters parameters ) {
74+
75+ Assert .notNull (parameters , "JpaParameters must not be null" );
76+
77+ return new LikeRewritingQueryParameterSetterFactory (parameters );
78+ }
79+
6580 /**
6681 * Creates a new {@link QueryParameterSetterFactory} using the given {@link JpaParameters} and
6782 * {@link ParameterMetadata}.
@@ -117,6 +132,29 @@ private static QueryParameterSetter createSetter(Function<JpaParametersParameter
117132 ParameterImpl .of (parameter , binding ), temporalType );
118133 }
119134
135+ @ Nullable
136+ private static JpaParameter findParameterForBinding (Parameters <JpaParameters , JpaParameter > parameters , String name ) {
137+
138+ JpaParameters bindableParameters = parameters .getBindableParameters ();
139+
140+ for (JpaParameter bindableParameter : bindableParameters ) {
141+ if (name .equals (getRequiredName (bindableParameter ))) {
142+ return bindableParameter ;
143+ }
144+ }
145+
146+ return null ;
147+ }
148+
149+ private static String getRequiredName (JpaParameter p ) {
150+ return p .getName ().orElseThrow (() -> new IllegalStateException (ParameterBinder .PARAMETER_NEEDS_TO_BE_NAMED ));
151+ }
152+
153+ @ Nullable
154+ static Object getValue (JpaParametersParameterAccessor accessor , Parameter parameter ) {
155+ return accessor .getValue (parameter );
156+ }
157+
120158 /**
121159 * Handles bindings that are SpEL expressions by evaluating the expression to obtain a value.
122160 *
@@ -176,6 +214,46 @@ private Object evaluateExpression(Expression expression, JpaParametersParameterA
176214 }
177215 }
178216
217+ /**
218+ * Handles bindings that use Like-rewriting.
219+ *
220+ * @author Mark Paluch
221+ * @since 3.1.2
222+ */
223+ private static class LikeRewritingQueryParameterSetterFactory extends QueryParameterSetterFactory {
224+
225+ private final Parameters <?, ?> parameters ;
226+
227+ /**
228+ * @param parameters must not be {@literal null}.
229+ */
230+ LikeRewritingQueryParameterSetterFactory (Parameters <?, ?> parameters ) {
231+
232+ Assert .notNull (parameters , "Parameters must not be null" );
233+
234+ this .parameters = parameters ;
235+ }
236+
237+ @ Nullable
238+ @ Override
239+ public QueryParameterSetter create (ParameterBinding binding , DeclaredQuery declaredQuery ) {
240+
241+ if (binding .isExpression () || !(binding instanceof LikeParameterBinding likeBinding )
242+ || !declaredQuery .hasNamedParameter ()) {
243+ return null ;
244+ }
245+ JpaParameter parameter = QueryParameterSetterFactory .findParameterForBinding ((JpaParameters ) parameters ,
246+ likeBinding .getDeclaredName ());
247+
248+ if (parameter == null ) {
249+ return null ;
250+ }
251+
252+ return createSetter (values -> values .getValue (parameter ), binding , parameter );
253+ }
254+
255+ }
256+
179257 /**
180258 * Extracts values for parameter bindings from method parameters. It handles named as well as indexed parameters.
181259 *
@@ -205,7 +283,7 @@ public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery decla
205283 JpaParameter parameter ;
206284
207285 if (declaredQuery .hasNamedParameter ()) {
208- parameter = findParameterForBinding (binding );
286+ parameter = findParameterForBinding (parameters , binding . getRequiredName () );
209287 } else {
210288
211289 int parameterIndex = binding .getRequiredPosition () - 1 ;
@@ -228,28 +306,6 @@ public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery decla
228306 : createSetter (values -> getValue (values , parameter ), binding , parameter );
229307 }
230308
231- @ Nullable
232- private JpaParameter findParameterForBinding (ParameterBinding binding ) {
233-
234- JpaParameters bindableParameters = parameters .getBindableParameters ();
235-
236- for (JpaParameter bindableParameter : bindableParameters ) {
237- if (binding .getRequiredName ().equals (getName (bindableParameter ))) {
238- return bindableParameter ;
239- }
240- }
241-
242- return null ;
243- }
244-
245- @ Nullable
246- private Object getValue (JpaParametersParameterAccessor accessor , Parameter parameter ) {
247- return accessor .getValue (parameter );
248- }
249-
250- private static String getName (JpaParameter p ) {
251- return p .getName ().orElseThrow (() -> new IllegalStateException (ParameterBinder .PARAMETER_NEEDS_TO_BE_NAMED ));
252- }
253309 }
254310
255311 /**
@@ -366,7 +422,7 @@ public Class<T> getParameterType() {
366422 @ Nullable
367423 private static String getName (@ Nullable JpaParameter parameter , ParameterBinding binding ) {
368424
369- if (parameter == null ) {
425+ if (binding . hasName () || parameter == null ) {
370426 return binding .getName ();
371427 }
372428
0 commit comments