Skip to content

Commit c0a6ee7

Browse files
mp911degregturn
authored andcommitted
Use unique named parameter bindings for like parameters.
We now replace LIKE expressions according to their type with individual bindings if an existing binding cannot be used because of how the bound value is being transformed. WHERE foo like %:name or bar like :name becomes WHERE foo like :name (i.e. '%' + :name) or bar like :name_1 (i.e. :name) See #3041
1 parent 454ce52 commit c0a6ee7

File tree

8 files changed

+284
-92
lines changed

8 files changed

+284
-92
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -327,14 +327,15 @@ public boolean canExtractQuery() {
327327
}
328328

329329
/**
330-
* Because Hibernate's {@literal TypedParameterValue} is only used to wrap a {@literal null}, swap it out with an
331-
* empty string for query creation.
330+
* Because Hibernate's {@literal TypedParameterValue} is only used to wrap a {@literal null}, swap it out with
331+
* {@code null} for query creation.
332332
*
333333
* @param value
334334
* @return the original value or null.
335335
* @since 3.0
336336
*/
337-
public static Object unwrapTypedParameterValue(Object value) {
337+
@Nullable
338+
public static Object unwrapTypedParameterValue(@Nullable Object value) {
338339

339340
return typedParameterValueClass != null && typedParameterValueClass.isInstance(value) //
340341
? null //

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ static ParameterBinder createBinder(JpaParameters parameters) {
4646

4747
Assert.notNull(parameters, "JpaParameters must not be null");
4848

49+
QueryParameterSetterFactory likeFactory = QueryParameterSetterFactory.forLikeRewrite(parameters);
4950
QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.basic(parameters);
5051
List<ParameterBinding> bindings = getBindings(parameters);
5152

52-
return new ParameterBinder(parameters, createSetters(bindings, setterFactory));
53+
return new ParameterBinder(parameters, createSetters(bindings, likeFactory, setterFactory));
5354
}
5455

5556
/**
@@ -95,9 +96,12 @@ static ParameterBinder createQueryAwareBinder(JpaParameters parameters, Declared
9596
List<ParameterBinding> bindings = query.getParameterBindings();
9697
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
9798
evaluationContextProvider, parameters);
99+
100+
QueryParameterSetterFactory like = QueryParameterSetterFactory.forLikeRewrite(parameters);
98101
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
99102

100-
return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory),
103+
return new ParameterBinder(parameters,
104+
createSetters(bindings, query, expressionSetterFactory, like, basicSetterFactory),
101105
!query.usesPaging());
102106
}
103107

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java

+80-24
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
2525
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
2626
import org.springframework.data.jpa.repository.query.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
27+
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
2728
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
2829
import org.springframework.data.repository.query.Parameter;
2930
import 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

Comments
 (0)