Skip to content

Commit 8039dbd

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 7068a8a commit 8039dbd

File tree

55 files changed

+1472
-1179
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

+1472
-1179
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

+59-39
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020

2121
import java.util.Objects;
2222

23-
import org.springframework.data.domain.Pageable;
24-
2523
import org.jspecify.annotations.Nullable;
24+
25+
import org.springframework.data.domain.Pageable;
2626
import org.springframework.data.domain.Sort;
2727
import org.springframework.data.expression.ValueEvaluationContextProvider;
2828
import org.springframework.data.jpa.repository.QueryRewriter;
@@ -32,7 +32,6 @@
3232
import org.springframework.data.util.Lazy;
3333
import org.springframework.util.Assert;
3434
import org.springframework.util.ConcurrentLruCache;
35-
import org.springframework.util.StringUtils;
3635

3736
/**
3837
* Base class for {@link String} based JPA queries.
@@ -49,8 +48,8 @@
4948
*/
5049
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
5150

52-
private final StringQuery query;
53-
private final Lazy<IntrospectedQuery> countQuery;
51+
private final EntityQuery query;
52+
private final Lazy<ParametrizedQuery> countQuery;
5453
private final ValueExpressionDelegate valueExpressionDelegate;
5554
private final QueryRewriter queryRewriter;
5655
private final QuerySortRewriter querySortRewriter;
@@ -64,25 +63,42 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6463
* @param method must not be {@literal null}.
6564
* @param em must not be {@literal null}.
6665
* @param queryString must not be {@literal null}.
67-
* @param countQueryString must not be {@literal null}.
66+
* @param countQuery can be {@literal null} if not defined.
6867
* @param queryConfiguration must not be {@literal null}.
6968
*/
70-
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
69+
AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
7170
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
71+
this(method, em, method.getDeclaredQuery(queryString),
72+
countQueryString != null ? method.getDeclaredQuery(countQueryString) : null, queryConfiguration);
73+
}
74+
75+
/**
76+
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
77+
* query {@link String}.
78+
*
79+
* @param method must not be {@literal null}.
80+
* @param em must not be {@literal null}.
81+
* @param query must not be {@literal null}.
82+
* @param countQuery can be {@literal null}.
83+
* @param queryConfiguration must not be {@literal null}.
84+
*/
85+
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, DeclaredQuery query,
86+
@Nullable DeclaredQuery countQuery, JpaQueryConfiguration queryConfiguration) {
7287

7388
super(method, em);
7489

75-
Assert.hasText(queryString, "Query string must not be null or empty");
90+
Assert.notNull(query, "Query must not be null");
7691
Assert.notNull(queryConfiguration, "JpaQueryConfiguration must not be null");
7792

7893
this.valueExpressionDelegate = queryConfiguration.getValueExpressionDelegate();
7994
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
80-
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);
95+
96+
this.query = TemplatedQuery.create(query, method.getEntityInformation(), queryConfiguration);
8197

8298
this.countQuery = Lazy.of(() -> {
8399

84-
if (StringUtils.hasText(countQueryString)) {
85-
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
100+
if (countQuery != null) {
101+
return TemplatedQuery.create(countQuery, method.getEntityInformation(), queryConfiguration);
86102
}
87103

88104
return this.query.deriveCountQuery(method.getCountQueryProjection());
@@ -108,21 +124,25 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
108124
"JDBC style parameters (?) are not supported for JPA queries");
109125
}
110126

127+
private DeclaredQuery createQuery(String queryString, boolean nativeQuery) {
128+
return nativeQuery ? DeclaredQuery.nativeQuery(queryString) : DeclaredQuery.jpqlQuery(queryString);
129+
}
130+
111131
@Override
112132
public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
113133

114134
Sort sort = accessor.getSort();
115135
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
116136
ReturnedType returnedType = processor.getReturnedType();
117-
String sortedQueryString = getSortedQueryString(sort, returnedType);
118-
Query query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), returnedType);
137+
QueryProvider sortedQuery = getSortedQuery(sort, returnedType);
138+
Query query = createJpaQuery(sortedQuery, sort, accessor.getPageable(), returnedType);
119139

120140
// it is ok to reuse the binding contained in the ParameterBinder, although we create a new query String because the
121141
// parameters in the query do not change.
122142
return parameterBinder.get().bindAndPrepare(query, accessor);
123143
}
124144

