17
17
18
18
import jakarta .persistence .EntityManager ;
19
19
import jakarta .persistence .Query ;
20
+ import jakarta .persistence .QueryHint ;
20
21
21
22
import java .util .List ;
22
23
import java .util .Optional ;
23
24
import java .util .function .LongSupplier ;
24
25
import java .util .regex .Pattern ;
25
26
27
+ import org .springframework .core .annotation .MergedAnnotation ;
26
28
import org .springframework .data .domain .SliceImpl ;
29
+ import org .springframework .data .jpa .repository .QueryHints ;
27
30
import org .springframework .data .jpa .repository .query .DeclaredQuery ;
28
31
import org .springframework .data .jpa .repository .query .ParameterBinding ;
29
32
import org .springframework .data .repository .aot .generate .AotRepositoryMethodGenerationContext ;
37
40
38
41
/**
39
42
* @author Christoph Strobl
40
- * @since 2025/01
43
+ * @author Mark Paluch
44
+ * @since 4.0
41
45
*/
42
- public class JpaCodeBlocks {
46
+ class JpaCodeBlocks {
43
47
44
48
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern .compile ("\\ ?(\\ d+)" );
45
49
46
- static QueryBlockBuilder queryBlockBuilder (AotRepositoryMethodGenerationContext context ) {
50
+ public static QueryBlockBuilder queryBuilder (AotRepositoryMethodGenerationContext context ) {
47
51
return new QueryBlockBuilder (context );
48
52
}
49
53
50
- static QueryExecutionBlockBuilder queryExecutionBlockBuilder (AotRepositoryMethodGenerationContext context ) {
54
+ static QueryExecutionBlockBuilder executionBuilder (AotRepositoryMethodGenerationContext context ) {
51
55
return new QueryExecutionBlockBuilder (context );
52
56
}
53
57
54
- static class QueryExecutionBlockBuilder {
55
-
56
- AotRepositoryMethodGenerationContext context ;
57
- private String queryVariableName = "query" ;
58
-
59
- public QueryExecutionBlockBuilder (AotRepositoryMethodGenerationContext context ) {
60
- this .context = context ;
61
- }
62
-
63
- QueryExecutionBlockBuilder referencing (String queryVariableName ) {
64
-
65
- this .queryVariableName = queryVariableName ;
66
- return this ;
67
- }
68
-
69
- CodeBlock build () {
70
-
71
- Builder builder = CodeBlock .builder ();
72
-
73
- boolean isProjecting = context .getActualReturnType () != null
74
- && !ObjectUtils .nullSafeEquals (TypeName .get (context .getRepositoryInformation ().getDomainType ()),
75
- context .getActualReturnType ());
76
- Object actualReturnType = isProjecting ? context .getActualReturnType ()
77
- : context .getRepositoryInformation ().getDomainType ();
78
-
79
- builder .add ("\n " );
80
-
81
- if (context .isDeleteMethod ()) {
82
-
83
- builder .addStatement ("$T<$T> resultList = $L.getResultList()" , List .class , actualReturnType , queryVariableName );
84
- builder .addStatement ("resultList.forEach($L::remove)" , context .fieldNameOf (EntityManager .class ));
85
- if (context .returnsSingleValue ()) {
86
- if (ClassUtils .isAssignable (Number .class , context .getMethod ().getReturnType ())) {
87
- builder .addStatement ("return $T.valueOf(resultList.size())" , context .getMethod ().getReturnType ());
88
- } else {
89
- builder .addStatement ("return resultList.isEmpty() ? null : resultList.iterator().next()" );
90
- }
91
- } else {
92
- builder .addStatement ("return resultList" );
93
- }
94
- } else if (context .isExistsMethod ()) {
95
- builder .addStatement ("return !$L.getResultList().isEmpty()" , queryVariableName );
96
- } else {
97
-
98
- if (context .returnsSingleValue ()) {
99
- if (context .returnsOptionalValue ()) {
100
- builder .addStatement ("return $T.ofNullable(($T) $L.getSingleResultOrNull())" , Optional .class ,
101
- actualReturnType , queryVariableName );
102
- } else {
103
- builder .addStatement ("return ($T) $L.getSingleResultOrNull()" , context .getReturnType (), queryVariableName );
104
- }
105
- } else if (context .returnsPage ()) {
106
- builder .addStatement ("return $T.getPage(($T<$T>) $L.getResultList(), $L, countAll)" ,
107
- PageableExecutionUtils .class , List .class , actualReturnType , queryVariableName ,
108
- context .getPageableParameterName ());
109
- } else if (context .returnsSlice ()) {
110
- builder .addStatement ("$T<$T> resultList = $L.getResultList()" , List .class , actualReturnType ,
111
- queryVariableName );
112
- builder .addStatement ("boolean hasNext = $L.isPaged() && resultList.size() > $L.getPageSize()" ,
113
- context .getPageableParameterName (), context .getPageableParameterName ());
114
- builder .addStatement (
115
- "return new $T<>(hasNext ? resultList.subList(0, $L.getPageSize()) : resultList, $L, hasNext)" ,
116
- SliceImpl .class , context .getPageableParameterName (), context .getPageableParameterName ());
117
- } else {
118
- builder .addStatement ("return ($T) query.getResultList()" , context .getReturnType ());
119
- }
120
- }
121
-
122
- return builder .build ();
123
-
124
- }
125
- }
126
-
127
58
/**
128
59
* Builder for the actual query code block.
129
60
*/
@@ -132,23 +63,35 @@ static class QueryBlockBuilder {
132
63
private final AotRepositoryMethodGenerationContext context ;
133
64
private String queryVariableName = "query" ;
134
65
private AotQueries queries ;
66
+ private MergedAnnotation <QueryHints > queryHints = MergedAnnotation .missing ();
135
67
136
- public QueryBlockBuilder (AotRepositoryMethodGenerationContext context ) {
68
+ private QueryBlockBuilder (AotRepositoryMethodGenerationContext context ) {
137
69
this .context = context ;
138
70
}
139
71
140
- QueryBlockBuilder usingQueryVariableName (String queryVariableName ) {
72
+ public QueryBlockBuilder usingQueryVariableName (String queryVariableName ) {
141
73
142
74
this .queryVariableName = queryVariableName ;
143
75
return this ;
144
76
}
145
77
146
- QueryBlockBuilder filter (AotQueries query ) {
78
+ public QueryBlockBuilder filter (AotQueries query ) {
147
79
this .queries = query ;
148
80
return this ;
149
81
}
150
82
151
- CodeBlock build () {
83
+ public QueryBlockBuilder queryHints (MergedAnnotation <QueryHints > queryHints ) {
84
+
85
+ this .queryHints = queryHints ;
86
+ return this ;
87
+ }
88
+
89
+ /**
90
+ * Build the query block.
91
+ *
92
+ * @return
93
+ */
94
+ public CodeBlock build () {
152
95
153
96
boolean isProjecting = context .getActualReturnType () != null
154
97
&& !ObjectUtils .nullSafeEquals (TypeName .get (context .getRepositoryInformation ().getDomainType ()),
@@ -172,8 +115,7 @@ CodeBlock build() {
172
115
countQuyerVariableName = "count%s" .formatted (StringUtils .capitalize (queryVariableName ));
173
116
174
117
StringAotQuery countQuery = (StringAotQuery ) queries .count ();
175
- builder .addStatement ("$T $L = $S" , String .class , countQueryStringNameVariableName ,
176
- countQuery .getQueryString ());
118
+ builder .addStatement ("$T $L = $S" , String .class , countQueryStringNameVariableName , countQuery .getQueryString ());
177
119
}
178
120
179
121
// sorting
@@ -185,17 +127,21 @@ CodeBlock build() {
185
127
}
186
128
187
129
if (StringUtils .hasText (sortParameterName )) {
188
- applySorting (builder , sortParameterName , queryStringNameVariableName , actualReturnType );
130
+ builder . add ( applySorting (sortParameterName , queryStringNameVariableName , actualReturnType ) );
189
131
}
190
132
191
- addQueryBlock ( builder , queryVariableName , queryStringNameVariableName , queries .result ());
133
+ builder . add ( createQuery ( queryVariableName , queryStringNameVariableName , queries .result (), queryHints ));
192
134
193
- applyLimits (builder );
135
+ builder . add ( applyLimits () );
194
136
195
137
if (StringUtils .hasText (countQueryStringNameVariableName )) {
196
138
197
139
builder .beginControlFlow ("$T $L = () ->" , LongSupplier .class , "countAll" );
198
- addQueryBlock (builder , countQuyerVariableName , countQueryStringNameVariableName , queries .count ());
140
+
141
+ boolean queryHints = this .queryHints .isPresent () && this .queryHints .getBoolean ("forCounting" );
142
+
143
+ builder .add (createQuery (countQuyerVariableName , countQueryStringNameVariableName , queries .count (),
144
+ queryHints ? this .queryHints : MergedAnnotation .missing ()));
199
145
builder .addStatement ("return ($T) $L.getSingleResult()" , Long .class , countQuyerVariableName );
200
146
201
147
// end control flow does not work well with lambdas
@@ -206,8 +152,9 @@ CodeBlock build() {
206
152
return builder .build ();
207
153
}
208
154
209
- private void applySorting (Builder builder , String sort , String queryString , Object actualReturnType ) {
155
+ private CodeBlock applySorting (String sort , String queryString , Object actualReturnType ) {
210
156
157
+ Builder builder = CodeBlock .builder ();
211
158
builder .beginControlFlow ("if ($L.isSorted())" , sort );
212
159
213
160
if (queries .isNative ()) {
@@ -221,14 +168,18 @@ private void applySorting(Builder builder, String sort, String queryString, Obje
221
168
builder .addStatement ("$L = rewriteQuery(declaredQuery, $L, $T.class)" , queryString , sort , actualReturnType );
222
169
223
170
builder .endControlFlow ();
171
+
172
+ return builder .build ();
224
173
}
225
174
226
- private void applyLimits (Builder builder ) {
175
+ private CodeBlock applyLimits () {
176
+
177
+ Builder builder = CodeBlock .builder ();
227
178
228
179
if (context .isExistsMethod ()) {
229
180
builder .addStatement ("$L.setMaxResults(1)" , queryVariableName );
230
181
231
- return ;
182
+ return builder . build () ;
232
183
}
233
184
234
185
String limit = context .getLimitParameterName ();
@@ -254,15 +205,24 @@ private void applyLimits(Builder builder) {
254
205
}
255
206
builder .endControlFlow ();
256
207
}
208
+
209
+ return builder .build ();
257
210
}
258
211
259
- private void addQueryBlock (Builder builder , String queryVariableName , String queryStringNameVariableName ,
260
- AotQuery query ) {
212
+ private CodeBlock createQuery (String queryVariableName , String queryStringNameVariableName , AotQuery query ,
213
+ MergedAnnotation <QueryHints > queryHints ) {
214
+
215
+ Builder builder = CodeBlock .builder ();
261
216
262
217
builder .addStatement ("$T $L = this.$L.$L($L)" , Query .class , queryVariableName ,
263
218
context .fieldNameOf (EntityManager .class ), query .isNative () ? "createNativeQuery" : "createQuery" ,
264
219
queryStringNameVariableName );
265
220
221
+ if (queryHints .isPresent ()) {
222
+ builder .add (applyHints (queryVariableName , queryHints ));
223
+ builder .add ("\n " );
224
+ }
225
+
266
226
for (ParameterBinding binding : query .getParameterBindings ()) {
267
227
268
228
Object prepare = binding .prepare ("s" );
@@ -287,6 +247,97 @@ private void addQueryBlock(Builder builder, String queryVariableName, String que
287
247
}
288
248
}
289
249
}
250
+
251
+ return builder .build ();
290
252
}
253
+
254
+ private CodeBlock applyHints (String queryVariableName , MergedAnnotation <QueryHints > queryHints ) {
255
+
256
+ Builder hintsBuilder = CodeBlock .builder ();
257
+ MergedAnnotation <QueryHint >[] values = queryHints .getAnnotationArray ("value" , QueryHint .class );
258
+
259
+ for (MergedAnnotation <QueryHint > hint : values ) {
260
+ hintsBuilder .addStatement ("$L.setHint($S, $S)" , queryVariableName , hint .getString ("name" ),
261
+ hint .getString ("value" ));
262
+ }
263
+
264
+ return hintsBuilder .build ();
265
+ }
266
+
291
267
}
268
+
269
+ static class QueryExecutionBlockBuilder {
270
+
271
+ private final AotRepositoryMethodGenerationContext context ;
272
+ private String queryVariableName = "query" ;
273
+
274
+ private QueryExecutionBlockBuilder (AotRepositoryMethodGenerationContext context ) {
275
+ this .context = context ;
276
+ }
277
+
278
+ public QueryExecutionBlockBuilder referencing (String queryVariableName ) {
279
+
280
+ this .queryVariableName = queryVariableName ;
281
+ return this ;
282
+ }
283
+
284
+ public CodeBlock build () {
285
+
286
+ Builder builder = CodeBlock .builder ();
287
+
288
+ boolean isProjecting = context .getActualReturnType () != null
289
+ && !ObjectUtils .nullSafeEquals (TypeName .get (context .getRepositoryInformation ().getDomainType ()),
290
+ context .getActualReturnType ());
291
+ Object actualReturnType = isProjecting ? context .getActualReturnType ()
292
+ : context .getRepositoryInformation ().getDomainType ();
293
+ builder .add ("\n " );
294
+
295
+ if (context .isDeleteMethod ()) {
296
+
297
+ builder .addStatement ("$T<$T> resultList = $L.getResultList()" , List .class , actualReturnType , queryVariableName );
298
+ builder .addStatement ("resultList.forEach($L::remove)" , context .fieldNameOf (EntityManager .class ));
299
+ if (context .returnsSingleValue ()) {
300
+ if (ClassUtils .isAssignable (Number .class , context .getMethod ().getReturnType ())) {
301
+ builder .addStatement ("return $T.valueOf(resultList.size())" , context .getMethod ().getReturnType ());
302
+ } else {
303
+ builder .addStatement ("return resultList.isEmpty() ? null : resultList.iterator().next()" );
304
+ }
305
+ } else {
306
+ builder .addStatement ("return resultList" );
307
+ }
308
+ } else if (context .isExistsMethod ()) {
309
+ builder .addStatement ("return !$L.getResultList().isEmpty()" , queryVariableName );
310
+ } else {
311
+
312
+ if (context .returnsSingleValue ()) {
313
+ if (context .returnsOptionalValue ()) {
314
+ builder .addStatement ("return $T.ofNullable(($T) $L.getSingleResultOrNull())" , Optional .class ,
315
+ actualReturnType , queryVariableName );
316
+ } else {
317
+ builder .addStatement ("return ($T) $L.getSingleResultOrNull()" , context .getReturnType (), queryVariableName );
318
+ }
319
+ } else if (context .returnsPage ()) {
320
+ builder .addStatement ("return $T.getPage(($T<$T>) $L.getResultList(), $L, countAll)" ,
321
+ PageableExecutionUtils .class , List .class , actualReturnType , queryVariableName ,
322
+ context .getPageableParameterName ());
323
+ } else if (context .returnsSlice ()) {
324
+ builder .addStatement ("$T<$T> resultList = $L.getResultList()" , List .class , actualReturnType ,
325
+ queryVariableName );
326
+ builder .addStatement ("boolean hasNext = $L.isPaged() && resultList.size() > $L.getPageSize()" ,
327
+ context .getPageableParameterName (), context .getPageableParameterName ());
328
+ builder .addStatement (
329
+ "return new $T<>(hasNext ? resultList.subList(0, $L.getPageSize()) : resultList, $L, hasNext)" ,
330
+ SliceImpl .class , context .getPageableParameterName (), context .getPageableParameterName ());
331
+ } else {
332
+ builder .addStatement ("return ($T) query.getResultList()" , context .getReturnType ());
333
+ }
334
+ }
335
+
336
+ return builder .build ();
337
+
338
+ }
339
+
340
+ }
341
+
342
+
292
343
}
0 commit comments