17
17
18
18
import static org .springframework .data .jdbc .repository .query .JdbcQueryExecution .*;
19
19
20
+ import java .lang .reflect .Field ;
20
21
import java .sql .ResultSet ;
21
22
import java .util .ArrayList ;
23
+ import java .util .Arrays ;
22
24
import java .util .Collection ;
25
+ import java .util .LinkedHashMap ;
23
26
import java .util .List ;
27
+ import java .util .Map ;
24
28
import java .util .function .Function ;
29
+ import java .util .function .IntFunction ;
25
30
import java .util .function .LongSupplier ;
26
31
import java .util .function .Supplier ;
32
+ import java .util .stream .Collectors ;
27
33
28
34
import org .jspecify .annotations .Nullable ;
29
35
import org .springframework .core .convert .converter .Converter ;
36
+ import org .springframework .data .domain .KeysetScrollPosition ;
37
+ import org .springframework .data .domain .Limit ;
38
+ import org .springframework .data .domain .OffsetScrollPosition ;
30
39
import org .springframework .data .domain .Pageable ;
40
+ import org .springframework .data .domain .ScrollPosition ;
31
41
import org .springframework .data .domain .Slice ;
32
42
import org .springframework .data .domain .SliceImpl ;
33
43
import org .springframework .data .domain .Sort ;
44
+ import org .springframework .data .domain .Window ;
34
45
import org .springframework .data .jdbc .core .JdbcAggregateOperations ;
35
46
import org .springframework .data .jdbc .core .convert .JdbcConverter ;
36
47
import org .springframework .data .relational .core .conversion .RelationalConverter ;
37
48
import org .springframework .data .relational .core .dialect .Dialect ;
38
49
import org .springframework .data .relational .core .mapping .RelationalMappingContext ;
50
+ import org .springframework .data .relational .core .mapping .RelationalPersistentEntity ;
51
+ import org .springframework .data .relational .core .mapping .RelationalPersistentProperty ;
39
52
import org .springframework .data .relational .repository .query .RelationalEntityMetadata ;
40
53
import org .springframework .data .relational .repository .query .RelationalParameterAccessor ;
41
54
import org .springframework .data .relational .repository .query .RelationalParametersParameterAccessor ;
51
64
import org .springframework .jdbc .core .namedparam .NamedParameterJdbcOperations ;
52
65
import org .springframework .jdbc .core .namedparam .SqlParameterSource ;
53
66
import org .springframework .util .Assert ;
67
+ import org .springframework .util .ReflectionUtils ;
54
68
55
69
/**
56
70
* An {@link AbstractJdbcQuery} implementation based on a {@link PartTree}.
61
75
* @author Mikhail Polivakha
62
76
* @author Yunyoung LEE
63
77
* @author Nikita Konev
78
+ * @author Artemij Degtyarev
64
79
* @since 2.0
65
80
*/
66
81
public class PartTreeJdbcQuery extends AbstractJdbcQuery {
@@ -191,6 +206,13 @@ private JdbcQueryExecution<?> getQueryExecution(ResultProcessor processor,
191
206
192
207
JdbcQueryExecution <?> queryExecution = getJdbcQueryExecution (extractor , rowMapper );
193
208
209
+ if (getQueryMethod ().isScrollQuery ()) {
210
+ // noinspection unchecked
211
+ return new ScrollQueryExecution <>((JdbcQueryExecution <Collection <Object >>) queryExecution ,
212
+ accessor .getScrollPosition (), this .tree .getMaxResults (), tree .getSort (), tree .getResultLimit (),
213
+ getQueryMethod ().getEntityInformation ().getTableEntity ());
214
+ }
215
+
194
216
if (getQueryMethod ().isSliceQuery ()) {
195
217
// noinspection unchecked
196
218
return new SliceQueryExecution <>((JdbcQueryExecution <Collection <Object >>) queryExecution , accessor .getPageable ());
@@ -205,7 +227,8 @@ private JdbcQueryExecution<?> getQueryExecution(ResultProcessor processor,
205
227
RelationalEntityMetadata <?> entityMetadata = getQueryMethod ().getEntityInformation ();
206
228
207
229
JdbcCountQueryCreator queryCreator = new JdbcCountQueryCreator (context , tree , converter , dialect ,
208
- entityMetadata , accessor , false , processor .getReturnedType (), getQueryMethod ().lookupLockAnnotation ());
230
+ entityMetadata , accessor , false , processor .getReturnedType (), getQueryMethod ().lookupLockAnnotation (),
231
+ false );
209
232
210
233
ParametrizedQuery countQuery = queryCreator .createQuery (Sort .unsorted ());
211
234
Object count = singleObjectQuery (new SingleColumnRowMapper <>(Number .class )).execute (countQuery .getQuery (),
@@ -227,7 +250,8 @@ ParametrizedQuery createQuery(RelationalParametersParameterAccessor accessor, Re
227
250
RelationalEntityMetadata <?> entityMetadata = getQueryMethod ().getEntityInformation ();
228
251
229
252
JdbcQueryCreator queryCreator = new JdbcQueryCreator (context , tree , converter , dialect , entityMetadata , accessor ,
230
- getQueryMethod ().isSliceQuery (), returnedType , this .getQueryMethod ().lookupLockAnnotation ());
253
+ getQueryMethod ().isSliceQuery (), returnedType , this .getQueryMethod ().lookupLockAnnotation (),
254
+ getQueryMethod ().isScrollQuery ());
231
255
return queryCreator .createQuery (getDynamicSort (accessor ));
232
256
}
233
257
@@ -243,7 +267,7 @@ private List<ParametrizedQuery> createDeleteQueries(RelationalParametersParamete
243
267
private JdbcQueryExecution <?> getJdbcQueryExecution (@ Nullable ResultSetExtractor <Boolean > extractor ,
244
268
Supplier <RowMapper <?>> rowMapper ) {
245
269
246
- if (getQueryMethod ().isPageQuery () || getQueryMethod ().isSliceQuery ()) {
270
+ if (getQueryMethod ().isPageQuery () || getQueryMethod ().isSliceQuery () || getQueryMethod (). isScrollQuery () ) {
247
271
return collectionQuery (rowMapper .get ());
248
272
} else {
249
273
@@ -255,6 +279,97 @@ private JdbcQueryExecution<?> getJdbcQueryExecution(@Nullable ResultSetExtractor
255
279
}
256
280
}
257
281
282
+ /**
283
+ * {@link JdbcQueryExecution} returning a {@link org.springframework.data.domain.Window}
284
+ *
285
+ * @param <T>
286
+ */
287
+ static class ScrollQueryExecution <T > implements JdbcQueryExecution <Window <T >> {
288
+ private final JdbcQueryExecution <? extends Collection <T >> delegate ;
289
+ private final @ Nullable ScrollPosition position ;
290
+ private final @ Nullable Integer maxResults ;
291
+ private final Sort sort ;
292
+ private final Limit limit ;
293
+ private final RelationalPersistentEntity <?> tableEntity ;
294
+
295
+ ScrollQueryExecution (JdbcQueryExecution <? extends Collection <T >> delegate , @ Nullable ScrollPosition position ,
296
+ @ Nullable Integer maxResults , Sort sort , Limit limit , RelationalPersistentEntity <?> tableEntity ) {
297
+ this .delegate = delegate ;
298
+ this .position = position ;
299
+ this .maxResults = maxResults ;
300
+ this .sort = sort ;
301
+ this .limit = limit ;
302
+ this .tableEntity = tableEntity ;
303
+ }
304
+
305
+ @ Override
306
+ public @ Nullable Window <T > execute (String query , SqlParameterSource parameter ) {
307
+ Collection <T > result = delegate .execute (query , parameter );
308
+
309
+ List <T > resultList = result instanceof List ? (List <T >) result : new ArrayList <>(result );
310
+ IntFunction <? extends ScrollPosition > positionFunction = null ;
311
+ if (position instanceof OffsetScrollPosition )
312
+ positionFunction = ((OffsetScrollPosition ) position ).positionFunction ();
313
+
314
+ if (position instanceof KeysetScrollPosition ) {
315
+ Map <String , Object > keys = ((KeysetScrollPosition ) position ).getKeys ();
316
+ List <String > orders = new ArrayList <>(keys .keySet ());
317
+
318
+ if (orders .isEmpty ())
319
+ orders = sort .get ().map (Sort .Order ::getProperty ).toList ();
320
+
321
+ orders = orders .stream ().map (it -> {
322
+ RelationalPersistentProperty prop = tableEntity .getPersistentProperty (it );
323
+
324
+ if (prop == null )
325
+ return it ;
326
+
327
+ return prop .getName ();
328
+ }).toList ();
329
+
330
+ keys = extractKeys (resultList , orders );
331
+
332
+ Map <String , Object > finalKeys = keys ;
333
+ positionFunction = (ignoredI ) -> ScrollPosition .of (finalKeys , ((KeysetScrollPosition ) position ).getDirection ());
334
+ }
335
+
336
+ if (positionFunction == null )
337
+ throw new UnsupportedOperationException ("Not supported scroll type." );
338
+
339
+ boolean hasNext ;
340
+ if (maxResults != null )
341
+ hasNext = resultList .size () >= maxResults ;
342
+ else if (limit .isLimited ())
343
+ hasNext = resultList .size () >= limit .max ();
344
+ else
345
+ hasNext = !resultList .isEmpty ();
346
+
347
+ return Window .from (resultList , positionFunction , hasNext );
348
+ }
349
+
350
+ private Map <String , Object > extractKeys (List <T > resultList , List <String > orders ) {
351
+ if (resultList .isEmpty ())
352
+ return Map .of ();
353
+
354
+ T last = resultList .get (resultList .size () - 1 );
355
+
356
+ Field [] fields = last .getClass ().getDeclaredFields ();
357
+
358
+ // noinspection DataFlowIssue
359
+ return Arrays .stream (fields ).filter (it -> {
360
+ String name = it .getName ();
361
+
362
+ RelationalPersistentProperty prop = tableEntity .getPersistentProperty (name );
363
+ if (prop != null )
364
+ name = prop .getName ();
365
+
366
+ String finalName = name ;
367
+ return orders .stream ().anyMatch (order -> order .equalsIgnoreCase (finalName ));
368
+ }).peek (ReflectionUtils ::makeAccessible ).collect (Collectors .toMap (Field ::getName ,
369
+ it -> ReflectionUtils .getField (it , last ), (e1 , e2 ) -> e1 , LinkedHashMap ::new ));
370
+ }
371
+ }
372
+
258
373
/**
259
374
* {@link JdbcQueryExecution} returning a {@link org.springframework.data.domain.Slice}.
260
375
*
@@ -327,8 +442,7 @@ class CachedRowMapperFactory implements Supplier<RowMapper<?>> {
327
442
private final Lazy <RowMapper <?>> rowMapper ;
328
443
private final Function <ResultProcessor , RowMapper <?>> rowMapperFunction ;
329
444
330
- public CachedRowMapperFactory (PartTree tree ,
331
- RowMapperFactory rowMapperFactory , RelationalConverter converter ,
445
+ public CachedRowMapperFactory (PartTree tree , RowMapperFactory rowMapperFactory , RelationalConverter converter ,
332
446
ResultProcessor defaultResultProcessor ) {
333
447
334
448
this .rowMapperFunction = processor -> {
@@ -338,8 +452,8 @@ public CachedRowMapperFactory(PartTree tree,
338
452
}
339
453
Converter <Object , Object > resultProcessingConverter = new ResultProcessingConverter (processor ,
340
454
converter .getMappingContext (), converter .getEntityInstantiators ());
341
- return new ConvertingRowMapper (
342
- rowMapperFactory . create ( processor . getReturnedType (). getDomainType ()), resultProcessingConverter );
455
+ return new ConvertingRowMapper (rowMapperFactory . create ( processor . getReturnedType (). getDomainType ()),
456
+ resultProcessingConverter );
343
457
};
344
458
345
459
this .rowMapper = Lazy .of (() -> this .rowMapperFunction .apply (defaultResultProcessor ));
0 commit comments