Skip to content

Commit 9ebfe27

Browse files
committed
Reinstate TupleQueries for PartTree queries.
1 parent d6579c7 commit 9ebfe27

12 files changed

+82
-66
lines changed

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.LinkedHashSet;
2323
import java.util.List;
2424
import java.util.Set;
25+
import java.util.concurrent.atomic.AtomicInteger;
2526

2627
import org.springframework.data.domain.KeysetScrollPosition;
2728
import org.springframework.data.domain.Sort;
@@ -42,7 +43,7 @@ class JpaKeysetScrollQueryCreator extends JpaQueryCreator {
4243
private final JpaEntityInformation<?, ?> entityInformation;
4344
private final KeysetScrollPosition scrollPosition;
4445
private final ParameterMetadataProvider provider;
45-
private final List<ParameterBinding> bindings = new ArrayList<>();
46+
private final List<ParameterBinding.Synthetic> bindings = new ArrayList<>();
4647

4748
public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvider provider,
4849
JpqlQueryTemplates templates, JpaEntityInformation<?, ?> entityInformation, KeysetScrollPosition scrollPosition,
@@ -56,7 +57,7 @@ public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, ParameterMe
5657
}
5758

5859
@Override
59-
List<ParameterBinding> getParameterBindings() {
60+
List<ParameterBinding.Synthetic> getSyntheticParameterBindings() {
6061
return bindings;
6162
}
6263

@@ -65,17 +66,15 @@ protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuil
6566

6667
KeysetScrollSpecification<Object> keysetSpec = new KeysetScrollSpecification<>(scrollPosition, sort,
6768
entityInformation);
68-
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(getFrom(), getEntity(), value -> {
69-
70-
int position = provider.nextPosition();
71-
bindings.add(new ParameterBinding(ParameterBinding.BindingIdentifier.of(position + 1),
72-
ParameterBinding.ParameterOrigin.synthetic(value)));
73-
74-
return JpqlQueryBuilder.expression(render(position));
75-
});
7669

7770
JpqlQueryBuilder.Select query = buildQuery(keysetSpec.sort());
7871

72+
AtomicInteger counter = new AtomicInteger(provider.getExpressions().size());
73+
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(getFrom(), getEntity(), value -> {
74+
75+
bindings.add(ParameterBinding.ParameterOrigin.synthetic(value));
76+
return JpqlQueryBuilder.expression(render(counter.getAndIncrement()));
77+
});
7978
JpqlQueryBuilder.Predicate predicateToUse = getPredicate(predicate, keysetPredicate);
8079

8180
if (predicateToUse != null) {

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,20 @@ JpqlQueryBuilder.Entity getEntity() {
100100
return entity;
101101
}
102102

103+
public boolean useTupleQuery() {
104+
return returnedType.needsCustomConstruction() && returnedType.getReturnedType().isInterface();
105+
}
106+
103107
/**
104108
* Returns all {@link jakarta.persistence.criteria.ParameterExpression} created when creating the query.
105109
*
106110
* @return the parameterExpressions
107111
*/
108-
public List<ParameterMetadata<?>> getParameterExpressions() {
112+
public List<ParameterMetadata> getParameterExpressions() {
109113
return provider.getExpressions();
110114
}
111115

112-
List<ParameterBinding> getParameterBindings() {
116+
List<ParameterBinding.Synthetic> getSyntheticParameterBindings() {
113117
return Collections.emptyList();
114118
}
115119

@@ -206,7 +210,7 @@ private JpqlQueryBuilder.Select doSelect(Sort sort) {
206210
if (returnedType.needsCustomConstruction()) {
207211

208212
Collection<String> requiredSelection = getRequiredSelection(sort, returnedType);
209-
if (returnedType.getReturnedType().isInterface()) {
213+
if (useTupleQuery()) {
210214
return selectStep.select(requiredSelection);
211215
} else {
212216
return selectStep.instantiate(returnedType.getReturnedType(), requiredSelection);
@@ -242,7 +246,7 @@ String render(int position) {
242246
return "?" + (position + 1);
243247
}
244248

245-
private String render(ParameterMetadata<?> metadata) {
249+
private String render(ParameterMetadata metadata) {
246250
return render(metadata.getPosition());
247251
}
248252

@@ -295,8 +299,8 @@ public JpqlQueryBuilder.Predicate build() {
295299

296300
switch (type) {
297301
case BETWEEN:
298-
ParameterMetadata<Comparable> first = provider.next(part);
299-
ParameterMetadata<Comparable> second = provider.next(part);
302+
ParameterMetadata first = provider.next(part);
303+
ParameterMetadata second = provider.next(part);
300304
return where.between(render(first), render(second));
301305
case AFTER:
302306
case GREATER_THAN:
@@ -331,7 +335,7 @@ public JpqlQueryBuilder.Predicate build() {
331335
case LIKE:
332336
case NOT_LIKE:
333337

334-
ParameterMetadata<? extends String> parameter = provider.next(part, String.class);
338+
ParameterMetadata parameter = provider.next(part, String.class);
335339
JpqlQueryBuilder.Expression parameterExpression = potentiallyIgnoreCase(part.getProperty(),
336340
JpqlQueryBuilder.parameter(render(parameter)));
337341
// Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
@@ -346,7 +350,7 @@ public JpqlQueryBuilder.Predicate build() {
346350
case FALSE:
347351
return where.isFalse();
348352
case SIMPLE_PROPERTY:
349-
ParameterMetadata<Object> metadata = provider.next(part);
353+
ParameterMetadata metadata = provider.next(part);
350354

351355
if (metadata.isIsNullParameter()) {
352356
return where.isNull();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,10 @@ public String render(RenderContext context) {
543543
}
544544

545545
builder.append(context.prefixWithAlias(source, path));
546+
547+
if (!path.contains(".")) {
548+
builder.append(" ").append(path);
549+
}
546550
}
547551

548552
return builder.toString();

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,21 @@ static ParameterBinder createBinder(JpaParameters parameters) {
5959
*
6060
* @param parameters method parameters that are available for binding, must not be {@literal null}.
6161
* @param metadata parameter metadata for method argument parameters, must not be {@literal null}.
62-
* @param bindings additional (e.g. synthetic) bindings, must not be {@literal null}.
62+
* @param syntheticBindings additional (e.g. synthetic) syntheticBindings, must not be {@literal null}.
6363
* @return a {@link ParameterBinder} that can assign values for the method parameters to query parameters of a
6464
* {@link jakarta.persistence.criteria.CriteriaQuery}
6565
*/
66-
static ParameterBinder createCriteriaBinder(JpaParameters parameters, List<ParameterMetadata<?>> metadata,
67-
List<ParameterBinding> bindings) {
66+
static ParameterBinder createCriteriaBinder(JpaParameters parameters, List<ParameterMetadata> metadata,
67+
List<ParameterBinding.Synthetic> syntheticBindings) {
6868

6969
Assert.notNull(parameters, "JpaParameters must not be null");
7070
Assert.notNull(metadata, "Parameter metadata must not be null");
7171

7272
List<ParameterBinding> bindingsToUse = getBindings(parameters);
73-
bindingsToUse.addAll(bindings);
73+
int offset = bindingsToUse.size();
74+
for (ParameterBinding.Synthetic binding : syntheticBindings) {
75+
bindingsToUse.add(new ParameterBinding(BindingIdentifier.of(++offset), binding));
76+
}
7477

7578
return new ParameterBinder(parameters,
7679
createSetters(bindingsToUse, QueryParameterSetterFactory.forPartTreeQuery(parameters, metadata),

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,11 @@ static MethodInvocationArgument ofParameter(BindingIdentifier identifier) {
563563
* @return {@code true} if the origin is an expression.
564564
*/
565565
boolean isExpression();
566+
567+
/**
568+
* @return {@code true} if the origin is an expression.
569+
*/
570+
boolean isSynthetic();
566571
}
567572

568573
/**
@@ -583,14 +588,18 @@ public boolean isMethodArgument() {
583588
public boolean isExpression() {
584589
return true;
585590
}
591+
592+
@Override
593+
public boolean isSynthetic() {
594+
return true;
595+
}
586596
}
587597

588598
/**
589599
* Value object capturing the expression of which a binding parameter originates.
590600
*
591-
* @param expression
601+
* @param value
592602
* @author Mark Paluch
593-
* @since 3.1.2
594603
*/
595604
public record Synthetic(@Nullable Object value) implements ParameterOrigin {
596605

@@ -601,6 +610,11 @@ public boolean isMethodArgument() {
601610

602611
@Override
603612
public boolean isExpression() {
613+
return false;
614+
}
615+
616+
@Override
617+
public boolean isSynthetic() {
604618
return true;
605619
}
606620
}
@@ -623,5 +637,10 @@ public boolean isMethodArgument() {
623637
public boolean isExpression() {
624638
return false;
625639
}
640+
641+
@Override
642+
public boolean isSynthetic() {
643+
return false;
644+
}
626645
}
627646
}

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

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
public class ParameterMetadataProvider {
5757

5858
private final Iterator<? extends Parameter> parameters;
59-
private final List<ParameterMetadata<?>> expressions;
59+
private final List<ParameterMetadata> expressions;
6060
private final @Nullable Iterator<Object> bindableParameterValues;
6161
private final EscapeCharacter escape;
6262
private final JpqlQueryTemplates templates;
@@ -120,20 +120,20 @@ private ParameterMetadataProvider(CriteriaBuilder builder, @Nullable Iterator<Ob
120120
*
121121
* @return the expressions
122122
*/
123-
public List<ParameterMetadata<?>> getExpressions() {
123+
public List<ParameterMetadata> getExpressions() {
124124
return expressions;
125125
}
126126

127127
/**
128128
* Builds a new {@link ParameterMetadata} for given {@link Part} and the next {@link Parameter}.
129129
*/
130130
@SuppressWarnings("unchecked")
131-
public <T> ParameterMetadata<T> next(Part part) {
131+
public <T> ParameterMetadata next(Part part) {
132132

133133
Assert.isTrue(parameters.hasNext(), () -> String.format("No parameter available for part %s", part));
134134

135135
Parameter parameter = parameters.next();
136-
return (ParameterMetadata<T>) next(part, parameter.getType(), parameter);
136+
return next(part, parameter.getType(), parameter);
137137
}
138138

139139
/**
@@ -145,11 +145,11 @@ public <T> ParameterMetadata<T> next(Part part) {
145145
* @return ParameterMetadata for the next parameter.
146146
*/
147147
@SuppressWarnings("unchecked")
148-
public <T> ParameterMetadata<? extends T> next(Part part, Class<T> type) {
148+
public <T> ParameterMetadata next(Part part, Class<T> type) {
149149

150150
Parameter parameter = parameters.next();
151151
Class<?> typeToUse = ClassUtils.isAssignable(type, parameter.getType()) ? parameter.getType() : type;
152-
return (ParameterMetadata<? extends T>) next(part, typeToUse, parameter);
152+
return next(part, typeToUse, parameter);
153153
}
154154

155155
/**
@@ -161,7 +161,7 @@ public <T> ParameterMetadata<? extends T> next(Part part, Class<T> type) {
161161
* @param parameter providing the name for the returned {@link ParameterMetadata}.
162162
* @return a new {@link ParameterMetadata} for the given type and name.
163163
*/
164-
private <T> ParameterMetadata<T> next(Part part, Class<T> type, Parameter parameter) {
164+
private <T> ParameterMetadata next(Part part, Class<T> type, Parameter parameter) {
165165

166166
Assert.notNull(type, "Type must not be null");
167167

@@ -174,8 +174,7 @@ private <T> ParameterMetadata<T> next(Part part, Class<T> type, Parameter parame
174174
Object value = bindableParameterValues == null ? ParameterMetadata.PLACEHOLDER : bindableParameterValues.next();
175175

176176
int currentPosition = position++;
177-
ParameterMetadata<T> metadata = new ParameterMetadata<>(reifiedType, part, value, escape, currentPosition,
178-
templates);
177+
ParameterMetadata metadata = new ParameterMetadata(reifiedType, part, value, escape, currentPosition, templates);
179178
expressions.add(metadata);
180179

181180
return metadata;
@@ -193,9 +192,8 @@ EscapeCharacter getEscape() {
193192
* @author Oliver Gierke
194193
* @author Thomas Darimont
195194
* @author Andrey Kovalev
196-
* @param <T>
197195
*/
198-
public static class ParameterMetadata<T> {
196+
public static class ParameterMetadata {
199197

200198
static final Object PLACEHOLDER = new Object();
201199

@@ -206,15 +204,13 @@ public static class ParameterMetadata<T> {
206204
private final EscapeCharacter escape;
207205
private final boolean ignoreCase;
208206
private final boolean noWildcards;
209-
private final @Nullable Object value;
210207

211208
/**
212209
* Creates a new {@link ParameterMetadata}.
213210
*/
214211
public ParameterMetadata(Class<?> parameterType, Part part, @Nullable Object value, EscapeCharacter escape,
215212
int position, JpqlQueryTemplates templates) {
216213

217-
this.value = null;
218214
this.parameterType = parameterType;
219215
this.position = position;
220216
this.templates = templates;
@@ -224,18 +220,6 @@ public ParameterMetadata(Class<?> parameterType, Part part, @Nullable Object val
224220
this.escape = escape;
225221
}
226222

227-
public ParameterMetadata(Object value, Type type, int position, JpqlQueryTemplates templates,
228-
EscapeCharacter escape, boolean ignoreCase, boolean noWildcards) {
229-
this.value = value;
230-
this.parameterType = value.getClass();
231-
this.type = type;
232-
this.position = position;
233-
this.templates = templates;
234-
this.escape = escape;
235-
this.ignoreCase = ignoreCase;
236-
this.noWildcards = noWildcards;
237-
}
238-
239223
public int getPosition() {
240224
return position;
241225
}
@@ -326,9 +310,5 @@ private Collection<?> potentiallyIgnoreCase(boolean ignoreCase, @Nullable Collec
326310
.collect(Collectors.toList());
327311
}
328312

329-
@Nullable
330-
public Object getValue() {
331-
return this.value;
332-
}
333313
}
334314
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.PersistenceUnitUtil;
2020
import jakarta.persistence.Query;
21+
import jakarta.persistence.Tuple;
2122
import jakarta.persistence.TypedQuery;
2223
import jakarta.persistence.criteria.CriteriaBuilder;
2324
import jakarta.persistence.criteria.CriteriaQuery;
@@ -226,14 +227,14 @@ public Query createQuery(JpaParametersParameterAccessor accessor) {
226227
Query query;
227228

228229
try {
229-
query = em.createQuery(jpql);
230+
query = creator.useTupleQuery() ? em.createQuery(jpql, Tuple.class) : em.createQuery(jpql);
230231
} catch (Exception e) {
231232
throw new BadJpqlGrammarException(e.getMessage(), jpql, e);
232233
}
233234

234-
List<ParameterMetadataProvider.ParameterMetadata<?>> expressions = creator.getParameterExpressions();
235+
List<ParameterMetadataProvider.ParameterMetadata> expressions = creator.getParameterExpressions();
235236
ParameterBinder binder = ParameterBinderFactory.createCriteriaBinder(parameters, expressions,
236-
creator.getParameterBindings());
237+
creator.getSyntheticParameterBindings());
237238

238239
ScrollPosition scrollPosition = accessor.getParameters().hasScrollPositionParameter()
239240
? accessor.getScrollPosition()

0 commit comments

Comments
 (0)