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 9555a1d2a0
commit ff0479c1d2
6 changed files with 98 additions and 40 deletions

View File

@ -350,14 +350,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
else {
// 2) build the EntityKey
entityKey = new EntityKey( id, concreteDescriptor );
// 3) schedule the EntityKey for batch loading, if possible
if ( concreteDescriptor.isBatchLoadable() ) {
final PersistenceContext persistenceContext =
rowProcessingState.getSession().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 org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
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.metamodel.mapping.AttributeMapping;
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.FetchParentAccess;
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 static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptableOrNull;
public abstract class AbstractBatchEntitySelectFetchInitializer extends AbstractFetchParentAccess
implements EntityInitializer {
@ -32,7 +39,7 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
protected final ToOneAttributeMapping referencedModelPart;
protected final EntityInitializer firstEntityInitializer;
protected Object entityInstance;
protected Object initializedEntityInstance;
protected EntityKey entityKey;
protected State state = State.UNINITIALIZED;
@ -90,18 +97,54 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
}
else {
entityKey = new EntityKey( entityIdentifier, concreteDescriptor );
state = State.KEY_RESOLVED;
rowProcessingState.getSession().getPersistenceContext()
.getBatchFetchQueue().addBatchLoadableEntityKey( entityKey );
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
public void finishUpRow(RowProcessingState rowProcessingState) {
entityInstance = null;
initializedEntityInstance = null;
entityKey = null;
state = State.UNINITIALIZED;
clearResolutionListeners();
@ -114,7 +157,7 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
@Override
public Object getEntityInstance() {
return entityInstance;
return initializedEntityInstance;
}
@Override
@ -129,8 +172,8 @@ public abstract class AbstractBatchEntitySelectFetchInitializer extends Abstract
@Override
public void registerResolutionListener(Consumer<Object> listener) {
if ( entityInstance != null ) {
listener.accept( entityInstance );
if ( initializedEntityInstance != null ) {
listener.accept( initializedEntityInstance );
}
else {
super.registerResolutionListener( listener );

View File

@ -63,12 +63,23 @@ public class BatchEntityInsideEmbeddableSelectFetchInitializer extends AbstractB
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
if ( state == State.INITIALIZED ) {
return;
}
resolveKey( rowProcessingState, referencedModelPart, parentAccess );
if ( entityKey == null ) {
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

View File

@ -39,7 +39,21 @@ public class BatchEntitySelectFetchInitializer extends AbstractBatchEntitySelect
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
if ( state == State.INITIALIZED ) {
return;
}
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

View File

@ -18,7 +18,6 @@ import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
/**
@ -46,32 +45,29 @@ public class BatchInitializeEntitySelectFetchInitializer extends AbstractBatchEn
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
resolveKey( rowProcessingState, referencedModelPart, parentAccess );
if ( state == State.INITIALIZED ) {
return;
}
resolveKey( rowProcessingState, referencedModelPart, parentAccess );
if ( entityKey == null ) {
return;
}
state = State.INITIALIZED;
final SharedSessionContractImplementor session = rowProcessingState.getSession();
entityInstance = session.getPersistenceContext().getEntity( entityKey );
if ( entityInstance == null ) {
final LoadingEntityEntry loadingEntityEntry = rowProcessingState.getJdbcValuesSourceProcessingState()
.findLoadingEntityLocally( entityKey );
if ( loadingEntityEntry != null ) {
loadingEntityEntry.getEntityInitializer().resolveInstance( rowProcessingState );
entityInstance = loadingEntityEntry.getEntityInstance();
}
else {
// Force creating a proxy
entityInstance = session.internalLoad(
entityKey.getEntityName(),
entityKey.getIdentifier(),
false,
false
);
toBatchLoad.add( entityKey );
}
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 );
// Force creating a proxy
initializedEntityInstance = rowProcessingState.getSession().internalLoad(
entityKey.getEntityName(),
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.results.graph.Initializer;
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.EntitySelectFetchInitializer;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
@ -118,7 +119,8 @@ public final class InitializersList {
private static boolean initializeFirst(final Initializer initializer) {
return !( initializer instanceof EntityDelayedFetchInitializer )
&& !( initializer instanceof EntitySelectFetchInitializer )
&& !( initializer instanceof AbstractCollectionInitializer );
&& !( initializer instanceof AbstractCollectionInitializer )
&& !(initializer instanceof AbstractBatchEntitySelectFetchInitializer );
}
InitializersList build(final Map<NavigablePath, Initializer> initializerMap) {