1616package org .springframework .data .jpa .repository .support ;
1717
1818import jakarta .persistence .EntityManager ;
19- import jakarta .persistence .Query ;
2019
2120import java .util .ArrayList ;
2221import java .util .Collection ;
2322import java .util .Collections ;
23+ import java .util .LinkedHashSet ;
2424import java .util .List ;
2525import java .util .function .BiFunction ;
2626import java .util .function .Function ;
2727import java .util .stream .Stream ;
2828
2929import org .springframework .dao .IncorrectResultSizeDataAccessException ;
30+ import org .springframework .data .domain .KeysetScrollPosition ;
3031import org .springframework .data .domain .Page ;
3132import org .springframework .data .domain .PageImpl ;
3233import org .springframework .data .domain .Pageable ;
3637import org .springframework .data .jpa .repository .query .ScrollDelegate ;
3738import org .springframework .data .projection .ProjectionFactory ;
3839import org .springframework .data .repository .query .FluentQuery .FetchableFluentQuery ;
40+ import org .springframework .data .repository .query .ReturnedType ;
3941import org .springframework .data .support .PageableExecutionUtils ;
42+ import org .springframework .lang .Nullable ;
4043import org .springframework .util .Assert ;
4144
45+ import com .querydsl .core .types .EntityPath ;
46+ import com .querydsl .core .types .Expression ;
47+ import com .querydsl .core .types .ExpressionBase ;
4248import 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 ;
4352import com .querydsl .jpa .impl .AbstractJPAQuery ;
4453
4554/**
5766 */
5867class FetchableFluentQueryByPredicate <S , R > extends FluentQuerySupport <S , R > implements FetchableFluentQuery <R > {
5968
69+ private final EntityPath <?> entityPath ;
70+ private final JpaEntityInformation <S , ?> entityInformation ;
71+ private final ScrollQueryFactory <AbstractJPAQuery <?, ?>> scrollQueryFactory ;
6072 private final Predicate predicate ;
6173 private final Function <Sort , AbstractJPAQuery <?, ?>> finder ;
6274
63- private final PredicateScrollDelegate <S > scroll ;
6475 private final BiFunction <Sort , Pageable , AbstractJPAQuery <?, ?>> pagedFinder ;
6576 private final Function <Predicate , Long > countOperation ;
6677 private final Function <Predicate , Boolean > existsOperation ;
6778 private final EntityManager entityManager ;
6879
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 ,
7183 BiFunction <Sort , Pageable , AbstractJPAQuery <?, ?>> pagedFinder , Function <Predicate , Long > countOperation ,
7284 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 ,
7487 pagedFinder , countOperation , existsOperation , entityManager , projectionFactory );
7588 }
7689
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 ,
8095 Function <Predicate , Long > countOperation , Function <Predicate , Boolean > existsOperation ,
8196 EntityManager entityManager , ProjectionFactory projectionFactory ) {
8297
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 ;
84101 this .predicate = predicate ;
85102 this .finder = finder ;
86- this .scroll = scroll ;
103+ this .scrollQueryFactory = scrollQueryFactory ;
87104 this .pagedFinder = pagedFinder ;
88105 this .countOperation = countOperation ;
89106 this .existsOperation = existsOperation ;
@@ -95,37 +112,37 @@ public FetchableFluentQuery<R> sortBy(Sort sort) {
95112
96113 Assert .notNull (sort , "Sort must not be null" );
97114
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 );
100118 }
101119
102120 @ Override
103121 public FetchableFluentQuery <R > limit (int limit ) {
104122
105123 Assert .isTrue (limit >= 0 , "Limit must not be negative" );
106124
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 );
109128 }
110129
111130 @ Override
112131 public <NR > FetchableFluentQuery <NR > as (Class <NR > resultType ) {
113132
114133 Assert .notNull (resultType , "Projection target type must not be null" );
115134
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 );
122138 }
123139
124140 @ Override
125141 public FetchableFluentQuery <R > project (Collection <String > properties ) {
126142
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 ,
129146 projectionFactory );
130147 }
131148
@@ -163,7 +180,8 @@ public Window<R> scroll(ScrollPosition scrollPosition) {
163180
164181 Assert .notNull (scrollPosition , "ScrollPosition must not be null" );
165182
166- return scroll .scroll (sort , limit , scrollPosition ).map (getConversionFunction ());
183+ return new PredicateScrollDelegate <>(scrollQueryFactory , entityInformation )
184+ .scroll (returnedType , sort , limit , scrollPosition ).map (getConversionFunction ());
167185 }
168186
169187 @ Override
@@ -192,6 +210,35 @@ public boolean exists() {
192210 private AbstractJPAQuery <?, ?> createSortedAndProjectedQuery () {
193211
194212 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+ }
195242
196243 if (!properties .isEmpty ()) {
197244 query .setHint (EntityGraphFactory .HINT , EntityGraphFactory .create (entityManager , entityType , properties ));
@@ -200,8 +247,6 @@ public boolean exists() {
200247 if (limit != 0 ) {
201248 query .limit (limit );
202249 }
203-
204- return query ;
205250 }
206251
207252 private Page <R > readPage (Pageable pageable ) {
@@ -233,23 +278,57 @@ private Function<Object, R> getConversionFunction() {
233278 return getConversionFunction (entityType , resultType );
234279 }
235280
236- static class PredicateScrollDelegate <T > extends ScrollDelegate <T > {
281+ class PredicateScrollDelegate <T > extends ScrollDelegate <T > {
237282
238- private final ScrollQueryFactory scrollFunction ;
283+ private final ScrollQueryFactory < AbstractJPAQuery <?, ?>> scrollFunction ;
239284
240- PredicateScrollDelegate (ScrollQueryFactory scrollQueryFactory , JpaEntityInformation <T , ?> entity ) {
285+ PredicateScrollDelegate (ScrollQueryFactory <AbstractJPAQuery <?, ?>> scrollQueryFactory ,
286+ JpaEntityInformation <T , ?> entity ) {
241287 super (entity );
242288 this .scrollFunction = scrollQueryFactory ;
243289 }
244290
245- public Window <T > scroll (Sort sort , int limit , ScrollPosition scrollPosition ) {
291+ public Window <T > scroll (ReturnedType returnedType , Sort sort , int limit , ScrollPosition scrollPosition ) {
246292
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 );
252298 }
253299 }
254300
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+ }
255334}
0 commit comments