16
16
package org .springframework .data .jpa .repository .support ;
17
17
18
18
import jakarta .persistence .EntityManager ;
19
- import jakarta .persistence .Query ;
20
19
21
20
import java .util .ArrayList ;
22
21
import java .util .Collection ;
23
22
import java .util .Collections ;
23
+ import java .util .LinkedHashSet ;
24
24
import java .util .List ;
25
25
import java .util .function .BiFunction ;
26
26
import java .util .function .Function ;
27
27
import java .util .stream .Stream ;
28
28
29
29
import org .springframework .dao .IncorrectResultSizeDataAccessException ;
30
+ import org .springframework .data .domain .KeysetScrollPosition ;
30
31
import org .springframework .data .domain .Page ;
31
32
import org .springframework .data .domain .PageImpl ;
32
33
import org .springframework .data .domain .Pageable ;
36
37
import org .springframework .data .jpa .repository .query .ScrollDelegate ;
37
38
import org .springframework .data .projection .ProjectionFactory ;
38
39
import org .springframework .data .repository .query .FluentQuery .FetchableFluentQuery ;
40
+ import org .springframework .data .repository .query .ReturnedType ;
39
41
import org .springframework .data .support .PageableExecutionUtils ;
42
+ import org .springframework .lang .Nullable ;
40
43
import org .springframework .util .Assert ;
41
44
45
+ import com .querydsl .core .types .EntityPath ;
46
+ import com .querydsl .core .types .Expression ;
47
+ import com .querydsl .core .types .ExpressionBase ;
42
48
import com .querydsl .core .types .Predicate ;
49
+ import com .querydsl .core .types .Visitor ;
50
+ import com .querydsl .core .types .dsl .PathBuilder ;
51
+ import com .querydsl .jpa .JPQLSerializer ;
43
52
import com .querydsl .jpa .impl .AbstractJPAQuery ;
44
53
45
54
/**
57
66
*/
58
67
class FetchableFluentQueryByPredicate <S , R > extends FluentQuerySupport <S , R > implements FetchableFluentQuery <R > {
59
68
69
+ private final EntityPath <?> entityPath ;
70
+ private final JpaEntityInformation <S , ?> entityInformation ;
71
+ private final ScrollQueryFactory <AbstractJPAQuery <?, ?>> scrollQueryFactory ;
60
72
private final Predicate predicate ;
61
73
private final Function <Sort , AbstractJPAQuery <?, ?>> finder ;
62
74
63
- private final PredicateScrollDelegate <S > scroll ;
64
75
private final BiFunction <Sort , Pageable , AbstractJPAQuery <?, ?>> pagedFinder ;
65
76
private final Function <Predicate , Long > countOperation ;
66
77
private final Function <Predicate , Boolean > existsOperation ;
67
78
private final EntityManager entityManager ;
68
79
69
- FetchableFluentQueryByPredicate (Predicate predicate , Class <S > entityType ,
70
- Function <Sort , AbstractJPAQuery <?, ?>> finder , PredicateScrollDelegate <S > scroll ,
80
+ FetchableFluentQueryByPredicate (EntityPath <?> entityPath , Predicate predicate ,
81
+ JpaEntityInformation <S , ?> entityInformation , Function <Sort , AbstractJPAQuery <?, ?>> finder ,
82
+ ScrollQueryFactory <AbstractJPAQuery <?, ?>> scrollQueryFactory ,
71
83
BiFunction <Sort , Pageable , AbstractJPAQuery <?, ?>> pagedFinder , Function <Predicate , Long > countOperation ,
72
84
Function <Predicate , Boolean > existsOperation , EntityManager entityManager , ProjectionFactory projectionFactory ) {
73
- this (predicate , entityType , (Class <R >) entityType , Sort .unsorted (), 0 , Collections .emptySet (), finder , scroll ,
85
+ this (entityPath , predicate , entityInformation , (Class <R >) entityInformation .getJavaType (), Sort .unsorted (), 0 ,
86
+ Collections .emptySet (), finder , scrollQueryFactory ,
74
87
pagedFinder , countOperation , existsOperation , entityManager , projectionFactory );
75
88
}
76
89
77
- private FetchableFluentQueryByPredicate (Predicate predicate , Class <S > entityType , Class <R > resultType , Sort sort ,
78
- int limit , Collection <String > properties , Function <Sort , AbstractJPAQuery <?, ?>> finder ,
79
- PredicateScrollDelegate <S > scroll , BiFunction <Sort , Pageable , AbstractJPAQuery <?, ?>> pagedFinder ,
90
+ private FetchableFluentQueryByPredicate (EntityPath <?> entityPath , Predicate predicate ,
91
+ JpaEntityInformation <S , ?> entityInformation , Class <R > resultType , Sort sort , int limit ,
92
+ Collection <String > properties , Function <Sort , AbstractJPAQuery <?, ?>> finder ,
93
+ ScrollQueryFactory <AbstractJPAQuery <?, ?>> scrollQueryFactory ,
94
+ BiFunction <Sort , Pageable , AbstractJPAQuery <?, ?>> pagedFinder ,
80
95
Function <Predicate , Long > countOperation , Function <Predicate , Boolean > existsOperation ,
81
96
EntityManager entityManager , ProjectionFactory projectionFactory ) {
82
97
83
- super (resultType , sort , limit , properties , entityType , projectionFactory );
98
+ super (resultType , sort , limit , properties , entityInformation .getJavaType (), projectionFactory );
99
+ this .entityInformation = entityInformation ;
100
+ this .entityPath = entityPath ;
84
101
this .predicate = predicate ;
85
102
this .finder = finder ;
86
- this .scroll = scroll ;
103
+ this .scrollQueryFactory = scrollQueryFactory ;
87
104
this .pagedFinder = pagedFinder ;
88
105
this .countOperation = countOperation ;
89
106
this .existsOperation = existsOperation ;
@@ -95,37 +112,37 @@ public FetchableFluentQuery<R> sortBy(Sort sort) {
95
112
96
113
Assert .notNull (sort , "Sort must not be null" );
97
114
98
- return new FetchableFluentQueryByPredicate <>(predicate , entityType , resultType , this .sort .and (sort ), limit ,
99
- properties , finder , scroll , pagedFinder , countOperation , existsOperation , entityManager , projectionFactory );
115
+ return new FetchableFluentQueryByPredicate <>(entityPath , predicate , entityInformation , resultType ,
116
+ this .sort .and (sort ), limit , properties , finder , scrollQueryFactory , pagedFinder , countOperation ,
117
+ existsOperation , entityManager , projectionFactory );
100
118
}
101
119
102
120
@ Override
103
121
public FetchableFluentQuery <R > limit (int limit ) {
104
122
105
123
Assert .isTrue (limit >= 0 , "Limit must not be negative" );
106
124
107
- return new FetchableFluentQueryByPredicate <>(predicate , entityType , resultType , sort , limit , properties , finder ,
108
- scroll , pagedFinder , countOperation , existsOperation , entityManager , projectionFactory );
125
+ return new FetchableFluentQueryByPredicate <>(entityPath , predicate , entityInformation , resultType , sort , limit ,
126
+ properties , finder , scrollQueryFactory , pagedFinder , countOperation , existsOperation , entityManager ,
127
+ projectionFactory );
109
128
}
110
129
111
130
@ Override
112
131
public <NR > FetchableFluentQuery <NR > as (Class <NR > resultType ) {
113
132
114
133
Assert .notNull (resultType , "Projection target type must not be null" );
115
134
116
- if (!resultType .isInterface ()) {
117
- throw new UnsupportedOperationException ("Class-based DTOs are not yet supported." );
118
- }
119
-
120
- return new FetchableFluentQueryByPredicate <>(predicate , entityType , resultType , sort , limit , properties , finder ,
121
- scroll , pagedFinder , countOperation , existsOperation , entityManager , projectionFactory );
135
+ return new FetchableFluentQueryByPredicate <>(entityPath , predicate , entityInformation , resultType , sort , limit ,
136
+ properties , finder , scrollQueryFactory , pagedFinder , countOperation , existsOperation , entityManager ,
137
+ projectionFactory );
122
138
}
123
139
124
140
@ Override
125
141
public FetchableFluentQuery <R > project (Collection <String > properties ) {
126
142
127
- return new FetchableFluentQueryByPredicate <>(predicate , entityType , resultType , sort , limit ,
128
- mergeProperties (properties ), finder , scroll , pagedFinder , countOperation , existsOperation , entityManager ,
143
+ return new FetchableFluentQueryByPredicate <>(entityPath , predicate , entityInformation , resultType , sort , limit ,
144
+ mergeProperties (properties ), finder , scrollQueryFactory , pagedFinder , countOperation , existsOperation ,
145
+ entityManager ,
129
146
projectionFactory );
130
147
}
131
148
@@ -163,7 +180,8 @@ public Window<R> scroll(ScrollPosition scrollPosition) {
163
180
164
181
Assert .notNull (scrollPosition , "ScrollPosition must not be null" );
165
182
166
- return scroll .scroll (sort , limit , scrollPosition ).map (getConversionFunction ());
183
+ return new PredicateScrollDelegate <>(scrollQueryFactory , entityInformation )
184
+ .scroll (returnedType , sort , limit , scrollPosition ).map (getConversionFunction ());
167
185
}
168
186
169
187
@ Override
@@ -192,6 +210,35 @@ public boolean exists() {
192
210
private AbstractJPAQuery <?, ?> createSortedAndProjectedQuery () {
193
211
194
212
AbstractJPAQuery <?, ?> query = finder .apply (sort );
213
+ applyQuerySettings (this .returnedType , this .limit , query , null );
214
+ return query ;
215
+ }
216
+
217
+ private void applyQuerySettings (ReturnedType returnedType , int limit , AbstractJPAQuery <?, ?> query ,
218
+ @ Nullable ScrollPosition scrollPosition ) {
219
+
220
+ List <String > inputProperties = returnedType .getInputProperties ();
221
+
222
+ if (returnedType .needsCustomConstruction () && !inputProperties .isEmpty ()) {
223
+
224
+ Collection <String > requiredSelection ;
225
+ if (scrollPosition instanceof KeysetScrollPosition && returnedType .getReturnedType ().isInterface ()) {
226
+ requiredSelection = new LinkedHashSet <>(inputProperties );
227
+ sort .forEach (it -> requiredSelection .add (it .getProperty ()));
228
+ entityInformation .getIdAttributeNames ().forEach (requiredSelection ::add );
229
+ } else {
230
+ requiredSelection = inputProperties ;
231
+ }
232
+
233
+ PathBuilder <?> builder = new PathBuilder <>(entityPath .getType (), entityPath .getMetadata ());
234
+ Expression <?>[] projection = requiredSelection .stream ().map (builder ::get ).toArray (Expression []::new );
235
+
236
+ if (returnedType .getReturnedType ().isInterface ()) {
237
+ query .select (new JakartaTuple (projection ));
238
+ } else {
239
+ query .select (new DtoProjection (returnedType .getReturnedType (), projection ));
240
+ }
241
+ }
195
242
196
243
if (!properties .isEmpty ()) {
197
244
query .setHint (EntityGraphFactory .HINT , EntityGraphFactory .create (entityManager , entityType , properties ));
@@ -200,8 +247,6 @@ public boolean exists() {
200
247
if (limit != 0 ) {
201
248
query .limit (limit );
202
249
}
203
-
204
- return query ;
205
250
}
206
251
207
252
private Page <R > readPage (Pageable pageable ) {
@@ -233,23 +278,57 @@ private Function<Object, R> getConversionFunction() {
233
278
return getConversionFunction (entityType , resultType );
234
279
}
235
280
236
- static class PredicateScrollDelegate <T > extends ScrollDelegate <T > {
281
+ class PredicateScrollDelegate <T > extends ScrollDelegate <T > {
237
282
238
- private final ScrollQueryFactory scrollFunction ;
283
+ private final ScrollQueryFactory < AbstractJPAQuery <?, ?>> scrollFunction ;
239
284
240
- PredicateScrollDelegate (ScrollQueryFactory scrollQueryFactory , JpaEntityInformation <T , ?> entity ) {
285
+ PredicateScrollDelegate (ScrollQueryFactory <AbstractJPAQuery <?, ?>> scrollQueryFactory ,
286
+ JpaEntityInformation <T , ?> entity ) {
241
287
super (entity );
242
288
this .scrollFunction = scrollQueryFactory ;
243
289
}
244
290
245
- public Window <T > scroll (Sort sort , int limit , ScrollPosition scrollPosition ) {
291
+ public Window <T > scroll (ReturnedType returnedType , Sort sort , int limit , ScrollPosition scrollPosition ) {
246
292
247
- Query query = scrollFunction .createQuery (sort , scrollPosition );
248
- if ( limit > 0 ) {
249
- query = query . setMaxResults ( limit );
250
- }
251
- return scroll (query , sort , scrollPosition );
293
+ AbstractJPAQuery <?, ?> query = scrollFunction .createQuery (returnedType , sort , scrollPosition );
294
+
295
+ applyQuerySettings ( returnedType , limit , query , scrollPosition );
296
+
297
+ return scroll (query . createQuery () , sort , scrollPosition );
252
298
}
253
299
}
254
300
301
+ private static class DtoProjection extends ExpressionBase <Object > {
302
+
303
+ private final Expression <?>[] projection ;
304
+
305
+ public DtoProjection (Class <?> resultType , Expression <?>[] projection ) {
306
+ super (resultType );
307
+ this .projection = projection ;
308
+ }
309
+
310
+ @ SuppressWarnings ("unchecked" )
311
+ @ Override
312
+ public <R , C > R accept (Visitor <R , C > v , @ Nullable C context ) {
313
+
314
+ if (v instanceof JPQLSerializer s ) {
315
+
316
+ s .append ("new " ).append (getType ().getName ()).append ("(" );
317
+ boolean first = true ;
318
+ for (Expression <?> expression : projection ) {
319
+ if (first ) {
320
+ first = false ;
321
+ } else {
322
+ s .append (", " );
323
+ }
324
+
325
+ expression .accept (v , context );
326
+ }
327
+
328
+ s .append (")" );
329
+ }
330
+
331
+ return (R ) this ;
332
+ }
333
+ }
255
334
}
0 commit comments