125-
String getSortedQueryString(Sort sort, ReturnedType returnedType) {
145+
QueryProvider getSortedQuery(Sort sort, ReturnedType returnedType) {
126146
return querySortRewriter.getSorted(query, sort, returnedType);
127147
}
128148

@@ -131,7 +151,7 @@ protected ParameterBinder createBinder() {
131151
return createBinder(query);
132152
}
133153

134-
protected ParameterBinder createBinder(IntrospectedQuery query) {
154+
protected ParameterBinder createBinder(ParametrizedQuery query) {
135155
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
136156
valueExpressionDelegate, valueExpressionContextProvider);
137157
}
@@ -164,19 +184,19 @@ public EntityQuery getQuery() {
164184
/**
165185
* @return the countQuery
166186
*/
167-
public IntrospectedQuery getCountQuery() {
187+
public ParametrizedQuery getCountQuery() {
168188
return countQuery.get();
169189
}
170190

171191
/**
172192
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
173193
* type.
174194
*/
175-
protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable pageable,
195+
protected Query createJpaQuery(QueryProvider query, Sort sort, @Nullable Pageable pageable,
176196
ReturnedType returnedType) {
177197

178198
EntityManager em = getEntityManager();
179-
String queryToUse = potentiallyRewriteQuery(queryString, sort, pageable);
199+
String queryToUse = potentiallyRewriteQuery(query.getQueryString(), sort, pageable);
180200

181201
if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) {
182202
return em.createQuery(queryToUse);
@@ -205,16 +225,16 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
205225
: queryRewriter.rewrite(originalQuery, sort);
206226
}
207227

208-
String applySorting(CachableQuery cachableQuery) {
209-
return cachableQuery.getDeclaredQuery().getQueryEnhancer()
228+
QueryProvider applySorting(CachableQuery cachableQuery) {
229+
return cachableQuery.getDeclaredQuery()
210230
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
211231
}
212232

213233
/**
214234
* Query Sort Rewriter interface.
215235
*/
216236
interface QuerySortRewriter {
217-
String getSorted(StringQuery query, Sort sort, ReturnedType returnedType);
237+
QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType);
218238
}
219239

220240
/**
@@ -224,28 +244,28 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
224244

225245
INSTANCE;
226246

227-
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
228-
return query.getQueryEnhancer().rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
247+
public QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType) {
248+
return query.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
229249
}
230250
}
231251

232252
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
233253

234-
private volatile @Nullable String cachedQueryString;
254+
private volatile @Nullable QueryProvider cachedQuery;
235255

236-
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
256+
public QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType) {
237257

238258
if (sort.isSorted()) {
239259
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
240260
}
241261

242-
String cachedQueryString = this.cachedQueryString;
243-
if (cachedQueryString == null) {
244-
this.cachedQueryString = cachedQueryString = query.getQueryEnhancer()
262+
QueryProvider cachedQuery = this.cachedQuery;
263+
if (cachedQuery == null) {
264+
this.cachedQuery = cachedQuery = query
245265
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
246266
}
247267

248-
return cachedQueryString;
268+
return cachedQuery;
249269
}
250270
}
251271

@@ -254,22 +274,22 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
254274
*/
255275
class CachingQuerySortRewriter implements QuerySortRewriter {
256276

257-
private final ConcurrentLruCache<CachableQuery, String> queryCache = new ConcurrentLruCache<>(16,
277+
private final ConcurrentLruCache<CachableQuery, QueryProvider> queryCache = new ConcurrentLruCache<>(16,
258278
AbstractStringBasedJpaQuery.this::applySorting);
259279

260-
private volatile @Nullable String cachedQueryString;
280+
private volatile @Nullable QueryProvider cachedQuery;
261281

262282
@Override
263-
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
283+
public QueryProvider getSorted(EntityQuery query, Sort sort, ReturnedType returnedType) {
264284

265285
if (sort.isUnsorted()) {
266286

267-
String cachedQueryString = this.cachedQueryString;
268-
if (cachedQueryString == null) {
269-
this.cachedQueryString = cachedQueryString = queryCache.get(new CachableQuery(query, sort, returnedType));
287+
QueryProvider cachedQuery = this.cachedQuery;
288+
if (cachedQuery == null) {
289+
this.cachedQuery = cachedQuery = queryCache.get(new CachableQuery(query, sort, returnedType));
270290
}
271291

272-
return cachedQueryString;
292+
return cachedQuery;
273293
}
274294

275295
return queryCache.get(new CachableQuery(query, sort, returnedType));
@@ -285,20 +305,20 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
285305
*/
286306
static class CachableQuery {
287307

288-
private final StringQuery query;
308+
private final EntityQuery query;
289309
private final String queryString;
290310
private final Sort sort;
291311
private final ReturnedType returnedType;
292312

293-
CachableQuery(StringQuery query, Sort sort, ReturnedType returnedType) {
313+
CachableQuery(EntityQuery query, Sort sort, ReturnedType returnedType) {
294314

295315
this.query = query;
296316
this.queryString = query.getQueryString();
297317
this.sort = sort;
298318
this.returnedType = returnedType;
299319
}
300320

301-
StringQuery getDeclaredQuery() {
321+
EntityQuery getDeclaredQuery() {
302322
return query;
303323
}
304324

0 commit comments

Comments
 (0)