2020import java .sql .ResultSet ;
2121import java .sql .SQLException ;
2222import java .sql .SQLType ;
23+ import java .util .Iterator ;
2324import java .util .Map ;
2425import java .util .Optional ;
26+ import java .util .function .Function ;
2527
2628import org .apache .commons .logging .Log ;
2729import org .apache .commons .logging .LogFactory ;
4446import org .springframework .data .mapping .model .SpELContext ;
4547import org .springframework .data .mapping .model .SpELExpressionEvaluator ;
4648import org .springframework .data .mapping .model .SpELExpressionParameterValueProvider ;
49+ import org .springframework .data .projection .EntityProjection ;
4750import org .springframework .data .relational .core .conversion .MappingRelationalConverter ;
51+ import org .springframework .data .relational .core .conversion .ObjectPath ;
4852import org .springframework .data .relational .core .conversion .RelationalConverter ;
53+ import org .springframework .data .relational .core .conversion .RowDocumentAccessor ;
4954import org .springframework .data .relational .core .mapping .AggregatePath ;
5055import org .springframework .data .relational .core .mapping .RelationalMappingContext ;
5156import org .springframework .data .relational .core .mapping .RelationalPersistentEntity ;
5257import org .springframework .data .relational .core .mapping .RelationalPersistentProperty ;
5358import org .springframework .data .relational .core .sql .IdentifierProcessing ;
59+ import org .springframework .data .relational .domain .RowDocument ;
5460import org .springframework .data .util .TypeInformation ;
5561import org .springframework .lang .Nullable ;
5662import org .springframework .util .Assert ;
@@ -83,18 +89,15 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
8389 private SpELContext spELContext ;
8490
8591 /**
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
8994 * {@link #BasicJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)}
9095 * (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
9196 *
9297 * @param context must not be {@literal null}.
9398 * @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
9499 */
95- public BasicJdbcConverter (
96- RelationalMappingContext context ,
97- RelationResolver relationResolver ) {
100+ public BasicJdbcConverter (RelationalMappingContext context , RelationResolver relationResolver ) {
98101
99102 super (context , new JdbcCustomConversions ());
100103
@@ -115,10 +118,8 @@ public BasicJdbcConverter(
115118 * @param identifierProcessing must not be {@literal null}
116119 * @since 2.0
117120 */
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 ) {
122123
123124 super (context , conversions );
124125
@@ -300,16 +301,241 @@ private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
300301
301302 @ Override
302303 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 ();
305306 }
306307
307-
308308 @ Override
309309 public <T > T mapRow (AggregatePath path , ResultSet resultSet , Identifier identifier , Object key ) {
310310 return new ReadingContext <T >(path , new ResultSetAccessor (resultSet ), identifier , key ).mapRow ();
311311 }
312312
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+
313539 static Object [] requireObjectArray (Object source ) {
314540
315541 Assert .isTrue (source .getClass ().isArray (), "Source object is not an array" );
@@ -361,25 +587,23 @@ private class ReadingContext<T> {
361587 private final ResultSetAccessor accessor ;
362588
363589 @ 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 ) {
366591 RelationalPersistentEntity <T > entity = (RelationalPersistentEntity <T >) rootPath .getLeafEntity ();
367592
368593 Assert .notNull (entity , "The rootPath must point to an entity" );
369594
370595 this .entity = entity ;
371596 this .rootPath = rootPath ;
372- this .path = getMappingContext ().getAggregatePath ( this .entity );
597+ this .path = getMappingContext ().getAggregatePath (this .entity );
373598 this .identifier = identifier ;
374599 this .key = key ;
375600 this .propertyValueProvider = new JdbcPropertyValueProvider (path , accessor );
376601 this .backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider (path , accessor );
377602 this .accessor = accessor ;
378603 }
379604
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 ,
383607 JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider , ResultSetAccessor accessor ) {
384608
385609 this .entity = entity ;
@@ -396,8 +620,8 @@ private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
396620
397621 return new ReadingContext <>(
398622 (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 );
401625 }
402626
403627 T mapRow () {
0 commit comments