20
20
21
21
import java .util .Objects ;
22
22
23
- import org .springframework .data .domain .Pageable ;
24
-
25
23
import org .jspecify .annotations .Nullable ;
24
+
25
+ import org .springframework .data .domain .Pageable ;
26
26
import org .springframework .data .domain .Sort ;
27
27
import org .springframework .data .expression .ValueEvaluationContextProvider ;
28
28
import org .springframework .data .jpa .repository .QueryRewriter ;
32
32
import org .springframework .data .util .Lazy ;
33
33
import org .springframework .util .Assert ;
34
34
import org .springframework .util .ConcurrentLruCache ;
35
- import org .springframework .util .StringUtils ;
36
35
37
36
/**
38
37
* Base class for {@link String} based JPA queries.
49
48
*/
50
49
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
51
50
52
- private final StringQuery query ;
53
- private final Lazy <IntrospectedQuery > countQuery ;
51
+ private final EntityQuery query ;
52
+ private final Lazy <ParametrizedQuery > countQuery ;
54
53
private final ValueExpressionDelegate valueExpressionDelegate ;
55
54
private final QueryRewriter queryRewriter ;
56
55
private final QuerySortRewriter querySortRewriter ;
@@ -64,25 +63,42 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
64
63
* @param method must not be {@literal null}.
65
64
* @param em must not be {@literal null}.
66
65
* @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 .
68
67
* @param queryConfiguration must not be {@literal null}.
69
68
*/
70
- public AbstractStringBasedJpaQuery (JpaQueryMethod method , EntityManager em , String queryString ,
69
+ AbstractStringBasedJpaQuery (JpaQueryMethod method , EntityManager em , String queryString ,
71
70
@ 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 ) {
72
87
73
88
super (method , em );
74
89
75
- Assert .hasText ( queryString , "Query string must not be null or empty " );
90
+ Assert .notNull ( query , "Query must not be null" );
76
91
Assert .notNull (queryConfiguration , "JpaQueryConfiguration must not be null" );
77
92
78
93
this .valueExpressionDelegate = queryConfiguration .getValueExpressionDelegate ();
79
94
this .valueExpressionContextProvider = valueExpressionDelegate .createValueContextProvider (method .getParameters ());
80
- this .query = ExpressionBasedStringQuery .create (queryString , method , queryConfiguration );
95
+
96
+ this .query = TemplatedQuery .create (query , method .getEntityInformation (), queryConfiguration );
81
97
82
98
this .countQuery = Lazy .of (() -> {
83
99
84
- if (StringUtils . hasText ( countQueryString ) ) {
85
- return ExpressionBasedStringQuery .create (countQueryString , method , queryConfiguration );
100
+ if (countQuery != null ) {
101
+ return TemplatedQuery .create (countQuery , method . getEntityInformation () , queryConfiguration );
86
102
}
87
103
88
104
return this .query .deriveCountQuery (method .getCountQueryProjection ());
@@ -108,21 +124,25 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
108
124
"JDBC style parameters (?) are not supported for JPA queries" );
109
125
}
110
126
127
+ private DeclaredQuery createQuery (String queryString , boolean nativeQuery ) {
128
+ return nativeQuery ? DeclaredQuery .nativeQuery (queryString ) : DeclaredQuery .jpqlQuery (queryString );
129
+ }
130
+
111
131
@ Override
112
132
public Query doCreateQuery (JpaParametersParameterAccessor accessor ) {
113
133
114
134
Sort sort = accessor .getSort ();
115
135
ResultProcessor processor = getQueryMethod ().getResultProcessor ().withDynamicProjection (accessor );
116
136
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 );
119
139
120
140
// it is ok to reuse the binding contained in the ParameterBinder, although we create a new query String because the
121
141
// parameters in the query do not change.
122
142
return parameterBinder .get ().bindAndPrepare (query , accessor );
123
143
}
124
144
125
- String getSortedQueryString (Sort sort , ReturnedType returnedType ) {
145
+ QueryProvider getSortedQuery (Sort sort , ReturnedType returnedType ) {
126
146
return querySortRewriter .getSorted (query , sort , returnedType );
127
147
}
128
148
@@ -131,7 +151,7 @@ protected ParameterBinder createBinder() {
131
151
return createBinder (query );
132
152
}
133
153
134
- protected ParameterBinder createBinder (IntrospectedQuery query ) {
154
+ protected ParameterBinder createBinder (ParametrizedQuery query ) {
135
155
return ParameterBinderFactory .createQueryAwareBinder (getQueryMethod ().getParameters (), query ,
136
156
valueExpressionDelegate , valueExpressionContextProvider );
137
157
}
@@ -164,19 +184,19 @@ public EntityQuery getQuery() {
164
184
/**
165
185
* @return the countQuery
166
186
*/
167
- public IntrospectedQuery getCountQuery () {
187
+ public ParametrizedQuery getCountQuery () {
168
188
return countQuery .get ();
169
189
}
170
190
171
191
/**
172
192
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
173
193
* type.
174
194
*/
175
- protected Query createJpaQuery (String queryString , Sort sort , @ Nullable Pageable pageable ,
195
+ protected Query createJpaQuery (QueryProvider query , Sort sort , @ Nullable Pageable pageable ,
176
196
ReturnedType returnedType ) {
177
197
178
198
EntityManager em = getEntityManager ();
179
- String queryToUse = potentiallyRewriteQuery (queryString , sort , pageable );
199
+ String queryToUse = potentiallyRewriteQuery (query . getQueryString () , sort , pageable );
180
200
181
201
if (this .query .hasConstructorExpression () || this .query .isDefaultProjection ()) {
182
202
return em .createQuery (queryToUse );
@@ -205,16 +225,16 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
205
225
: queryRewriter .rewrite (originalQuery , sort );
206
226
}
207
227
208
- String applySorting (CachableQuery cachableQuery ) {
209
- return cachableQuery .getDeclaredQuery (). getQueryEnhancer ()
228
+ QueryProvider applySorting (CachableQuery cachableQuery ) {
229
+ return cachableQuery .getDeclaredQuery ()
210
230
.rewrite (new DefaultQueryRewriteInformation (cachableQuery .getSort (), cachableQuery .getReturnedType ()));
211
231
}
212
232
213
233
/**
214
234
* Query Sort Rewriter interface.
215
235
*/
216
236
interface QuerySortRewriter {
217
- String getSorted (StringQuery query , Sort sort , ReturnedType returnedType );
237
+ QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType );
218
238
}
219
239
220
240
/**
@@ -224,28 +244,28 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
224
244
225
245
INSTANCE ;
226
246
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 ));
229
249
}
230
250
}
231
251
232
252
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
233
253
234
- private volatile @ Nullable String cachedQueryString ;
254
+ private volatile @ Nullable QueryProvider cachedQuery ;
235
255
236
- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
256
+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
237
257
238
258
if (sort .isSorted ()) {
239
259
throw new UnsupportedOperationException ("NoOpQueryCache does not support sorting" );
240
260
}
241
261
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
245
265
.rewrite (new DefaultQueryRewriteInformation (sort , returnedType ));
246
266
}
247
267
248
- return cachedQueryString ;
268
+ return cachedQuery ;
249
269
}
250
270
}
251
271
@@ -254,22 +274,22 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
254
274
*/
255
275
class CachingQuerySortRewriter implements QuerySortRewriter {
256
276
257
- private final ConcurrentLruCache <CachableQuery , String > queryCache = new ConcurrentLruCache <>(16 ,
277
+ private final ConcurrentLruCache <CachableQuery , QueryProvider > queryCache = new ConcurrentLruCache <>(16 ,
258
278
AbstractStringBasedJpaQuery .this ::applySorting );
259
279
260
- private volatile @ Nullable String cachedQueryString ;
280
+ private volatile @ Nullable QueryProvider cachedQuery ;
261
281
262
282
@ Override
263
- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
283
+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
264
284
265
285
if (sort .isUnsorted ()) {
266
286
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 ));
270
290
}
271
291
272
- return cachedQueryString ;
292
+ return cachedQuery ;
273
293
}
274
294
275
295
return queryCache .get (new CachableQuery (query , sort , returnedType ));
@@ -285,20 +305,20 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
285
305
*/
286
306
static class CachableQuery {
287
307
288
- private final StringQuery query ;
308
+ private final EntityQuery query ;
289
309
private final String queryString ;
290
310
private final Sort sort ;
291
311
private final ReturnedType returnedType ;
292
312
293
- CachableQuery (StringQuery query , Sort sort , ReturnedType returnedType ) {
313
+ CachableQuery (EntityQuery query , Sort sort , ReturnedType returnedType ) {
294
314
295
315
this .query = query ;
296
316
this .queryString = query .getQueryString ();
297
317
this .sort = sort ;
298
318
this .returnedType = returnedType ;
299
319
}
300
320
301
- StringQuery getDeclaredQuery () {
321
+ EntityQuery getDeclaredQuery () {
302
322
return query ;
303
323
}
304
324
0 commit comments