HHH-16890 StackOverflowError when loading entities with @Proxy(lazy = false)

This commit is contained in:
Andrea Boriero 2023-07-06 15:09:11 +02:00 committed by Andrea Boriero
parent abaaa09225
commit 75d834efe9
6 changed files with 98 additions and 42 deletions

View File

@ -351,16 +351,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
else { else {
// 2) build the EntityKey // 2) build the EntityKey
entityKey = new EntityKey( id, concreteDescriptor ); entityKey = new EntityKey( id, concreteDescriptor );
// 3) schedule the EntityKey for batch loading, if possible
final SharedSessionContractImplementor session = rowProcessingState.getSession();
if ( session.getLoadQueryInfluencers().effectivelyBatchLoadable( concreteDescriptor ) ) {
final PersistenceContext persistenceContext =
session.getPersistenceContextInternal();
if ( !persistenceContext.containsEntity( entityKey ) ) {
persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( entityKey );
}
}
} }
} }
} }

View File

@ -8,7 +8,11 @@ package org.hibernate.sql.results.graph.entity.internal;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
@ -19,8 +23,11 @@ import org.hibernate.sql.results.graph.AbstractFetchParentAccess;
import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptableOrNull;
public abstract class AbstractBatchEntitySelectFetchInitializer extends AbstractFetchParentAccess public abstract class AbstractBatchEntitySelectFetchInitializer extends AbstractFetchParentAccess
implements EntityInitializer { implements EntityInitializer {
@ -32,7 +39,7 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
protected final ToOneAttributeMapping referencedModelPart; protected final ToOneAttributeMapping referencedModelPart;
protected final EntityInitializer firstEntityInitializer; protected final EntityInitializer firstEntityInitializer;
protected Object entityInstance; protected Object initializedEntityInstance;
protected EntityKey entityKey; protected EntityKey entityKey;
protected State state = State.UNINITIALIZED; protected State state = State.UNINITIALIZED;
@ -90,18 +97,54 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
} }
else { else {
entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); entityKey = new EntityKey( entityIdentifier, concreteDescriptor );
state = State.KEY_RESOLVED; state = State.KEY_RESOLVED;
rowProcessingState.getSession().getPersistenceContext()
.getBatchFetchQueue().addBatchLoadableEntityKey( entityKey );
registerResolutionListener(); registerResolutionListener();
} }
} }
protected Object getExistingInitializedInstance(RowProcessingState rowProcessingState) {
assert entityKey != null;
final SharedSessionContractImplementor session = rowProcessingState.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContext();
final Object instance = persistenceContext.getEntity( entityKey );
if ( instance == null ) {
final LoadingEntityEntry loadingEntityEntry = persistenceContext
.getLoadContexts().findLoadingEntityEntry( entityKey );
if ( loadingEntityEntry != null ) {
return loadingEntityEntry.getEntityInstance();
}
}
else if ( isInitialized( instance ) ) {
return instance;
}
return null;
}
private boolean isInitialized(Object entity) {
final PersistentAttributeInterceptable attributeInterceptable = asPersistentAttributeInterceptableOrNull(
entity );
if ( attributeInterceptable == null ) {
return true;
}
final PersistentAttributeInterceptor interceptor =
attributeInterceptable.$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
return ( (EnhancementAsProxyLazinessInterceptor) interceptor ).isInitialized();
}
else {
return true;
}
}
protected void registerToBatchFetchQueue(RowProcessingState rowProcessingState) {
assert entityKey != null;
rowProcessingState.getSession().getPersistenceContext()
.getBatchFetchQueue().addBatchLoadableEntityKey( entityKey );
}
@Override @Override
public void finishUpRow(RowProcessingState rowProcessingState) { public void finishUpRow(RowProcessingState rowProcessingState) {
entityInstance = null; initializedEntityInstance = null;
entityKey = null; entityKey = null;
state = State.UNINITIALIZED; state = State.UNINITIALIZED;
clearResolutionListeners(); clearResolutionListeners();
@ -114,7 +157,7 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
@Override @Override
public Object getEntityInstance() { public Object getEntityInstance() {
return entityInstance; return initializedEntityInstance;
} }
@Override @Override
@ -129,8 +172,8 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
@Override @Override
public void registerResolutionListener(Consumer<Object> listener) { public void registerResolutionListener(Consumer<Object> listener) {
if ( entityInstance != null ) { if ( initializedEntityInstance != null ) {
listener.accept( entityInstance ); listener.accept( initializedEntityInstance );
} }
else { else {
super.registerResolutionListener( listener ); super.registerResolutionListener( listener );

View File

@ -63,12 +63,23 @@ public class BatchEntityInsideEmbeddableSelectFetchInitializer extends AbstractB
@Override @Override
public void resolveInstance(RowProcessingState rowProcessingState) { public void resolveInstance(RowProcessingState rowProcessingState) {
if ( state == State.INITIALIZED ) {
return;
}
resolveKey( rowProcessingState, referencedModelPart, parentAccess ); resolveKey( rowProcessingState, referencedModelPart, parentAccess );
if ( entityKey == null ) { if ( entityKey == null ) {
return; return;
} }
state = State.INITIALIZED;
initializedEntityInstance = getExistingInitializedInstance( rowProcessingState );
if ( initializedEntityInstance == null ) {
entityInstance = BATCH_PROPERTY; // need to add the key to the batch queue only when the entity has not been already loaded or
// there isn't another initializer that is loading it
registerToBatchFetchQueue( rowProcessingState );
initializedEntityInstance = BATCH_PROPERTY;
}
} }
@Override @Override

View File

@ -39,7 +39,21 @@ public class BatchEntitySelectFetchInitializer extends AbstractBatchEntitySelect
@Override @Override
public void resolveInstance(RowProcessingState rowProcessingState) { public void resolveInstance(RowProcessingState rowProcessingState) {
if ( state == State.INITIALIZED ) {
return;
}
resolveKey( rowProcessingState, referencedModelPart, parentAccess ); resolveKey( rowProcessingState, referencedModelPart, parentAccess );
if ( entityKey == null ) {
return;
}
state = State.INITIALIZED;
initializedEntityInstance = getExistingInitializedInstance( rowProcessingState );
if ( initializedEntityInstance == null ) {
// need to add the key to the batch queue only when the entity has not been already loaded or
// there isn't another initializer that is loading it
registerToBatchFetchQueue( rowProcessingState );
}
} }
@Override @Override

View File

@ -18,7 +18,6 @@ import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
/** /**
@ -46,32 +45,29 @@ public class BatchInitializeEntitySelectFetchInitializer extends AbstractBatchEn
@Override @Override
public void resolveInstance(RowProcessingState rowProcessingState) { public void resolveInstance(RowProcessingState rowProcessingState) {
resolveKey( rowProcessingState, referencedModelPart, parentAccess ); if ( state == State.INITIALIZED ) {
return;
}
resolveKey( rowProcessingState, referencedModelPart, parentAccess );
if ( entityKey == null ) { if ( entityKey == null ) {
return; return;
} }
state = State.INITIALIZED; state = State.INITIALIZED;
final SharedSessionContractImplementor session = rowProcessingState.getSession(); initializedEntityInstance = getExistingInitializedInstance( rowProcessingState );
entityInstance = session.getPersistenceContext().getEntity( entityKey ); if ( initializedEntityInstance == null ) {
if ( entityInstance == null ) { // need to add the key to the batch queue only when the entity has not been already loaded or
final LoadingEntityEntry loadingEntityEntry = rowProcessingState.getJdbcValuesSourceProcessingState() // there isn't another initializer that is loading it
.findLoadingEntityLocally( entityKey ); registerToBatchFetchQueue( rowProcessingState );
if ( loadingEntityEntry != null ) { // Force creating a proxy
loadingEntityEntry.getEntityInitializer().resolveInstance( rowProcessingState ); initializedEntityInstance = rowProcessingState.getSession().internalLoad(
entityInstance = loadingEntityEntry.getEntityInstance(); entityKey.getEntityName(),
} entityKey.getIdentifier(),
else { false,
// Force creating a proxy false
entityInstance = session.internalLoad( );
entityKey.getEntityName(), toBatchLoad.add( entityKey );
entityKey.getIdentifier(),
false,
false
);
toBatchLoad.add( entityKey );
}
} }
} }

View File

@ -15,6 +15,7 @@ import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.collection.internal.AbstractCollectionInitializer; import org.hibernate.sql.results.graph.collection.internal.AbstractCollectionInitializer;
import org.hibernate.sql.results.graph.entity.internal.AbstractBatchEntitySelectFetchInitializer;
import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer;
import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
@ -118,7 +119,8 @@ public final class InitializersList {
private static boolean initializeFirst(final Initializer initializer) { private static boolean initializeFirst(final Initializer initializer) {
return !( initializer instanceof EntityDelayedFetchInitializer ) return !( initializer instanceof EntityDelayedFetchInitializer )
&& !( initializer instanceof EntitySelectFetchInitializer ) && !( initializer instanceof EntitySelectFetchInitializer )
&& !( initializer instanceof AbstractCollectionInitializer ); && !( initializer instanceof AbstractCollectionInitializer )
&& !(initializer instanceof AbstractBatchEntitySelectFetchInitializer );
} }
InitializersList build(final Map<NavigablePath, Initializer> initializerMap) { InitializersList build(final Map<NavigablePath, Initializer> initializerMap) {