23
23
import java .util .Objects ;
24
24
import java .util .concurrent .ConcurrentHashMap ;
25
25
26
- import org .springframework .data .domain .Pageable ;
27
-
28
26
import org .jspecify .annotations .Nullable ;
27
+
28
+ import org .springframework .data .domain .Pageable ;
29
29
import org .springframework .data .domain .Sort ;
30
30
import org .springframework .data .expression .ValueEvaluationContextProvider ;
31
31
import org .springframework .data .jpa .repository .QueryRewriter ;
54
54
*/
55
55
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
56
56
57
- private final StringQuery query ;
57
+ private final EntityQuery query ;
58
58
private final Map <Class <?>, Boolean > knownProjections = new ConcurrentHashMap <>();
59
- private final Lazy <IntrospectedQuery > countQuery ;
59
+ private final Lazy <ParametrizedQuery > countQuery ;
60
60
private final ValueExpressionDelegate valueExpressionDelegate ;
61
61
private final QueryRewriter queryRewriter ;
62
62
private final QuerySortRewriter querySortRewriter ;
@@ -70,25 +70,42 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
70
70
* @param method must not be {@literal null}.
71
71
* @param em must not be {@literal null}.
72
72
* @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 .
74
74
* @param queryConfiguration must not be {@literal null}.
75
75
*/
76
- public AbstractStringBasedJpaQuery (JpaQueryMethod method , EntityManager em , String queryString ,
76
+ AbstractStringBasedJpaQuery (JpaQueryMethod method , EntityManager em , String queryString ,
77
77
@ 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 ) {
78
94
79
95
super (method , em );
80
96
81
- Assert .hasText ( queryString , "Query string must not be null or empty " );
97
+ Assert .notNull ( query , "Query must not be null" );
82
98
Assert .notNull (queryConfiguration , "JpaQueryConfiguration must not be null" );
83
99
84
100
this .valueExpressionDelegate = queryConfiguration .getValueExpressionDelegate ();
85
101
this .valueExpressionContextProvider = valueExpressionDelegate .createValueContextProvider (method .getParameters ());
86
- this .query = ExpressionBasedStringQuery .create (queryString , method , queryConfiguration );
102
+
103
+ this .query = TemplatedQuery .create (query , method .getEntityInformation (), queryConfiguration );
87
104
88
105
this .countQuery = Lazy .of (() -> {
89
106
90
- if (StringUtils . hasText ( countQueryString ) ) {
91
- return ExpressionBasedStringQuery .create (countQueryString , method , queryConfiguration );
107
+ if (countQuery != null ) {
108
+ return TemplatedQuery .create (countQuery , method . getEntityInformation () , queryConfiguration );
92
109
}
93
110
94
111
return this .query .deriveCountQuery (method .getCountQueryProjection ());
@@ -114,14 +131,18 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
114
131
"JDBC style parameters (?) are not supported for JPA queries" );
115
132
}
116
133
134
+ private DeclaredQuery createQuery (String queryString , boolean nativeQuery ) {
135
+ return nativeQuery ? DeclaredQuery .nativeQuery (queryString ) : DeclaredQuery .jpqlQuery (queryString );
136
+ }
137
+
117
138
@ Override
118
139
public Query doCreateQuery (JpaParametersParameterAccessor accessor ) {
119
140
120
141
Sort sort = accessor .getSort ();
121
142
ResultProcessor processor = getQueryMethod ().getResultProcessor ().withDynamicProjection (accessor );
122
143
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 );
125
146
126
147
// it is ok to reuse the binding contained in the ParameterBinder, although we create a new query String because the
127
148
// parameters in the query do not change.
@@ -212,7 +233,7 @@ protected ParameterBinder createBinder() {
212
233
return createBinder (query );
213
234
}
214
235
215
- protected ParameterBinder createBinder (IntrospectedQuery query ) {
236
+ protected ParameterBinder createBinder (ParametrizedQuery query ) {
216
237
return ParameterBinderFactory .createQueryAwareBinder (getQueryMethod ().getParameters (), query ,
217
238
valueExpressionDelegate , valueExpressionContextProvider );
218
239
}
@@ -245,19 +266,19 @@ public EntityQuery getQuery() {
245
266
/**
246
267
* @return the countQuery
247
268
*/
248
- public IntrospectedQuery getCountQuery () {
269
+ public ParametrizedQuery getCountQuery () {
249
270
return countQuery .get ();
250
271
}
251
272
252
273
/**
253
274
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
254
275
* type.
255
276
*/
256
- protected Query createJpaQuery (String queryString , Sort sort , @ Nullable Pageable pageable ,
277
+ protected Query createJpaQuery (QueryProvider query , Sort sort , @ Nullable Pageable pageable ,
257
278
ReturnedType returnedType ) {
258
279
259
280
EntityManager em = getEntityManager ();
260
- String queryToUse = potentiallyRewriteQuery (queryString , sort , pageable );
281
+ String queryToUse = potentiallyRewriteQuery (query . getQueryString () , sort , pageable );
261
282
262
283
if (this .query .hasConstructorExpression () || this .query .isDefaultProjection ()) {
263
284
return em .createQuery (queryToUse );
@@ -286,16 +307,16 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
286
307
: queryRewriter .rewrite (originalQuery , sort );
287
308
}
288
309
289
- String applySorting (CachableQuery cachableQuery ) {
290
- return cachableQuery .getDeclaredQuery (). getQueryEnhancer ()
310
+ QueryProvider applySorting (CachableQuery cachableQuery ) {
311
+ return cachableQuery .getDeclaredQuery ()
291
312
.rewrite (new DefaultQueryRewriteInformation (cachableQuery .getSort (), cachableQuery .getReturnedType ()));
292
313
}
293
314
294
315
/**
295
316
* Query Sort Rewriter interface.
296
317
*/
297
318
interface QuerySortRewriter {
298
- String getSorted (StringQuery query , Sort sort , ReturnedType returnedType );
319
+ QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType );
299
320
}
300
321
301
322
/**
@@ -305,28 +326,28 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
305
326
306
327
INSTANCE ;
307
328
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 ));
310
331
}
311
332
}
312
333
313
334
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
314
335
315
- private volatile @ Nullable String cachedQueryString ;
336
+ private volatile @ Nullable QueryProvider cachedQuery ;
316
337
317
- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
338
+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
318
339
319
340
if (sort .isSorted ()) {
320
341
throw new UnsupportedOperationException ("NoOpQueryCache does not support sorting" );
321
342
}
322
343
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
326
347
.rewrite (new DefaultQueryRewriteInformation (sort , returnedType ));
327
348
}
328
349
329
- return cachedQueryString ;
350
+ return cachedQuery ;
330
351
}
331
352
}
332
353
@@ -335,22 +356,22 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
335
356
*/
336
357
class CachingQuerySortRewriter implements QuerySortRewriter {
337
358
338
- private final ConcurrentLruCache <CachableQuery , String > queryCache = new ConcurrentLruCache <>(16 ,
359
+ private final ConcurrentLruCache <CachableQuery , QueryProvider > queryCache = new ConcurrentLruCache <>(16 ,
339
360
AbstractStringBasedJpaQuery .this ::applySorting );
340
361
341
- private volatile @ Nullable String cachedQueryString ;
362
+ private volatile @ Nullable QueryProvider cachedQuery ;
342
363
343
364
@ Override
344
- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
365
+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
345
366
346
367
if (sort .isUnsorted ()) {
347
368
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 ));
351
372
}
352
373
353
- return cachedQueryString ;
374
+ return cachedQuery ;
354
375
}
355
376
356
377
return queryCache .get (new CachableQuery (query , sort , returnedType ));
@@ -366,20 +387,20 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
366
387
*/
367
388
static class CachableQuery {
368
389
369
- private final StringQuery query ;
390
+ private final EntityQuery query ;
370
391
private final String queryString ;
371
392
private final Sort sort ;
372
393
private final ReturnedType returnedType ;
373
394
374
- CachableQuery (StringQuery query , Sort sort , ReturnedType returnedType ) {
395
+ CachableQuery (EntityQuery query , Sort sort , ReturnedType returnedType ) {
375
396
376
397
this .query = query ;
377
398
this .queryString = query .getQueryString ();
378
399
this .sort = sort ;
379
400
this .returnedType = returnedType ;
380
401
}
381
402
382
- StringQuery getDeclaredQuery () {
403
+ EntityQuery getDeclaredQuery () {
383
404
return query ;
384
405
}
385
406
0 commit comments