Skip to content

Commit 363b946

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 e14e382 commit 363b946

17 files changed

+2085
-90
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-43
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
* @author Jinmyeong Kim
6068
*/
6169
class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Predicate> implements JpqlQueryCreator {
@@ -66,8 +74,8 @@ class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Pred
6674
private final PartTree tree;
6775
private final EscapeCharacter escape;
6876
private final EntityType<?> entityType;
69-
private final From<?, ?> from;
7077
private final JpqlQueryBuilder.Entity entity;
78+
private final Metamodel metamodel;
7179

7280
/**
7381
* Create a new {@link JpaQueryCreator}.
@@ -88,12 +96,12 @@ public JpaQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvid
8896
this.templates = templates;
8997
this.escape = provider.getEscape();
9098
this.entityType = em.getMetamodel().entity(type.getDomainType());
91-
this.from = em.getCriteriaBuilder().createQuery().from(type.getDomainType());
9299
this.entity = JpqlQueryBuilder.entity(returnedType.getDomainType());
100+
this.metamodel = em.getMetamodel();
93101
}
94102

95-
From<?, ?> getFrom() {
96-
return from;
103+
Bindable<?> getFrom() {
104+
return entityType;
97105
}
98106

99107
JpqlQueryBuilder.Entity getEntity() {
@@ -175,7 +183,7 @@ protected JpqlQueryBuilder.Select buildQuery(Sort sort) {
175183
QueryUtils.checkSortExpression(order);
176184

177185
try {
178-
expression = JpqlQueryBuilder.expression(JpqlUtils.toExpressionRecursively(entity, from,
186+
expression = JpqlQueryBuilder.expression(JpqlUtils.toExpressionRecursively(metamodel, entity, entityType,
179187
PropertyPath.from(order.getProperty(), entityType.getJavaType())));
180188
} catch (PropertyReferenceException e) {
181189

@@ -210,12 +218,19 @@ private JpqlQueryBuilder.Select doSelect(Sort sort) {
210218

211219
if (returnedType.needsCustomConstruction()) {
212220

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

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

221236
if (useTupleQuery()) {
@@ -231,14 +246,14 @@ private JpqlQueryBuilder.Select doSelect(Sort sort) {
231246
if (entityType.hasSingleIdAttribute()) {
232247

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

237252
} else {
238253

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

258-
String render(ParameterBinding binding) {
259-
return render(binding.getRequiredPosition());
273+
JpqlQueryBuilder.Expression placeholder(ParameterBinding binding) {
274+
return placeholder(binding.getRequiredPosition());
260275
}
261276

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

266281
/**
@@ -305,33 +320,33 @@ public JpqlQueryBuilder.Predicate build() {
305320
PropertyPath property = part.getProperty();
306321
Type type = part.getType();
307322

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

312327
switch (type) {
313328
case BETWEEN:
314329
PartTreeParameterBinding first = provider.next(part);
315330
ParameterBinding second = provider.next(part);
316-
return where.between(render(first), render(second));
331+
return where.between(placeholder(first), placeholder(second));
317332
case AFTER:
318333
case GREATER_THAN:
319-
return where.gt(render(provider.next(part)));
334+
return where.gt(placeholder(provider.next(part)));
320335
case GREATER_THAN_EQUAL:
321-
return where.gte(render(provider.next(part)));
336+
return where.gte(placeholder(provider.next(part)));
322337
case BEFORE:
323338
case LESS_THAN:
324-
return where.lt(render(provider.next(part)));
339+
return where.lt(placeholder(provider.next(part)));
325340
case LESS_THAN_EQUAL:
326-
return where.lte(render(provider.next(part)));
341+
return where.lte(placeholder(provider.next(part)));
327342
case IS_NULL:
328343
return where.isNull();
329344
case IS_NOT_NULL:
330345
return where.isNotNull();
331346
case NOT_IN:
332-
return whereIgnoreCase.notIn(render(provider.next(part, Collection.class)));
347+
return whereIgnoreCase.notIn(placeholder(provider.next(part, Collection.class)));
333348
case IN:
334-
return whereIgnoreCase.in(render(provider.next(part, Collection.class)));
349+
return whereIgnoreCase.in(placeholder(provider.next(part, Collection.class)));
335350
case STARTING_WITH:
336351
case ENDING_WITH:
337352
case CONTAINING:
@@ -340,16 +355,16 @@ public JpqlQueryBuilder.Predicate build() {
340355
if (property.getLeafProperty().isCollection()) {
341356
where = JpqlQueryBuilder.where(entity, property);
342357

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

347362
case LIKE:
348363
case NOT_LIKE:
349364

350365
PartTreeParameterBinding parameter = provider.next(part, String.class);
351366
JpqlQueryBuilder.Expression parameterExpression = potentiallyIgnoreCase(part.getProperty(),
352-
JpqlQueryBuilder.parameter(render(parameter)));
367+
placeholder(parameter));
353368
// Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
354369
String escapeChar = Character.toString(escape.getEscapeCharacter());
355370
return
@@ -362,23 +377,16 @@ public JpqlQueryBuilder.Predicate build() {
362377
case FALSE:
363378
return where.isFalse();
364379
case SIMPLE_PROPERTY:
365-
PartTreeParameterBinding simple = provider.next(part);
366-
367-
if (simple.isIsNullParameter()) {
368-
return where.isNull();
369-
}
370-
371-
return whereIgnoreCase.eq(potentiallyIgnoreCase(property, JpqlQueryBuilder.expression(render(simple))));
372380
case NEGATING_SIMPLE_PROPERTY:
373381

374-
PartTreeParameterBinding negating = provider.next(part);
382+
PartTreeParameterBinding simple = provider.next(part);
375383

376-
if (negating.isIsNullParameter()) {
377-
return where.isNotNull();
384+
if (simple.isIsNullParameter()) {
385+
return type.equals(SIMPLE_PROPERTY) ? where.isNull() : where.isNotNull();
378386
}
379387

380-
return whereIgnoreCase
381-
.neq(potentiallyIgnoreCase(property, JpqlQueryBuilder.expression(render(negating))));
388+
JpqlQueryBuilder.Expression expression = potentiallyIgnoreCase(property, placeholder(metadata));
389+
return type.equals(SIMPLE_PROPERTY) ? whereIgnoreCase.eq(expression) : whereIgnoreCase.neq(expression);
382390
case IS_EMPTY:
383391
case IS_NOT_EMPTY:
384392

@@ -412,8 +420,8 @@ private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(JpqlQueryBuilder.O
412420
* @param path must not be {@literal null}.
413421
* @return
414422
*/
415-
private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(PathAndOrigin pas) {
416-
return potentiallyIgnoreCase(pas.path(), JpqlQueryBuilder.expression(pas));
423+
private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(PathAndOrigin path) {
424+
return potentiallyIgnoreCase(path.path(), JpqlQueryBuilder.expression(path));
417425
}
418426

419427
/**

0 commit comments

Comments
 (0)