Skip to content

Commit e2446ca

Browse files
committed
Polishing.
Introduce refined names: EntityQuery, TemplatedQuery, ParametrizedQuery, QueryProvider. Return QueryProvider where possible. Introduce rewrite as concept on DeclaredQuery to retain its nature and track the origin of the query rewriting. Move methods solely used in tests to TestDefaultEntityQuery. Remove unused methods, fix naming, group DeclaredQuery implementations in DeclaredQueries. Add documentation. See #3622 Original pull request: #3527
1 parent 5da86f1 commit e2446ca

File tree

55 files changed

+1469
-1177
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1469
-1177
lines changed

spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.openjdk.jmh.annotations.Warmup;
2828

2929
import org.springframework.data.domain.Sort;
30+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
31+
import org.springframework.data.repository.query.ReturnedType;
3032

3133
/**
3234
* @author Mark Paluch
@@ -44,6 +46,7 @@ public static class BenchmarkParameters {
4446
DeclaredQuery query;
4547
Sort sort = Sort.by("foo");
4648
QueryEnhancer enhancer;
49+
QueryEnhancer.QueryRewriteInformation rewriteInformation;
4750

4851
@Setup(Level.Iteration)
4952
public void doSetup() {
@@ -57,12 +60,14 @@ OR TREAT(p AS SmallProject).name LIKE 'Persist%'
5760

5861
query = DeclaredQuery.jpqlQuery(s);
5962
enhancer = QueryEnhancerFactory.forQuery(query).create(query);
63+
rewriteInformation = new DefaultQueryRewriteInformation(sort,
64+
ReturnedType.of(Object.class, Object.class, new SpelAwareProxyProjectionFactory()));
6065
}
6166
}
6267

6368
@Benchmark
6469
public Object measure(BenchmarkParameters parameters) {
65-
return parameters.enhancer.applySorting(parameters.sort);
70+
return parameters.enhancer.rewrite(parameters.rewriteInformation);
6671
}
6772

6873
}

spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.openjdk.jmh.annotations.Warmup;
3030

3131
import org.springframework.data.domain.Sort;
32+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
33+
import org.springframework.data.repository.query.ReturnedType;
3234

3335
/**
3436
* @author Mark Paluch
@@ -46,6 +48,7 @@ public static class BenchmarkParameters {
4648
JSqlParserQueryEnhancer enhancer;
4749
Sort sort = Sort.by("foo");
4850
private byte[] serialized;
51+
private QueryEnhancer.QueryRewriteInformation rewriteInformation;
4952

5053
@Setup(Level.Iteration)
5154
public void doSetup() throws IOException {
@@ -57,12 +60,14 @@ public void doSetup() throws IOException {
5760
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";
5861

5962
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.nativeQuery(s));
63+
rewriteInformation = new DefaultQueryRewriteInformation(sort,
64+
ReturnedType.of(Object.class, Object.class, new SpelAwareProxyProjectionFactory()));
6065
}
6166
}
6267

6368
@Benchmark
6469
public Object applySortWithParsing(BenchmarkParameters p) {
65-
return p.enhancer.applySorting(p.sort);
70+
return p.enhancer.rewrite(p.rewriteInformation);
6671
}
6772

6873
}

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

-11
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,17 @@
1515
*/
1616
package org.springframework.data.jpa.repository;
1717

18-
import jakarta.persistence.criteria.CriteriaBuilder;
19-
import jakarta.persistence.criteria.CriteriaQuery;
20-
import jakarta.persistence.criteria.Root;
21-
22-
import java.util.Arrays;
23-
import java.util.Collection;
2418
import java.util.Arrays;
2519
import java.util.Collection;
2620
import java.util.List;
2721
import java.util.Optional;
2822
import java.util.function.Function;
2923

30-
import org.springframework.dao.InvalidDataAccessApiUsageException;
3124
import org.jspecify.annotations.Nullable;
3225

3326
import org.springframework.dao.InvalidDataAccessApiUsageException;
34-
35-
import org.jspecify.annotations.Nullable;
3627
import org.springframework.data.domain.Page;
3728
import org.springframework.data.domain.Pageable;
38-
import org.springframework.data.domain.Slice;
3929
import org.springframework.data.domain.Sort;
4030
import org.springframework.data.jpa.domain.DeleteSpecification;
4131
import org.springframework.data.jpa.domain.PredicateSpecification;
@@ -115,7 +105,6 @@ default List<T> findAll(PredicateSpecification<T> spec) {
115105
* Returns a {@link Page} of entities matching the given {@link Specification}.
116106
* <p>
117107
* Supports counting the total number of entities matching the {@link Specification}.
118-
* <p>
119108
*
120109
* @param spec can be {@literal null}, if no {@link Specification} is given all entities matching {@code <T>} will be
121110
* selected.

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

+1
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,5 @@
9494
* Name of the {@link jakarta.persistence.SqlResultSetMapping @SqlResultSetMapping(name)} to apply for this query.
9595
*/
9696
String sqlResultSetMapping() default "";
97+
9798
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,5 @@
9090
* @since 3.0
9191
*/
9292
Class<? extends QueryRewriter> queryRewriter() default QueryRewriter.IdentityQueryRewriter.class;
93+
9394
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaRepositories.java

+1
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,5 @@
178178
* @since 4.0
179179
*/
180180
Class<? extends QueryEnhancerSelector> queryEnhancerSelector() default QueryEnhancerSelector.DefaultQueryEnhancerSelector.class;
181+
181182
}

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

