20
20
import java .sql .ResultSet ;
21
21
import java .sql .SQLException ;
22
22
import java .sql .SQLType ;
23
+ import java .util .Iterator ;
23
24
import java .util .Map ;
24
25
import java .util .Optional ;
26
+ import java .util .function .Function ;
25
27
26
28
import org .apache .commons .logging .Log ;
27
29
import org .apache .commons .logging .LogFactory ;
44
46
import org .springframework .data .mapping .model .SpELContext ;
45
47
import org .springframework .data .mapping .model .SpELExpressionEvaluator ;
46
48
import org .springframework .data .mapping .model .SpELExpressionParameterValueProvider ;
49
+ import org .springframework .data .projection .EntityProjection ;
47
50
import org .springframework .data .relational .core .conversion .MappingRelationalConverter ;
51
+ import org .springframework .data .relational .core .conversion .ObjectPath ;
48
52
import org .springframework .data .relational .core .conversion .RelationalConverter ;
53
+ import org .springframework .data .relational .core .conversion .RowDocumentAccessor ;
49
54
import org .springframework .data .relational .core .mapping .AggregatePath ;
50
55
import org .springframework .data .relational .core .mapping .RelationalMappingContext ;
51
56
import org .springframework .data .relational .core .mapping .RelationalPersistentEntity ;
52
57
import org .springframework .data .relational .core .mapping .RelationalPersistentProperty ;
53
58
import org .springframework .data .relational .core .sql .IdentifierProcessing ;
59
+ import org .springframework .data .relational .domain .RowDocument ;
54
60
import org .springframework .data .util .TypeInformation ;
55
61
import org .springframework .lang .Nullable ;
56
62
import org .springframework .util .Assert ;
@@ -83,18 +89,15 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
83
89
private SpELContext spELContext ;
84
90
85
91
/**
86
- * Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a
87
- * {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
88
- * creation. Use
92
+ * Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
93
+ * no-op type factory} throwing {@link UnsupportedOperationException} on type creation. Use
89
94
* {@link #BasicJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)}
90
95
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
91
96
*
92
97
* @param context must not be {@literal null}.
93
98
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
94
99
*/
95
- public BasicJdbcConverter (
96
- RelationalMappingContext context ,
97
- RelationResolver relationResolver ) {
100
+ public BasicJdbcConverter (RelationalMappingContext context , RelationResolver relationResolver ) {
98
101
99
102
super (context , new JdbcCustomConversions ());
100
103
@@ -115,10 +118,8 @@ public BasicJdbcConverter(
115
118
* @param identifierProcessing must not be {@literal null}
116
119
* @since 2.0
117
120
*/
118
- public BasicJdbcConverter (
119
- RelationalMappingContext context ,
120
- RelationResolver relationResolver , CustomConversions conversions , JdbcTypeFactory typeFactory ,
121
- IdentifierProcessing identifierProcessing ) {
121
+ public BasicJdbcConverter (RelationalMappingContext context , RelationResolver relationResolver ,
122
+ CustomConversions conversions , JdbcTypeFactory typeFactory , IdentifierProcessing identifierProcessing ) {
122
123
123
124
super (context , conversions );
124
125
@@ -300,16 +301,241 @@ private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
300
301
301
302
@ Override
302
303
public <T > T mapRow (RelationalPersistentEntity <T > entity , ResultSet resultSet , Object key ) {
303
- return new ReadingContext <T >(getMappingContext ().getAggregatePath ( entity ),
304
- new ResultSetAccessor ( resultSet ), Identifier .empty (), key ).mapRow ();
304
+ return new ReadingContext <T >(getMappingContext ().getAggregatePath (entity ), new ResultSetAccessor ( resultSet ),
305
+ Identifier .empty (), key ).mapRow ();
305
306
}
306
307
307
-
308
308
@ Override
309
309
public <T > T mapRow (AggregatePath path , ResultSet resultSet , Identifier identifier , Object key ) {
310
310
return new ReadingContext <T >(path , new ResultSetAccessor (resultSet ), identifier , key ).mapRow ();
311
311
}
312
312
313
+ @ Override
314
+ public <R > R projectAndResolve (EntityProjection <R , ?> projection , RowDocument document ) {
315
+
316
+ RelationalPersistentEntity <?> entity = getMappingContext ()
317
+ .getRequiredPersistentEntity (projection .getActualDomainType ());
318
+ ResolvingConversionContext context = new ResolvingConversionContext (newProjectingConversionContext (projection ),
319
+ getMappingContext ().getAggregatePath (entity ), Identifier .empty ());
320
+
321
+ return doReadProjection (context , document , projection );
322
+ }
323
+
324
+ @ SuppressWarnings ("unchecked" )
325
+ @ Override
326
+ public <R > R readAndResolve (Class <R > type , RowDocument source , Identifier identifier ) {
327
+
328
+ RelationalPersistentEntity <R > entity = (RelationalPersistentEntity <R >) getMappingContext ()
329
+ .getRequiredPersistentEntity (type );
330
+ AggregatePath path = getMappingContext ().getAggregatePath (entity );
331
+ Identifier identifierToUse = ResolvingRelationalPropertyValueProvider .potentiallyAppendIdentifier (identifier ,
332
+ entity , it -> source .get (it .getColumnName ().getReference ()));
333
+ ResolvingConversionContext context = new ResolvingConversionContext (getConversionContext (ObjectPath .ROOT ), path ,
334
+ identifierToUse );
335
+
336
+ return readAggregate (context , source , entity .getTypeInformation ());
337
+ }
338
+
339
+ @ Override
340
+ protected RelationalPropertyValueProvider newValueProvider (RowDocumentAccessor documentAccessor ,
341
+ SpELExpressionEvaluator evaluator , ConversionContext context ) {
342
+
343
+ if (context instanceof ResolvingConversionContext rcc ) {
344
+
345
+ AggregatePathValueProvider delegate = (AggregatePathValueProvider ) super .newValueProvider (documentAccessor ,
346
+ evaluator , context );
347
+
348
+ return new ResolvingRelationalPropertyValueProvider (delegate , documentAccessor , rcc , rcc .identifier ());
349
+ }
350
+
351
+ return super .newValueProvider (documentAccessor , evaluator , context );
352
+ }
353
+
354
+ /**
355
+ * {@link RelationalPropertyValueProvider} using a resolving context to lookup relations. This is highly
356
+ * context-sensitive. Note that the identifier is held here because of a chicken and egg problem, while
357
+ * {@link ResolvingConversionContext} hols the {@link AggregatePath}.
358
+ */
359
+ class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValueProvider {
360
+
361
+ private final AggregatePathValueProvider delegate ;
362
+
363
+ private final RowDocumentAccessor accessor ;
364
+
365
+ private final ResolvingConversionContext context ;
366
+
367
+ private final Identifier identifier ;
368
+
369
+ private ResolvingRelationalPropertyValueProvider (AggregatePathValueProvider delegate , RowDocumentAccessor accessor ,
370
+ ResolvingConversionContext context , Identifier identifier ) {
371
+
372
+ AggregatePath path = context .aggregatePath ();
373
+
374
+ this .delegate = delegate ;
375
+ this .accessor = accessor ;
376
+ this .context = context ;
377
+ this .identifier = path .isEntity ()
378
+ ? potentiallyAppendIdentifier (identifier , path .getRequiredLeafEntity (), delegate ::getPropertyValue )
379
+ : identifier ;
380
+ }
381
+
382
+ /**
383
+ * Conditionally append the identifier if the entity has an identifier property.
384
+ */
385
+ static Identifier potentiallyAppendIdentifier (Identifier base , RelationalPersistentEntity <?> entity ,
386
+ Function <RelationalPersistentProperty , Object > getter ) {
387
+
388
+ if (entity .hasIdProperty ()) {
389
+
390
+ RelationalPersistentProperty idProperty = entity .getRequiredIdProperty ();
391
+ Object propertyValue = getter .apply (idProperty );
392
+
393
+ if (propertyValue != null ) {
394
+ return base .withPart (idProperty .getColumnName (), propertyValue , idProperty .getType ());
395
+ }
396
+ }
397
+
398
+ return base ;
399
+ }
400
+
401
+ @ SuppressWarnings ("unchecked" )
402
+ @ Nullable
403
+ @ Override
404
+ public <T > T getPropertyValue (RelationalPersistentProperty property ) {
405
+
406
+ AggregatePath aggregatePath = this .context .aggregatePath ();
407
+
408
+ if (getConversions ().isSimpleType (property .getActualType ())) {
409
+ return (T ) delegate .getValue (aggregatePath );
410
+ }
411
+
412
+ if (property .isEntity ()) {
413
+
414
+ if (property .isCollectionLike () || property .isMap ()) {
415
+
416
+ Identifier identifier1 = this .identifier ;
417
+ if (property .getOwner ().hasIdProperty ()) {
418
+ Object id = this .identifier .get (property .getOwner ().getRequiredIdProperty ().getColumnName ());
419
+
420
+ if (id != null ) {
421
+ identifier1 = Identifier .of (aggregatePath .getTableInfo ().reverseColumnInfo ().name (), id , Object .class );
422
+ }
423
+ }
424
+
425
+ Iterable <Object > allByPath = relationResolver .findAllByPath (identifier1 ,
426
+ aggregatePath .getRequiredPersistentPropertyPath ());
427
+
428
+ if (property .isCollectionLike ()) {
429
+ return (T ) allByPath ;
430
+ }
431
+
432
+ if (property .isMap ()) {
433
+ return (T ) ITERABLE_OF_ENTRY_TO_MAP_CONVERTER .convert (allByPath );
434
+ }
435
+
436
+ Iterator <Object > iterator = allByPath .iterator ();
437
+ if (iterator .hasNext ()) {
438
+ return (T ) iterator .next ();
439
+ }
440
+
441
+ return null ;
442
+ }
443
+
444
+ return hasValue (property ) ? (T ) readAggregate (this .context , accessor , property .getTypeInformation ()) : null ;
445
+ }
446
+
447
+ return (T ) delegate .getValue (aggregatePath );
448
+ }
449
+
450
+ @ Override
451
+ public boolean hasValue (RelationalPersistentProperty property ) {
452
+
453
+ if (property .isCollectionLike () || property .isMap ()) {
454
+ // attempt relation fetch
455
+ return true ;
456
+ }
457
+
458
+ AggregatePath aggregatePath = context .aggregatePath ();
459
+
460
+ if (property .isEntity ()) {
461
+
462
+ RelationalPersistentEntity <?> entity = getMappingContext ().getRequiredPersistentEntity (property );
463
+ if (entity .hasIdProperty ()) {
464
+
465
+ RelationalPersistentProperty referenceId = entity .getRequiredIdProperty ();
466
+ AggregatePath toUse = aggregatePath .append (referenceId );
467
+ return delegate .hasValue (toUse );
468
+ }
469
+
470
+ return delegate .hasValue (aggregatePath .getTableInfo ().reverseColumnInfo ().alias ());
471
+ }
472
+
473
+ return delegate .hasValue (aggregatePath );
474
+ }
475
+
476
+ @ Override
477
+ public RelationalPropertyValueProvider withContext (ConversionContext context ) {
478
+
479
+ return context == this .context ? this
480
+ : new ResolvingRelationalPropertyValueProvider (delegate .withContext (context ), accessor ,
481
+ (ResolvingConversionContext ) context , identifier );
482
+ }
483
+
484
+ }
485
+
486
+ /**
487
+ * Marker object to indicate that the property value provider should resolve relations.
488
+ *
489
+ * @param delegate
490
+ * @param aggregatePath
491
+ * @param identifier
492
+ */
493
+ private record ResolvingConversionContext (ConversionContext delegate , AggregatePath aggregatePath ,
494
+ Identifier identifier ) implements ConversionContext {
495
+
496
+ @ Override
497
+ public <S > S convert (Object source , TypeInformation <? extends S > typeHint ) {
498
+ return delegate .convert (source , typeHint );
499
+ }
500
+
501
+ @ Override
502
+ public <S > S convert (Object source , TypeInformation <? extends S > typeHint , ConversionContext context ) {
503
+ return delegate .convert (source , typeHint , context );
504
+ }
505
+
506
+ @ Override
507
+ public ResolvingConversionContext forProperty (String name ) {
508
+ RelationalPersistentProperty property = aggregatePath .getRequiredLeafEntity ().getRequiredPersistentProperty (name );
509
+ return forProperty (property );
510
+ }
511
+
512
+ @ Override
513
+ public ResolvingConversionContext forProperty (RelationalPersistentProperty property ) {
514
+ ConversionContext nested = delegate .forProperty (property );
515
+ return new ResolvingConversionContext (nested , aggregatePath .append (property ), identifier );
516
+ }
517
+
518
+ @ Override
519
+ public ResolvingConversionContext withPath (ObjectPath currentPath ) {
520
+ return new ResolvingConversionContext (delegate .withPath (currentPath ), aggregatePath , identifier );
521
+ }
522
+
523
+ @ Override
524
+ public ObjectPath getPath () {
525
+ return delegate .getPath ();
526
+ }
527
+
528
+ @ Override
529
+ public CustomConversions getCustomConversions () {
530
+ return delegate .getCustomConversions ();
531
+ }
532
+
533
+ @ Override
534
+ public RelationalConverter getSourceConverter () {
535
+ return delegate .getSourceConverter ();
536
+ }
537
+ }
538
+
313
539
static Object [] requireObjectArray (Object source ) {
314
540
315
541
Assert .isTrue (source .getClass ().isArray (), "Source object is not an array" );
@@ -361,25 +587,23 @@ private class ReadingContext<T> {
361
587
private final ResultSetAccessor accessor ;
362
588
363
589
@ SuppressWarnings ("unchecked" )
364
- private ReadingContext (AggregatePath rootPath , ResultSetAccessor accessor , Identifier identifier ,
365
- Object key ) {
590
+ private ReadingContext (AggregatePath rootPath , ResultSetAccessor accessor , Identifier identifier , Object key ) {
366
591
RelationalPersistentEntity <T > entity = (RelationalPersistentEntity <T >) rootPath .getLeafEntity ();
367
592
368
593
Assert .notNull (entity , "The rootPath must point to an entity" );
369
594
370
595
this .entity = entity ;
371
596
this .rootPath = rootPath ;
372
- this .path = getMappingContext ().getAggregatePath ( this .entity );
597
+ this .path = getMappingContext ().getAggregatePath (this .entity );
373
598
this .identifier = identifier ;
374
599
this .key = key ;
375
600
this .propertyValueProvider = new JdbcPropertyValueProvider (path , accessor );
376
601
this .backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider (path , accessor );
377
602
this .accessor = accessor ;
378
603
}
379
604
380
- private ReadingContext (RelationalPersistentEntity <T > entity , AggregatePath rootPath ,
381
- AggregatePath path , Identifier identifier , Object key ,
382
- JdbcPropertyValueProvider propertyValueProvider ,
605
+ private ReadingContext (RelationalPersistentEntity <T > entity , AggregatePath rootPath , AggregatePath path ,
606
+ Identifier identifier , Object key , JdbcPropertyValueProvider propertyValueProvider ,
383
607
JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider , ResultSetAccessor accessor ) {
384
608
385
609
this .entity = entity ;
@@ -396,8 +620,8 @@ private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
396
620
397
621
return new ReadingContext <>(
398
622
(RelationalPersistentEntity <S >) getMappingContext ().getRequiredPersistentEntity (property .getActualType ()),
399
- rootPath .append (property ), path .append (property ), identifier , key ,
400
- propertyValueProvider . extendBy ( property ), backReferencePropertyValueProvider .extendBy (property ), accessor );
623
+ rootPath .append (property ), path .append (property ), identifier , key , propertyValueProvider . extendBy ( property ),
624
+ backReferencePropertyValueProvider .extendBy (property ), accessor );
401
625
}
402
626
403
627
T mapRow () {
0 commit comments