2323import java .util .Objects ;
2424import java .util .concurrent .ConcurrentHashMap ;
2525
26- import org .springframework .data .domain .Pageable ;
27-
2826import org .jspecify .annotations .Nullable ;
27+
28+ import org .springframework .data .domain .Pageable ;
2929import org .springframework .data .domain .Sort ;
3030import org .springframework .data .expression .ValueEvaluationContextProvider ;
3131import org .springframework .data .jpa .repository .QueryRewriter ;
5454 */
5555abstract 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