+58-37
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
import java.util.Objects;
2424
import java.util.concurrent.ConcurrentHashMap;
2525

26-
import org.springframework.data.domain.Pageable;
27-
2826
import org.jspecify.annotations.Nullable;
27+
28+
import org.springframework.data.domain.Pageable;
2929
import org.springframework.data.domain.Sort;
3030
import org.springframework.data.expression.ValueEvaluationContextProvider;
3131
import org.springframework.data.jpa.repository.QueryRewriter;
@@ -54,9 +54,9 @@
5454
*/
5555
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
5656

57-
private final StringQuery query;
57+
private final EntityQuery query;
5858
private final Map<Class<?>, Boolean> knownProjections = new ConcurrentHashMap<>();
59-
private final Lazy<IntrospectedQuery> countQuery;
59+
private final Lazy<ParametrizedQuery> countQuery;
6060
private final ValueExpressionDelegate valueExpressionDelegate;
6161
private final QueryRewriter queryRewriter;
6262
private final QuerySortRewriter querySortRewriter;
@@ -70,25 +70,42 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
7070
* @param method must not be {@literal null}.
7171
* @param em must not be {@literal null}.
7272
* @param queryString must not be {@literal null}.
73-
* @param countQueryString must not be {@literal null}.
73+
* @param countQuery can be {@literal null} if not defined.
7474
* @param queryConfiguration must not be {@literal null}.
7575
*/
76-
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
76+
AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
7777
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
78+
this(method, em, method.getDeclaredQuery(queryString),
79+
countQueryString != null ? method.getDeclaredQuery(countQueryString) : null, queryConfiguration);
80+
}
81+
82+
/**
83+
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
84+
* query {@link String}.
85+
*
86+
* @param method must not be {@literal null}.
87+
* @param em must not be {@literal null}.
88+
* @param query must not be {@literal null}.
89+
* @param countQuery can be {@literal null}.
90+
* @param queryConfiguration must not be {@literal null}.
91+
*/
92+
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, DeclaredQuery query,
93+
@Nullable DeclaredQuery countQuery, JpaQueryConfiguration queryConfiguration) {
7894

7995
super(method, em);
8096

81-
Assert.hasText(queryString, "Query string must not be null or empty");
97+
Assert.notNull(query, "Query must not be null");
8298
Assert.notNull(queryConfiguration, "JpaQueryConfiguration must not be null");
8399

84100
this.valueExpressionDelegate = queryConfiguration.getValueExpressionDelegate();
85101
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
86-
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);
102+
103+
this.query = TemplatedQuery.create(query, method.getEntityInformation(), queryConfiguration);
87104

88105
this.countQuery = Lazy.of(() -> {
89106

90-
if (StringUtils.hasText(countQueryString)) {
91-
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
107+
if (countQuery != null) {
108+
return TemplatedQuery.create(countQuery, method.getEntityInformation(), queryConfiguration);
92109
}
93110

94111
return this.query.deriveCountQuery(method.getCountQueryProjection());
@@ -114,14 +131,18 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
114131
"JDBC style parameters (?) are not supported for JPA queries");
115132
}
116133

134+
private DeclaredQuery createQuery(String queryString, boolean nativeQuery) {
135+
return nativeQuery ? DeclaredQuery.nativeQuery(queryString) : DeclaredQuery.jpqlQuery(queryString);
136+
}
137+
117138
@Override
118139
public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
119140

120141
Sort sort = accessor.getSort();
121142
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
122143
ReturnedType returnedType = getReturnedType(processor);
123-
String sortedQueryString = getSortedQueryString(sort, returnedType);
124-
Query query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), returnedType);
144+
QueryProvider sortedQuery = getSortedQuery(sort, returnedType);
145+
Query query = createJpaQuery(sortedQuery, sort, accessor.getPageable(), returnedType);
125146

126147
// it is ok to reuse the binding contained in the ParameterBinder, although we create a new query String because the
127148
// parameters in the query do not change.
@@ -212,7 +233,7 @@ protected ParameterBinder createBinder() {
212233
return createBinder(query);
213234
}
214235

