Fix SingleIdEntityLoaderDynamicBatch#load() method

This commit is contained in:
Andrea Boriero 2021-10-26 13:59:53 +02:00 committed by Steve Ebersole
parent 696eea9bbe
commit 29e22c68ac
4 changed files with 129 additions and 115 deletions

View File

@ -58,6 +58,16 @@ public class SingleIdEntityLoaderDynamicBatch<T> extends SingleIdEntityLoaderSup
@Override
public T load(Object pkValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) {
return load( pkValue, null, lockOptions, readOnly, session );
}
@Override
public T load(
Object pkValue,
Object entityInstance,
LockOptions lockOptions,
Boolean readOnly,
SharedSessionContractImplementor session) {
final Object[] batchIds = session.getPersistenceContextInternal()
.getBatchFetchQueue()
.getBatchLoadableEntityIds( getLoadable(), pkValue, maxBatchSize );
@ -134,43 +144,7 @@ public class SingleIdEntityLoaderDynamicBatch<T> extends SingleIdEntityLoaderSup
JdbcSelectExecutorStandardImpl.INSTANCE.list(
jdbcSelect,
jdbcParameterBindings,
new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return new QueryOptionsAdapter() {
@Override
public Boolean isReadOnly() {
return readOnly;
}
};
}
@Override
public String getQueryIdentifier(String sql) {
return sql;
}
@Override
public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) {
subSelectFetchableKeysHandler.addKey( entityKey );
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return null;
}
},
getExecutionContext( entityInstance, readOnly, session, subSelectFetchableKeysHandler ),
RowTransformerPassThruImpl.instance(),
ListResultsConsumer.UniqueSemantic.FILTER
);
@ -185,17 +159,56 @@ public class SingleIdEntityLoaderDynamicBatch<T> extends SingleIdEntityLoaderSup
final EntityKey entityKey = session.generateEntityKey( pkValue, getLoadable().getEntityPersister() );
//noinspection unchecked
return (T) session.getPersistenceContext().getEntity( entityKey );
}
@Override
public T load(
Object pkValue,
private ExecutionContext getExecutionContext(
Object entityInstance,
LockOptions lockOptions,
Boolean readOnly,
SharedSessionContractImplementor session) {
initializeSingleIdLoaderIfNeeded( session );
return singleIdLoader.load( pkValue, entityInstance, lockOptions, readOnly, session );
SharedSessionContractImplementor session,
SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler) {
return new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public Object getEntityInstance() {
return entityInstance;
}
@Override
public QueryOptions getQueryOptions() {
return new QueryOptionsAdapter() {
@Override
public Boolean isReadOnly() {
return readOnly;
}
};
}
@Override
public String getQueryIdentifier(String sql) {
return sql;
}
@Override
public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) {
subSelectFetchableKeysHandler.addKey( entityKey );
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return null;
}
};
}
private void initializeSingleIdLoaderIfNeeded(SharedSessionContractImplementor session) {

View File

@ -17,11 +17,14 @@ import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.engine.spi.EntityEntry;
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.SessionEventListenerManager;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -91,6 +94,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
private Object entityInstance;
private Object entityInstanceForNotify;
private boolean missing;
private boolean isOwningInitializer;
private Object[] resolvedEntityState;
// todo (6.0) : ^^ need a better way to track whether we are loading the entity state or if something else is/has
@ -473,6 +477,11 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
final Object proxy = getProxy( persistenceContext );
// Special case map proxy to avoid stack overflows
// We know that a map proxy will always be of "the right type" so just use that object
final LoadingEntityEntry existingLoadingEntry = persistenceContext
.getLoadContexts()
.findLoadingEntityEntry( entityKey );
setIsOwningInitializer(entityKey.getIdentifier(), existingLoadingEntry );
if ( proxy != null && ( proxy instanceof MapProxy
|| entityDescriptor.getJavaTypeDescriptor().getJavaTypeClass().isInstance( proxy ) ) ) {
entityInstance = proxy;
@ -488,11 +497,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
// initializer is already loading the entity
entityInstance = resolveInstance(
entityIdentifier,
existingLoadingEntry,
rowProcessingState,
session,
persistenceContext
session
);
}
if ( LockMode.NONE != lockMode ) {
@ -536,16 +544,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
return persistenceContext.getProxy( entityKey );
}
private Object resolveInstance(
Object entityIdentifier,
RowProcessingState rowProcessingState,
SharedSessionContractImplementor session,
PersistenceContext persistenceContext) {
final LoadingEntityEntry existingLoadingEntry = persistenceContext
.getLoadContexts()
.findLoadingEntityEntry( entityKey );
Object instance = null;
private void setIsOwningInitializer(Object entityIdentifier,LoadingEntityEntry existingLoadingEntry) {
if ( existingLoadingEntry != null ) {
if ( EntityLoadingLogger.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf(
@ -554,38 +553,47 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
toLoggableString( getNavigablePath(), entityIdentifier )
);
}
instance = existingLoadingEntry.getEntityInstance();
if ( existingLoadingEntry.getEntityInitializer() != this ) {
// the entity is already being loaded elsewhere
if ( EntityLoadingLogger.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf(
"(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing",
getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ),
existingLoadingEntry.getEntityInitializer()
);
}
// EARLY EXIT!!!
return instance;
if ( existingLoadingEntry.getEntityInitializer() == this ) {
isOwningInitializer = true;
}
}
else {
isOwningInitializer = true;
}
}
if ( instance == null ) {
// this isEntityReturn bit is just for entity loaders, not hql/criteria
if ( isEntityReturn() ) {
final Object requestedEntityId = rowProcessingState.getJdbcValuesSourceProcessingState()
.getProcessingOptions()
.getEffectiveOptionalId();
final Object optionalEntityInstance = rowProcessingState.getJdbcValuesSourceProcessingState()
.getProcessingOptions()
.getEffectiveOptionalObject();
if ( requestedEntityId != null && optionalEntityInstance != null && requestedEntityId.equals(
entityKey.getIdentifier() ) ) {
instance = optionalEntityInstance;
}
private Object resolveInstance(
Object entityIdentifier,
LoadingEntityEntry existingLoadingEntry,
RowProcessingState rowProcessingState,
SharedSessionContractImplementor session) {
if ( !isOwningInitializer ) {
// the entity is already being loaded elsewhere
if ( EntityLoadingLogger.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf(
"(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing",
getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ),
existingLoadingEntry.getEntityInitializer()
);
}
// EARLY EXIT!!!
return existingLoadingEntry.getEntityInstance();
}
Object instance = null;
// this isEntityReturn bit is just for entity loaders, not hql/criteria
if ( isEntityReturn() ) {
final Object requestedEntityId = rowProcessingState.getJdbcValuesSourceProcessingState()
.getProcessingOptions()
.getEffectiveOptionalId();
final Object optionalEntityInstance = rowProcessingState.getJdbcValuesSourceProcessingState()
.getProcessingOptions()
.getEffectiveOptionalObject();
if ( requestedEntityId != null && optionalEntityInstance != null && requestedEntityId.equals(
entityKey.getIdentifier() ) ) {
instance = optionalEntityInstance;
}
}
@ -631,6 +639,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
entityKey,
loadingEntry
);
return instance;
}
@ -644,7 +653,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
final SharedSessionContractImplementor session = rowProcessingState.getJdbcValuesSourceProcessingState().getSession();
final PersistenceContext persistenceContext = session.getPersistenceContext();
if ( entityInstance instanceof HibernateProxy ) {
LazyInitializer hibernateLazyInitializer = ( (HibernateProxy) entityInstance ).getHibernateLazyInitializer();
if ( !hibernateLazyInitializer.isUninitialized() ) {
@ -655,9 +663,9 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
if ( instance == null ) {
instance = resolveInstance(
entityKey.getIdentifier(),
persistenceContext.getLoadContexts().findLoadingEntityEntry( entityKey ),
rowProcessingState,
session,
persistenceContext
session
);
initializeEntity( instance, rowProcessingState, session, persistenceContext );
}
@ -865,10 +873,26 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
}
}
protected boolean skipInitialization(
private boolean skipInitialization(
Object toInitialize,
RowProcessingState rowProcessingState,
EntityEntry entry) {
if ( !isOwningInitializer ) {
return true;
}
if ( toInitialize instanceof PersistentAttributeInterceptable ) {
final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) toInitialize ).$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
if ( entry.getStatus() != Status.LOADING ) {
// Avoid loading the same entity proxy twice for the same result set: it could lead to errors,
// because some code writes to its input (ID in hydrated state replaced by the loaded entity, in particular).
return false;
}
return true;
}
}
// If the instance to initialize is the main entity, we can't skip this
// This can happen if we initialize an enhanced proxy
if ( entry.getStatus() != Status.LOADING ) {
@ -891,7 +915,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
return true;
}
final Boolean queryOption = rowProcessingState.getJdbcValuesSourceProcessingState().getQueryOptions().isReadOnly();
return queryOption == null ? false : queryOption;
@ -927,6 +950,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
@Override
public void finishUpRow(RowProcessingState rowProcessingState) {
// reset row state
isOwningInitializer = false;
concreteDescriptor = null;
entityKey = null;
entityInstance = null;

View File

@ -7,12 +7,7 @@
package org.hibernate.sql.results.graph.entity.internal;
import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.Status;
import org.hibernate.internal.log.LoggingHelper;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
@ -22,7 +17,6 @@ import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
/**
* @author Andrea Boriero
@ -84,21 +78,4 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer {
public String toString() {
return "EntityJoinedFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")";
}
@Override
protected boolean skipInitialization(
Object toInitialize, RowProcessingState rowProcessingState, EntityEntry entry) {
if ( toInitialize instanceof PersistentAttributeInterceptable ) {
final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) toInitialize ).$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
if ( entry.getStatus() != Status.LOADING ) {
// Avoid loading the same entity proxy twice for the same result set: it could lead to errors,
// because some code writes to its input (ID in hydrated state replaced by the loaded entity, in particular).
return false;
}
return true;
}
}
return super.skipInitialization( toInitialize, rowProcessingState, entry );
}
}

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.bytecode.enhancement.lazy.proxy;
package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy;
import java.util.ArrayList;
import java.util.List;