2020
2121import java .util .Objects ;
2222
23- import org .springframework .data .domain .Pageable ;
24-
2523import org .jspecify .annotations .Nullable ;
24+
25+ import org .springframework .data .domain .Pageable ;
2626import org .springframework .data .domain .Sort ;
2727import org .springframework .data .expression .ValueEvaluationContextProvider ;
2828import org .springframework .data .jpa .repository .QueryRewriter ;
3232import org .springframework .data .util .Lazy ;
3333import org .springframework .util .Assert ;
3434import org .springframework .util .ConcurrentLruCache ;
35- import org .springframework .util .StringUtils ;
3635
3736/**
3837 * Base class for {@link String} based JPA queries.
4948 */
5049abstract 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 }
@@ -162,28 +182,28 @@ public EntityQuery getQuery() {
162182 /**
163183 * @return the countQuery
164184 */
165- public IntrospectedQuery getCountQuery () {
185+ public ParametrizedQuery getCountQuery () {
166186 return countQuery .get ();
167187 }
168188
169189 /**
170190 * Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
171191 * type.
172192 */
173- protected Query createJpaQuery (String queryString , Sort sort , @ Nullable Pageable pageable ,
193+ protected Query createJpaQuery (QueryProvider query , Sort sort , @ Nullable Pageable pageable ,
174194 ReturnedType returnedType ) {
175195
176196 EntityManager em = getEntityManager ();
177197
178198 if (this .query .hasConstructorExpression () || this .query .isDefaultProjection ()) {
179- return em .createQuery (potentiallyRewriteQuery (queryString , sort , pageable ));
199+ return em .createQuery (potentiallyRewriteQuery (query . getQueryString () , sort , pageable ));
180200 }
181201
182202 Class <?> typeToRead = getTypeToRead (returnedType );
183203
184204 return typeToRead == null //
185- ? em .createQuery (potentiallyRewriteQuery (queryString , sort , pageable )) //
186- : em .createQuery (potentiallyRewriteQuery (queryString , sort , pageable ), typeToRead );
205+ ? em .createQuery (potentiallyRewriteQuery (query . getQueryString () , sort , pageable )) //
206+ : em .createQuery (potentiallyRewriteQuery (query . getQueryString () , sort , pageable ), typeToRead );
187207 }
188208
189209 /**
@@ -202,16 +222,16 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
202222 : queryRewriter .rewrite (originalQuery , sort );
203223 }
204224
205- String applySorting (CachableQuery cachableQuery ) {
206- return cachableQuery .getDeclaredQuery (). getQueryEnhancer ()
225+ QueryProvider applySorting (CachableQuery cachableQuery ) {
226+ return cachableQuery .getDeclaredQuery ()
207227 .rewrite (new DefaultQueryRewriteInformation (cachableQuery .getSort (), cachableQuery .getReturnedType ()));
208228 }
209229
210230 /**
211231 * Query Sort Rewriter interface.
212232 */
213233 interface QuerySortRewriter {
214- String getSorted (StringQuery query , Sort sort , ReturnedType returnedType );
234+ QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType );
215235 }
216236
217237 /**
@@ -221,28 +241,28 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
221241
222242 INSTANCE ;
223243
224- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
225- return query .getQueryEnhancer (). rewrite (new DefaultQueryRewriteInformation (sort , returnedType ));
244+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
245+ return query .rewrite (new DefaultQueryRewriteInformation (sort , returnedType ));
226246 }
227247 }
228248
229249 static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
230250
231- private volatile @ Nullable String cachedQueryString ;
251+ private volatile @ Nullable QueryProvider cachedQuery ;
232252
233- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
253+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
234254
235255 if (sort .isSorted ()) {
236256 throw new UnsupportedOperationException ("NoOpQueryCache does not support sorting" );
237257 }
238258
239- String cachedQueryString = this .cachedQueryString ;
240- if (cachedQueryString == null ) {
241- this .cachedQueryString = cachedQueryString = query . getQueryEnhancer ()
259+ QueryProvider cachedQuery = this .cachedQuery ;
260+ if (cachedQuery == null ) {
261+ this .cachedQuery = cachedQuery = query
242262 .rewrite (new DefaultQueryRewriteInformation (sort , returnedType ));
243263 }
244264
245- return cachedQueryString ;
265+ return cachedQuery ;
246266 }
247267 }
248268
@@ -251,22 +271,22 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
251271 */
252272 class CachingQuerySortRewriter implements QuerySortRewriter {
253273
254- private final ConcurrentLruCache <CachableQuery , String > queryCache = new ConcurrentLruCache <>(16 ,
274+ private final ConcurrentLruCache <CachableQuery , QueryProvider > queryCache = new ConcurrentLruCache <>(16 ,
255275 AbstractStringBasedJpaQuery .this ::applySorting );
256276
257- private volatile @ Nullable String cachedQueryString ;
277+ private volatile @ Nullable QueryProvider cachedQuery ;
258278
259279 @ Override
260- public String getSorted (StringQuery query , Sort sort , ReturnedType returnedType ) {
280+ public QueryProvider getSorted (EntityQuery query , Sort sort , ReturnedType returnedType ) {
261281
262282 if (sort .isUnsorted ()) {
263283
264- String cachedQueryString = this .cachedQueryString ;
265- if (cachedQueryString == null ) {
266- this .cachedQueryString = cachedQueryString = queryCache .get (new CachableQuery (query , sort , returnedType ));
284+ QueryProvider cachedQuery = this .cachedQuery ;
285+ if (cachedQuery == null ) {
286+ this .cachedQuery = cachedQuery = queryCache .get (new CachableQuery (query , sort , returnedType ));
267287 }
268288
269- return cachedQueryString ;
289+ return cachedQuery ;
270290 }
271291
272292 return queryCache .get (new CachableQuery (query , sort , returnedType ));
@@ -282,20 +302,20 @@ public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType)
282302 */
283303 static class CachableQuery {
284304
285- private final StringQuery query ;
305+ private final EntityQuery query ;
286306 private final String queryString ;
287307 private final Sort sort ;
288308 private final ReturnedType returnedType ;
289309
290- CachableQuery (StringQuery query , Sort sort , ReturnedType returnedType ) {
310+ CachableQuery (EntityQuery query , Sort sort , ReturnedType returnedType ) {
291311
292312 this .query = query ;
293313 this .queryString = query .getQueryString ();
294314 this .sort = sort ;
295315 this .returnedType = returnedType ;
296316 }
297317
298- StringQuery getDeclaredQuery () {
318+ EntityQuery getDeclaredQuery () {
299319 return query ;
300320 }
301321
0 commit comments