215-
protected ParameterBinder createBinder(IntrospectedQuery query) {
236+
protected ParameterBinder createBinder(ParametrizedQuery query) {
216237
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
217238
valueExpressionDelegate, valueExpressionContextProvider);
218239
}
@@ -245,19 +266,19 @@ public EntityQuery getQuery() {
245266
/**
246267
* @return the countQuery
247268
*/
248-
public IntrospectedQuery getCountQuery() {
269+
public ParametrizedQuery getCountQuery() {
249270
return countQuery.get();
250271
}
251272

252273
/**
253274
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
254275
* type.
255276
*/
256-
protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable pageable,
277+
protected Query createJpaQuery(QueryProvider query, Sort sort, @Nullable Pageable pageable,
257278
ReturnedType returnedType) {
258279

259280
EntityManager em = getEntityManager();
260-
String queryToUse = potentiallyRewriteQuery(queryString, sort, pageable);
281+
String queryToUse = potentiallyRewriteQuery(query.getQueryString(), sort, pageable);
261282

262283
if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) {
263284
return em.createQuery(queryToUse);
@@ -286,16 +307,16 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
286307
: queryRewriter.rewrite(originalQuery, sort);
287308
}
288309

289-
String applySorting(CachableQuery cachableQuery) {
290-
return cachableQuery.getDeclaredQuery().getQueryEnhancer()
310+
QueryProvider applySorting(CachableQuery cachableQuery) {
311+
return cachableQuery.getDeclaredQuery()
291312
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
292313
}
293314

294315
/**
295316
* Query Sort Rewriter interface.
296317
*/
297318
interface QuerySortRewriter {
298-
String getSorted(StringQuery query, Sort sort, ReturnedType returnedType);
319+
QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType);
299320
}
300321

301322
/**
@@ -305,28 +326,28 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
305326

306327
INSTANCE;
307328

308-
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
309-
return query.getQueryEnhancer().rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
329+
public QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType) {
330+
return query.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
310331
}
311332
}
312333

313334
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
314335

315-
private volatile @Nullable String cachedQueryString;
336+
private volatile @Nullable QueryProvider cachedQuery;
316337

317-
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
338+
public QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType) {
318339

319340
if (sort.isSorted()) {
320341
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
321342
}
322343

323-
String cachedQueryString = this.cachedQueryString;
324-
if (cachedQueryString == null) {
325-
this.cachedQueryString = cachedQueryString = query.getQueryEnhancer()
344+
QueryProvider cachedQuery = this.cachedQuery;
345+
if (cachedQuery == null) {
346+
this.cachedQuery = cachedQuery = query
326347
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
327348
}
328349

329-
return cachedQueryString;
350+
return cachedQuery;
330351
}
331352
}
332353

@@ -335,22 +356,22 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
335356
*/
336357
class CachingQuerySortRewriter implements QuerySortRewriter {
337358

338-
private final ConcurrentLruCache<CachableQuery, String> queryCache = new ConcurrentLruCache<>(16,
359+
private final ConcurrentLruCache<CachableQuery, QueryProvider> queryCache = new ConcurrentLruCache<>(16,
339360
AbstractStringBasedJpaQuery.this::applySorting);
340361

341-
private volatile @Nullable String cachedQueryString;
362+
private volatile @Nullable QueryProvider cachedQuery;
342363

343364
@Override
344-
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
365+
public QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType) {
345366

346367
if (sort.isUnsorted()) {
347368

348-
String cachedQueryString = this.cachedQueryString;
349-
if (cachedQueryString == null) {
350-
this.cachedQueryString = cachedQueryString = queryCache.get(new CachableQuery(query, sort, returnedType));
369+
QueryProvider cachedQuery = this.cachedQuery;
370+
if (cachedQuery == null) {
371+
this.cachedQuery = cachedQuery = queryCache.get(new CachableQuery(query, sort, returnedType));
351372
}
352373

353-
return cachedQueryString;
374+
return cachedQuery;
354375
}
355376

356377
return queryCache.get(new CachableQuery(query, sort, returnedType));
@@ -366,20 +387,20 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
366387
*/
367388
static class CachableQuery {
368389

369-
private final StringQuery query;
390+
private final EntityQuery query;
370391
private final String queryString;
371392
private final Sort sort;
372393
private final ReturnedType returnedType;
373394

374-
CachableQuery(StringQuery query, Sort sort, ReturnedType returnedType) {
395+
CachableQuery(EntityQuery query, Sort sort, ReturnedType returnedType) {
375396

376397
this.query = query;
377398
this.queryString = query.getQueryString();
378399
this.sort = sort;
379400
this.returnedType = returnedType;
380401
}
381402

382-
StringQuery getDeclaredQuery() {
403+
EntityQuery getDeclaredQuery() {
383404
return query;
384405
}
385406

0 commit comments

Comments
 (0)