Skip to content

Commit 90289fd

Browse files
christophstroblmp911de
authored andcommitted
Polishing.
Make usage of ParameterExpression more explicit. Add JPQL rendering tests. Favor Metamodel over From for building jpql queries. Align IsNull and IsNotNull handling. Support Derived Delete and Exists, consider null values when caching queries. See #3588 Original pull request: #3653
1 parent 27cae97 commit 90289fd

18 files changed

+2095
-82
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuil
7979
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(getFrom(), getEntity(), value -> {
8080

8181
syntheticBindings.add(provider.nextSynthetic(value, scrollPosition));
82-
return JpqlQueryBuilder.expression(render(counter.incrementAndGet()));
82+
return placeholder(counter.incrementAndGet());
8383
});
8484
JpqlQueryBuilder.Predicate predicateToUse = getPredicate(predicate, keysetPredicate);
8585

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ protected JpaParameters(ParametersSource parametersSource,
6363
super(parametersSource, parameterFactory);
6464
}
6565

66-
private JpaParameters(List<JpaParameter> parameters) {
66+
JpaParameters(List<JpaParameter> parameters) {
6767
super(parameters);
6868
}
6969

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

+51-36
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,30 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.springframework.data.repository.query.parser.Part.Type.*;
18+
import static org.springframework.data.repository.query.parser.Part.Type.IS_NOT_EMPTY;
19+
import static org.springframework.data.repository.query.parser.Part.Type.NOT_CONTAINING;
20+
import static org.springframework.data.repository.query.parser.Part.Type.NOT_LIKE;
21+
import static org.springframework.data.repository.query.parser.Part.Type.SIMPLE_PROPERTY;
1922

2023
import jakarta.persistence.EntityManager;
2124
import jakarta.persistence.criteria.CriteriaQuery;
2225
import jakarta.persistence.criteria.Expression;
23-
import jakarta.persistence.criteria.From;
2426
import jakarta.persistence.criteria.Predicate;
27+
import jakarta.persistence.metamodel.Attribute;
28+
import jakarta.persistence.metamodel.Bindable;
2529
import jakarta.persistence.metamodel.EntityType;
30+
import jakarta.persistence.metamodel.Metamodel;
2631
import jakarta.persistence.metamodel.SingularAttribute;
2732

2833
import java.util.ArrayList;
2934
import java.util.Collection;
3035
import java.util.Iterator;
3136
import java.util.List;
37+
import java.util.stream.Collectors;
3238

3339
import org.springframework.data.domain.Sort;
3440
import org.springframework.data.jpa.domain.JpaSort;
41+
import org.springframework.data.jpa.repository.query.JpqlQueryBuilder.ParameterPlaceholder;
3542
import org.springframework.data.jpa.repository.query.JpqlQueryBuilder.PathAndOrigin;
3643
import org.springframework.data.jpa.repository.query.ParameterBinding.PartTreeParameterBinding;
3744
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
@@ -56,6 +63,7 @@
5663
* @author Moritz Becker
5764
* @author Andrey Kovalev
5865
* @author Greg Turnquist
66+
* @author Christoph Strobl
5967
*/
6068
class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Predicate> implements JpqlQueryCreator {
6169

@@ -65,8 +73,8 @@ class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Pred
6573
private final PartTree tree;
6674
private final EscapeCharacter escape;
6775
private final EntityType<?> entityType;
68-
private final From<?, ?> from;
6976
private final JpqlQueryBuilder.Entity entity;
77+
private final Metamodel metamodel;
7078

7179
/**
7280
* Create a new {@link JpaQueryCreator}.
@@ -87,12 +95,12 @@ public JpaQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvid
8795
this.templates = templates;
8896
this.escape = provider.getEscape();
8997
this.entityType = em.getMetamodel().entity(type.getDomainType());
90-
this.from = em.getCriteriaBuilder().createQuery().from(type.getDomainType());
9198
this.entity = JpqlQueryBuilder.entity(returnedType.getDomainType());
99+
this.metamodel = em.getMetamodel();
92100
}
93101

94-
From<?, ?> getFrom() {
95-
return from;
102+
Bindable<?> getFrom() {
103+
return entityType;
96104
}
97105

98106
JpqlQueryBuilder.Entity getEntity() {
@@ -174,7 +182,7 @@ protected JpqlQueryBuilder.Select buildQuery(Sort sort) {
174182
QueryUtils.checkSortExpression(order);
175183

176184
try {
177-
expression = JpqlQueryBuilder.expression(JpqlUtils.toExpressionRecursively(entity, from,
185+
expression = JpqlQueryBuilder.expression(JpqlUtils.toExpressionRecursively(metamodel, entity, entityType,
178186
PropertyPath.from(order.getProperty(), entityType.getJavaType())));
179187
} catch (PropertyReferenceException e) {
180188

@@ -209,12 +217,19 @@ private JpqlQueryBuilder.Select doSelect(Sort sort) {
209217

210218
if (returnedType.needsCustomConstruction()) {
211219

212-
Collection<String> requiredSelection = getRequiredSelection(sort, returnedType);
220+
Collection<String> requiredSelection = null;
221+
if (returnedType.getReturnedType().getPackageName().startsWith("java.util")
222+
|| returnedType.getReturnedType().getPackageName().startsWith("jakarta.persistence")) {
223+
requiredSelection = metamodel.managedType(returnedType.getDomainType()).getAttributes().stream()
224+
.map(Attribute::getName).collect(Collectors.toList());
225+
} else {
226+
requiredSelection = getRequiredSelection(sort, returnedType);
227+
}
213228

214229
List<PathAndOrigin> paths = new ArrayList<>(requiredSelection.size());
215230
for (String selection : requiredSelection) {
216-
paths.add(
217-
JpqlUtils.toExpressionRecursively(entity, from, PropertyPath.from(selection, from.getJavaType()), true));
231+
paths.add(JpqlUtils.toExpressionRecursively(metamodel, entity, entityType,
232+
PropertyPath.from(selection, returnedType.getDomainType()), true));
218233
}
219234

220235
if (useTupleQuery()) {
@@ -230,14 +245,14 @@ private JpqlQueryBuilder.Select doSelect(Sort sort) {
230245
if (entityType.hasSingleIdAttribute()) {
231246

232247
SingularAttribute<?, ?> id = entityType.getId(entityType.getIdType().getJavaType());
233-
return selectStep.select(
234-
JpqlUtils.toExpressionRecursively(entity, from, PropertyPath.from(id.getName(), from.getJavaType()), true));
248+
return selectStep.select(JpqlUtils.toExpressionRecursively(metamodel, entity, entityType,
249+
PropertyPath.from(id.getName(), returnedType.getDomainType()), true));
235250

236251
} else {
237252

238253
List<PathAndOrigin> paths = entityType.getIdClassAttributes().stream()//
239-
.map(it -> JpqlUtils.toExpressionRecursively(entity, from,
240-
PropertyPath.from(it.getName(), from.getJavaType()), true))
254+
.map(it -> JpqlUtils.toExpressionRecursively(metamodel, entity, entityType,
255+
PropertyPath.from(it.getName(), returnedType.getDomainType()), true))
241256
.toList();
242257
return selectStep.select(paths);
243258
}
@@ -254,12 +269,12 @@ Collection<String> getRequiredSelection(Sort sort, ReturnedType returnedType) {
254269
return returnedType.getInputProperties();
255270
}
256271

257-
String render(ParameterBinding binding) {
258-
return render(binding.getRequiredPosition());
272+
JpqlQueryBuilder.Expression placeholder(ParameterBinding binding) {
273+
return placeholder(binding.getRequiredPosition());
259274
}
260275

261-
String render(int position) {
262-
return "?" + position;
276+
JpqlQueryBuilder.Expression placeholder(int position) {
277+
return JpqlQueryBuilder.parameter(ParameterPlaceholder.indexed(position));
263278
}
264279

265280
/**
@@ -304,33 +319,33 @@ public JpqlQueryBuilder.Predicate build() {
304319
PropertyPath property = part.getProperty();
305320
Type type = part.getType();
306321

307-
PathAndOrigin pas = JpqlUtils.toExpressionRecursively(entity, from, property);
322+
PathAndOrigin pas = JpqlUtils.toExpressionRecursively(metamodel, entity, entityType, property);
308323
JpqlQueryBuilder.WhereStep where = JpqlQueryBuilder.where(pas);
309324
JpqlQueryBuilder.WhereStep whereIgnoreCase = JpqlQueryBuilder.where(potentiallyIgnoreCase(pas));
310325

311326
switch (type) {
312327
case BETWEEN:
313328
PartTreeParameterBinding first = provider.next(part);
314329
ParameterBinding second = provider.next(part);
315-
return where.between(render(first), render(second));
330+
return where.between(placeholder(first), placeholder(second));
316331
case AFTER:
317332
case GREATER_THAN:
318-
return where.gt(render(provider.next(part)));
333+
return where.gt(placeholder(provider.next(part)));
319334
case GREATER_THAN_EQUAL:
320-
return where.gte(render(provider.next(part)));
335+
return where.gte(placeholder(provider.next(part)));
321336
case BEFORE:
322337
case LESS_THAN:
323-
return where.lt(render(provider.next(part)));
338+
return where.lt(placeholder(provider.next(part)));
324339
case LESS_THAN_EQUAL:
325-
return where.lte(render(provider.next(part)));
340+
return where.lte(placeholder(provider.next(part)));
326341
case IS_NULL:
327342
return where.isNull();
328343
case IS_NOT_NULL:
329344
return where.isNotNull();
330345
case NOT_IN:
331-
return whereIgnoreCase.notIn(render(provider.next(part, Collection.class)));
346+
return whereIgnoreCase.notIn(placeholder(provider.next(part, Collection.class)));
332347
case IN:
333-
return whereIgnoreCase.in(render(provider.next(part, Collection.class)));
348+
return whereIgnoreCase.in(placeholder(provider.next(part, Collection.class)));
334349
case STARTING_WITH:
335350
case ENDING_WITH:
336351
case CONTAINING:
@@ -339,16 +354,16 @@ public JpqlQueryBuilder.Predicate build() {
339354
if (property.getLeafProperty().isCollection()) {
340355
where = JpqlQueryBuilder.where(entity, property);
341356

342-
return type.equals(NOT_CONTAINING) ? where.notMemberOf(render(provider.next(part)))
343-
: where.memberOf(render(provider.next(part)));
357+
return type.equals(NOT_CONTAINING) ? where.notMemberOf(placeholder(provider.next(part)))
358+
: where.memberOf(placeholder(provider.next(part)));
344359
}
345360

346361
case LIKE:
347362
case NOT_LIKE:
348363

349364
PartTreeParameterBinding parameter = provider.next(part, String.class);
350365
JpqlQueryBuilder.Expression parameterExpression = potentiallyIgnoreCase(part.getProperty(),
351-
JpqlQueryBuilder.parameter(render(parameter)));
366+
placeholder(parameter));
352367
// Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
353368
String escapeChar = Character.toString(escape.getEscapeCharacter());
354369
return
@@ -361,16 +376,16 @@ public JpqlQueryBuilder.Predicate build() {
361376
case FALSE:
362377
return where.isFalse();
363378
case SIMPLE_PROPERTY:
379+
case NEGATING_SIMPLE_PROPERTY:
380+
364381
PartTreeParameterBinding metadata = provider.next(part);
365382

366383
if (metadata.isIsNullParameter()) {
367-
return where.isNull();
384+
return type.equals(SIMPLE_PROPERTY) ? where.isNull() : where.isNotNull();
368385
}
369386

370-
return whereIgnoreCase.eq(potentiallyIgnoreCase(property, JpqlQueryBuilder.expression(render(metadata))));
371-
case NEGATING_SIMPLE_PROPERTY:
372-
return whereIgnoreCase
373-
.neq(potentiallyIgnoreCase(property, JpqlQueryBuilder.expression(render(provider.next(part)))));
387+
JpqlQueryBuilder.Expression expression = potentiallyIgnoreCase(property, placeholder(metadata));
388+
return type.equals(SIMPLE_PROPERTY) ? whereIgnoreCase.eq(expression) : whereIgnoreCase.neq(expression);
374389
case IS_EMPTY:
375390
case IS_NOT_EMPTY:
376391

@@ -404,8 +419,8 @@ private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(JpqlQueryBuilder.O
404419
* @param path must not be {@literal null}.
405420
* @return
406421
*/
407-
private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(PathAndOrigin pas) {
408-
return potentiallyIgnoreCase(pas.path(), JpqlQueryBuilder.expression(pas));
422+
private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(PathAndOrigin path) {
423+
return potentiallyIgnoreCase(path.path(), JpqlQueryBuilder.expression(path));
409424
}
410425

411426
/**

0 commit comments

Comments
 (0)