2020import org .hibernate .engine .spi .PersistentAttributeInterceptor ;
2121import org .hibernate .engine .spi .SharedSessionContractImplementor ;
2222import org .hibernate .engine .spi .Status ;
23+ import org .hibernate .internal .util .ImmutableBitSet ;
2324import org .hibernate .metamodel .mapping .internal .ToOneAttributeMapping ;
2425import org .hibernate .persister .entity .EntityPersister ;
2526import org .hibernate .proxy .LazyInitializer ;
3536import org .hibernate .sql .results .graph .Initializer ;
3637import org .hibernate .sql .results .graph .InitializerData ;
3738import org .hibernate .sql .results .graph .InitializerParent ;
39+ import org .hibernate .sql .results .graph .UnfetchedResultAssembler ;
40+ import org .hibernate .sql .results .graph .collection .internal .UnfetchedCollectionAssembler ;
3841import org .hibernate .sql .results .graph .entity .EntityResultGraphNode ;
3942import org .hibernate .sql .results .graph .entity .internal .EntityInitializerImpl ;
4043import org .hibernate .sql .results .jdbc .spi .JdbcValuesSourceProcessingOptions ;
@@ -216,7 +219,7 @@ else if ( lazyInitializer.isUninitialized() ) {
216219 if ( data .getState () == State .INITIALIZED ) {
217220 registerReloadedEntity ( data );
218221 resolveInstanceSubInitializers ( data );
219- if ( rowProcessingState .needsResolveState () ) {
222+ if ( data . getState () == State . INITIALIZED && rowProcessingState .needsResolveState () ) {
220223 // We need to read result set values to correctly populate the query cache
221224 resolveState ( data );
222225 }
@@ -329,14 +332,28 @@ private void postResolveInstance(ReactiveEntityInitializerData data) {
329332
330333 @ Override
331334 public CompletionStage <Void > reactiveInitializeInstance (EntityInitializerData data ) {
332- if ( data .getState () != State .RESOLVED ) {
333- return voidFuture ();
334- }
335- if ( !skipInitialization ( data ) ) {
336- assert consistentInstance ( data );
337- return reactiveInitializeEntityInstance ( (ReactiveEntityInitializerData ) data );
335+ final ReactiveEntityInitializerData reactiveEntityInitializerData = (ReactiveEntityInitializerData ) data ;
336+ if ( reactiveEntityInitializerData .getState () == State .RESOLVED ) {
337+ // todo: think about what to do when one initializer fetches a lazy basic but not the other
338+ if ( !skipInitialization ( reactiveEntityInitializerData ) ) {
339+ assert consistentInstance ( reactiveEntityInitializerData );
340+ return reactiveInitializeEntityInstance ( reactiveEntityInitializerData )
341+ .thenAccept ( unused -> reactiveEntityInitializerData .setState ( State .INITIALIZED ) );
342+ }
343+ else if ( reactiveEntityInitializerData .getRowProcessingState ().needsResolveState () ) {
344+ if ( reactiveEntityInitializerData .getRowProcessingState ().needsResolveState () ) {
345+ // A sub-initializer might have taken responsibility for this entity,
346+ // but we still need to resolve the state to correctly populate a query cache
347+ resolveState ( reactiveEntityInitializerData );
348+ }
349+ if ( getRootEntityDescriptor ().getBytecodeEnhancementMetadata ().isEnhancedForLazyLoading ()
350+ && reactiveEntityInitializerData .getEntityHolder ().getEntityInitializer () != this
351+ && reactiveEntityInitializerData .getEntityHolder ().isInitialized () ) {
352+ updateInitializedEntityInstance ( reactiveEntityInitializerData );
353+ }
354+ }
355+ reactiveEntityInitializerData .setState ( State .INITIALIZED );
338356 }
339- data .setState ( State .INITIALIZED );
340357 return voidFuture ();
341358 }
342359
@@ -348,6 +365,7 @@ protected CompletionStage<Void> reactiveInitializeEntityInstance(ReactiveEntityI
348365
349366 return reactiveExtractConcreteTypeStateValues ( data )
350367 .thenAccept ( resolvedEntityState -> {
368+ rowProcessingState .getJdbcValuesSourceProcessingState ().registerLoadingEntityHolder ( data .getEntityHolder () );
351369
352370 preLoad ( data , resolvedEntityState );
353371
@@ -447,7 +465,9 @@ protected CompletionStage<Void> reactiveResolveEntityInstance1(ReactiveEntityIni
447465 else {
448466 data .setInstance ( proxy );
449467 if ( Hibernate .isInitialized ( data .getInstance () ) ) {
450- data .setState ( State .INITIALIZED );
468+ if ( data .getEntityHolder ().isInitialized () ) {
469+ data .setState ( State .INITIALIZED );
470+ }
451471 data .setEntityInstanceForNotify ( Hibernate .unproxy ( data .getInstance () ) );
452472 }
453473 else {
@@ -538,13 +558,14 @@ protected CompletionStage<Object> reactiveResolveEntityInstance(ReactiveEntityIn
538558 return completedFuture ( resolved );
539559 }
540560 else {
541- if ( rowProcessingState . isQueryCacheHit () && getEntityDescriptor (). useShallowQueryCacheLayout () ) {
561+ if ( data . getShallowCached () ) {
542562 // We must load the entity this way, because the query cache entry contains only the primary key
543563 data .setState ( State .INITIALIZED );
544564 final SharedSessionContractImplementor session = rowProcessingState .getSession ();
545565 assert data .getEntityHolder ().getEntityInitializer () == this ;
546566 // If this initializer owns the entity, we have to remove the entity holder,
547567 // because the subsequent loading process will claim the entity
568+ rowProcessingState .getJdbcValuesSourceProcessingState ().getLoadingEntityHolders ().remove ( data .getEntityHolder () );
548569 session .getPersistenceContextInternal ().removeEntityHolder ( data .getEntityKey () );
549570 return ( (ReactiveQueryProducer ) session ).reactiveInternalLoad (
550571 data .getConcreteDescriptor ().getEntityName (),
@@ -674,15 +695,39 @@ protected CompletionStage<Void> reactiveResolveKey(ReactiveEntityInitializerData
674695
675696
676697 protected CompletionStage <Void > reactiveResolveInstanceSubInitializers (ReactiveEntityInitializerData data ) {
677- final Initializer <?>[] initializers = getSubInitializers ()[data .getConcreteDescriptor ().getSubclassId ()];
678- if ( initializers .length == 0 ) {
679- return voidFuture ();
680- }
698+ final int subclassId = data .getConcreteDescriptor ().getSubclassId ();
681699 final EntityEntry entityEntry = data .getEntityHolder ().getEntityEntry ();
700+ assert entityEntry != null : "This method should only be called if the entity is already initialized" ;
701+
702+ final Initializer <?>[] initializers ;
703+ final ImmutableBitSet maybeLazySet ;
704+ final boolean hasLazyInitializingSubAssemblers = hasLazyInitializingSubAssemblers ();
705+ if ( data .getEntityHolder ().getEntityInitializer () == this ) {
706+ // When this entity is already initialized, but this initializer runs anyway,
707+ // we only need to process collection containing initializers
708+ initializers = getCollectionContainingSubInitializers ()[subclassId ];
709+ maybeLazySet = null ;
710+ }
711+ else {
712+ // If an entity has unfetched attributes, we should probably also invoke non-eager initializers.
713+ // Non-eager initializers only set proxies, but since that contains the FK information,
714+ // it would be wasteful not to set that information on the bytecode enhanced entity
715+ var subInitializersToUse = getRootEntityDescriptor ().getBytecodeEnhancementMetadata ().hasUnFetchedAttributes ( data .getEntityInstanceForNotify () )
716+ ? getSubInitializers ()
717+ : getEagerSubInitializers ();
718+ initializers = subInitializersToUse [subclassId ];
719+ maybeLazySet = entityEntry .getMaybeLazySet ();
720+ // Skip resolving if this initializer has no sub-initializers
721+ // or the lazy set of this initializer is a superset/contains the entity entry maybeLazySet
722+ if ( initializers .length == 0 && !hasLazyInitializingSubAssemblers
723+ || maybeLazySet != null && getLazySets ()[subclassId ].contains ( maybeLazySet ) ) {
724+ return voidFuture ();
725+ }
726+ }
682727 final RowProcessingState rowProcessingState = data .getRowProcessingState ();
683- assert entityEntry = = rowProcessingState .getSession ()
684- .getPersistenceContextInternal ()
685- .getEntry ( data .getEntityInstanceForNotify () );
728+ final PersistenceContext persistenceContext = rowProcessingState .getSession ()
729+ .getPersistenceContextInternal ();
730+ assert entityEntry == persistenceContext .getEntry ( data .getEntityInstanceForNotify () );
686731 final Object [] loadedState = entityEntry .getLoadedState ();
687732 final Object [] state ;
688733 if ( loadedState == null ) {
@@ -700,32 +745,76 @@ protected CompletionStage<Void> reactiveResolveInstanceSubInitializers(ReactiveE
700745 else {
701746 state = loadedState ;
702747 }
703- return loop ( 0 , initializers .length , i -> {
704- final Initializer <?> initializer = initializers [i ];
705- if ( initializer != null ) {
706- final Object subInstance = state [i ];
707- if ( subInstance == UNFETCHED_PROPERTY ) {
708- if ( initializer instanceof ReactiveInitializer ) {
709- return ( (ReactiveInitializer <?>) initializer )
710- .reactiveResolveKey ( rowProcessingState );
711- }
712- else {
713- // Go through the normal initializer process
714- initializer .resolveKey ( rowProcessingState );
748+ if ( initializers .length == 0 ) {
749+ boolean needsLoadedValuesUpdate = false ;
750+ if ( hasLazyInitializingSubAssemblers ) {
751+ var subAssemblers = getAssemblers ()[subclassId ];
752+ for ( int i = 0 ; i < state .length ; i ++ ) {
753+ final Object subInstance = state [i ];
754+ final var assembler = subAssemblers [i ];
755+ if ( subInstance == UNFETCHED_PROPERTY
756+ && !(assembler instanceof UnfetchedResultAssembler <?> )
757+ && !(assembler instanceof UnfetchedCollectionAssembler ) ) {
758+ // This assembler will produce a value when the underlying entity property is still lazy
759+ needsLoadedValuesUpdate = true ;
760+ break ;
715761 }
716762 }
717- else {
718- if ( initializer instanceof ReactiveInitializer ) {
719- return ( (ReactiveInitializer <?>) initializer )
720- .reactiveResolveInstance ( subInstance , rowProcessingState );
763+ }
764+ if ( needsLoadedValuesUpdate ) {
765+ // Mark as resolved to update the state of the entity during initialization phase
766+ data .setState ( State .RESOLVED );
767+ }
768+ return voidFuture ();
769+ }
770+ else {
771+ final boolean [] needsLoadedValuesUpdate = new boolean [1 ];
772+ var eagerInitializers = getEagerSubInitializers ()[subclassId ];
773+ return loop ( 0 , initializers .length , i -> {
774+ final Initializer <?> initializer = initializers [i ];
775+ if ( maybeLazySet == null || maybeLazySet .get ( i ) ) {
776+ final Object subInstance = state [i ];
777+ if ( initializer != null ) {
778+ if ( subInstance == UNFETCHED_PROPERTY ) {
779+ // Go through the normal initializer process
780+ if ( initializer instanceof ReactiveInitializer <?> reactiveInitializer ) {
781+ reactiveInitializer .reactiveResolveKey ( rowProcessingState );
782+ }
783+ else {
784+ initializer .resolveKey ( rowProcessingState );
785+ }
786+ // Assume that the initializer will produce a proxy or the real value
787+ needsLoadedValuesUpdate [0 ] = true ;
788+ }
789+ // Avoid resolving initializers that are not lazy when the property isn't unfetched
790+ else if ( eagerInitializers .length != 0 && eagerInitializers [i ] != null ) {
791+ if ( initializer instanceof ReactiveInitializer <?> reactiveInitializer ) {
792+ reactiveInitializer .reactiveResolveInstance ( subInstance , rowProcessingState );
793+ }
794+ else {
795+ initializer .resolveInstance ( subInstance , rowProcessingState );
796+ }
797+ }
721798 }
722- else {
723- initializer .resolveInstance ( subInstance , rowProcessingState );
799+ else if ( !needsLoadedValuesUpdate [0 ] && hasLazyInitializingSubAssemblers && subInstance == UNFETCHED_PROPERTY ) {
800+ final var assembler = getAssemblers ()[subclassId ][i ];
801+ if ( !( assembler instanceof UnfetchedResultAssembler <?> )
802+ && !( assembler instanceof UnfetchedCollectionAssembler ) ) {
803+ // This assembler will produce a value when the underlying entity property is still lazy
804+ needsLoadedValuesUpdate [0 ] = true ;
805+ }
724806 }
725807 }
726- }
727- return voidFuture ();
728- } );
808+ return voidFuture ();
809+ }
810+ ).thenAccept ( unused ->
811+ {
812+ if ( needsLoadedValuesUpdate [0 ] ) {
813+ // Mark as resolved to update the state of the entity during initialization phase
814+ data .setState ( State .RESOLVED );
815+ }
816+ } );
817+ }
729818 }
730819
731820 protected CompletionStage <Void > reactiveResolveKeySubInitializers (ReactiveEntityInitializerData data ) {
0 commit comments