finish big refactor of AbstractMultiIdEntityLoader and children

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-10-26 12:26:35 +02:00
parent ae538102f9
commit 55255e9d4a
4 changed files with 251 additions and 323 deletions

View File

@ -262,7 +262,7 @@ public final class CollectionHelper {
return copy; return copy;
} }
public static boolean isEmpty(Collection collection) { public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty(); return collection == null || collection.isEmpty();
} }

View File

@ -4,6 +4,7 @@
*/ */
package org.hibernate.loader.ast.internal; package org.hibernate.loader.ast.internal;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
@ -20,10 +21,12 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic; import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic;
import static org.hibernate.loader.ast.internal.LoaderHelper.getReadOnlyFromLoadQueryInfluencers; import static org.hibernate.loader.ast.internal.LoaderHelper.getReadOnlyFromLoadQueryInfluencers;
import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER;
@ -37,11 +40,13 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
private final EntityMappingType entityDescriptor; private final EntityMappingType entityDescriptor;
private final SessionFactoryImplementor sessionFactory; private final SessionFactoryImplementor sessionFactory;
private final EntityIdentifierMapping identifierMapping; private final EntityIdentifierMapping identifierMapping;
protected final Object[] idArray;
public AbstractMultiIdEntityLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) { public AbstractMultiIdEntityLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor; this.entityDescriptor = entityDescriptor;
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
identifierMapping = getLoadable().getIdentifierMapping(); identifierMapping = getLoadable().getIdentifierMapping();
idArray = (Object[]) Array.newInstance( identifierMapping.getJavaType().getJavaTypeClass(), 0 );
} }
protected EntityMappingType getEntityDescriptor() { protected EntityMappingType getEntityDescriptor() {
@ -76,31 +81,43 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
@Override @Override
public final <K> List<T> load(K[] ids, MultiIdLoadOptions loadOptions, EventSource session) { public final <K> List<T> load(K[] ids, MultiIdLoadOptions loadOptions, EventSource session) {
assert ids != null; assert ids != null;
if ( loadOptions.isOrderReturnEnabled() ) { return loadOptions.isOrderReturnEnabled()
return performOrderedMultiLoad( ids, loadOptions, session ); ? performOrderedMultiLoad( ids, loadOptions, session )
} : performUnorderedMultiLoad( ids, loadOptions, session );
else { }
return performUnorderedMultiLoad( ids, loadOptions, session );
private List<T> performUnorderedMultiLoad(
Object[] ids,
MultiIdLoadOptions loadOptions,
EventSource session) {
assert !loadOptions.isOrderReturnEnabled();
assert ids != null;
if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MULTI_KEY_LOAD_LOGGER.tracef( "#performUnorderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() );
} }
return unorderedMultiLoad( ids, loadOptions, lockOptions( loadOptions ), session );
} }
protected List<T> performOrderedMultiLoad( protected List<T> performOrderedMultiLoad(
Object[] ids, Object[] ids,
MultiIdLoadOptions loadOptions, MultiIdLoadOptions loadOptions,
EventSource session) { EventSource session) {
assert loadOptions.isOrderReturnEnabled();
assert ids != null;
if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MULTI_KEY_LOAD_LOGGER.tracef( "#performOrderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() ); MULTI_KEY_LOAD_LOGGER.tracef( "#performOrderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() );
} }
return orderedMultiLoad( ids, loadOptions, lockOptions( loadOptions ), session );
}
assert loadOptions.isOrderReturnEnabled(); private List<T> orderedMultiLoad(
Object[] ids,
final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); MultiIdLoadOptions loadOptions,
LockOptions lockOptions,
EventSource session) {
final boolean idCoercionEnabled = isIdCoercionEnabled();
final JavaType<?> idType = getLoadable().getIdentifierMapping().getJavaType(); final JavaType<?> idType = getLoadable().getIdentifierMapping().getJavaType();
final LockOptions lockOptions = loadOptions.getLockOptions() == null
? new LockOptions( LockMode.NONE )
: loadOptions.getLockOptions();
final int maxBatchSize = maxBatchSize( ids, loadOptions ); final int maxBatchSize = maxBatchSize( ids, loadOptions );
final List<Object> result = arrayList( ids.length ); final List<Object> result = arrayList( ids.length );
@ -109,10 +126,10 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>(); final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>();
for ( int i = 0; i < ids.length; i++ ) { for ( int i = 0; i < ids.length; i++ ) {
final Object id = coerce ? idType.coerce( ids[i], session ) : ids[i]; final Object id = idCoercionEnabled ? idType.coerce( ids[i], session ) : ids[i];
final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() );
if ( !loadFromCaches( loadOptions, session, id, lockOptions, entityKey, result, i ) ) { if ( !loadFromEnabledCaches( loadOptions, session, id, lockOptions, entityKey, result, i ) ) {
// if we did not hit any of the continues above, // if we did not hit any of the continues above,
// then we need to batch load the entity state. // then we need to batch load the entity state.
idsInBatch.add( id ); idsInBatch.add( id );
@ -142,13 +159,27 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
return (List<T>) result; return (List<T>) result;
} }
protected static LockOptions lockOptions(MultiIdLoadOptions loadOptions) {
return loadOptions.getLockOptions() == null
? new LockOptions( LockMode.NONE )
: loadOptions.getLockOptions();
}
protected abstract int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions); protected abstract int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions);
protected abstract void handleResults(MultiIdLoadOptions loadOptions, EventSource session, List<Integer> elementPositionsLoadedByBatch, List<Object> result); protected abstract void handleResults(
MultiIdLoadOptions loadOptions,
EventSource session,
List<Integer> elementPositionsLoadedByBatch,
List<Object> result);
protected abstract void loadEntitiesById(List<Object> idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, EventSource session); protected abstract void loadEntitiesById(
List<Object> idsInBatch,
LockOptions lockOptions,
MultiIdLoadOptions loadOptions,
EventSource session);
protected boolean loadFromCaches( protected boolean loadFromEnabledCaches(
MultiIdLoadOptions loadOptions, MultiIdLoadOptions loadOptions,
EventSource session, EventSource session,
Object id, Object id,
@ -157,48 +188,189 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
List<Object> result, List<Object> result,
int i) { int i) {
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
final LoadEvent loadEvent = new LoadEvent( return loadFromCaches( loadOptions, entityKey, result, i,
id, new LoadEvent(
getLoadable().getJavaType().getJavaTypeClass().getName(), id,
lockOptions, getLoadable().getJavaType().getJavaTypeClass().getName(),
session, lockOptions,
getReadOnlyFromLoadQueryInfluencers( session ) session,
getReadOnlyFromLoadQueryInfluencers( session )
)
); );
}
else {
return false;
}
}
Object managedEntity = null; private boolean loadFromCaches(
MultiIdLoadOptions loadOptions,
EntityKey entityKey,
List<Object> result,
int i,
LoadEvent loadEvent) {
Object managedEntity = null;
if ( loadOptions.isSessionCheckingEnabled() ) { if ( loadOptions.isSessionCheckingEnabled() ) {
// look for it in the Session first // look for it in the Session first
final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry =
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET ); loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
managedEntity = persistenceContextEntry.getEntity(); managedEntity = persistenceContextEntry.getEntity();
if ( managedEntity != null if ( managedEntity != null
&& !loadOptions.isReturnOfDeletedEntitiesEnabled() && !loadOptions.isReturnOfDeletedEntitiesEnabled()
&& !persistenceContextEntry.isManaged() ) { && !persistenceContextEntry.isManaged() ) {
// put a null in the result // put a null in the result
result.add( i, null ); result.add( i, null );
return true;
}
}
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
// look for it in the SessionFactory
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
loadEvent,
getLoadable().getEntityPersister(),
entityKey
);
}
if ( managedEntity != null ) {
result.add( i, managedEntity );
return true; return true;
} }
} }
return false;
if ( managedEntity == null
&& loadOptions.isSecondLevelCacheCheckingEnabled() ) {
// look for it in the SessionFactory
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
loadEvent,
getLoadable().getEntityPersister(),
entityKey
);
}
if ( managedEntity != null ) {
result.add( i, managedEntity );
return true;
}
else {
return false;
}
} }
protected abstract <K> List<T> performUnorderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session); protected List<T> unorderedMultiLoad(
Object[] ids,
MultiIdLoadOptions loadOptions,
LockOptions lockOptions,
EventSource session) {
final List<T> result = arrayList( ids.length );
final Object[] unresolvableIds =
resolveInCachesIfEnabled( ids, loadOptions, lockOptions, session,
(position, entityKey, resolvedRef) -> result.add( (T) resolvedRef ) );
if ( !isEmpty( unresolvableIds ) ) {
loadEntitiesWithUnresolvedIds( loadOptions, lockOptions, session, unresolvableIds, result );
}
return result;
}
protected abstract void loadEntitiesWithUnresolvedIds(
MultiIdLoadOptions loadOptions,
LockOptions lockOptions,
EventSource session,
Object[] unresolvableIds,
List<T> result);
protected final <R> Object[] resolveInCachesIfEnabled(
Object[] ids,
@NonNull MultiIdLoadOptions loadOptions,
@NonNull LockOptions lockOptions,
EventSource session,
ResolutionConsumer<R> resolutionConsumer) {
return loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled()
// the user requested that we exclude ids corresponding to already managed
// entities from the generated load SQL. So here we will iterate all
// incoming id values and see whether it corresponds to an existing
// entity associated with the PC - if it does we add it to the result
// list immediately and remove its id from the group of ids to load.
// we'll load all of them from the database
? resolveInCaches( ids, loadOptions, lockOptions, session, resolutionConsumer )
: ids;
}
protected final <R> Object[] resolveInCaches(
Object[] ids,
MultiIdLoadOptions loadOptions,
LockOptions lockOptions,
EventSource session,
ResolutionConsumer<R> resolutionConsumer) {
final boolean idCoercionEnabled = isIdCoercionEnabled();
final JavaType<?> idType = getLoadable().getIdentifierMapping().getJavaType();
List<Object> unresolvedIds = null;
for ( int i = 0; i < ids.length; i++ ) {
final Object id = idCoercionEnabled ? idType.coerce( ids[i], session ) : ids[i];
final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() );
unresolvedIds = loadFromCaches( id, entityKey, i, unresolvedIds, loadOptions, resolutionConsumer,
new LoadEvent(
id,
getLoadable().getJavaType().getJavaTypeClass().getName(),
lockOptions,
session,
getReadOnlyFromLoadQueryInfluencers( session )
)
);
}
if ( isEmpty( unresolvedIds ) ) {
// all the given ids were already associated with the Session
return null;
}
else if ( unresolvedIds.size() == ids.length ) {
// we need to load all the ids
return ids;
}
else {
// we need to load only some the ids
return unresolvedIds.toArray( idArray );
}
}
private boolean isIdCoercionEnabled() {
return !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled();
}
public interface ResolutionConsumer<T> {
void consume(int position, EntityKey entityKey, T resolvedRef);
}
private <R, K> List<K> loadFromCaches(
K id, EntityKey entityKey, int i,
List<K> unresolvedIds,
MultiIdLoadOptions loadOptions,
ResolutionConsumer<R> resolutionConsumer,
LoadEvent loadEvent) {
Object cachedEntity = null;
// look for it in the Session first
final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry =
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
if ( loadOptions.isSessionCheckingEnabled() ) {
cachedEntity = persistenceContextEntry.getEntity();
if ( cachedEntity != null
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
&& !persistenceContextEntry.isManaged() ) {
resolutionConsumer.consume( i, entityKey, null );
return unresolvedIds;
}
}
if ( cachedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
loadEvent,
getLoadable().getEntityPersister(),
entityKey
);
}
if ( cachedEntity != null ) {
//noinspection unchecked
resolutionConsumer.consume( i, entityKey, (R) cachedEntity);
}
else {
if ( unresolvedIds == null ) {
unresolvedIds = new ArrayList<>();
}
unresolvedIds.add( id );
}
return unresolvedIds;
}
} }

View File

@ -4,29 +4,20 @@
*/ */
package org.hibernate.loader.ast.internal; package org.hibernate.loader.ast.internal;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.PersistenceContextEntry;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader; import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
@ -35,19 +26,12 @@ import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
import org.hibernate.sql.results.spi.ManagedResultConsumer; import org.hibernate.sql.results.spi.ManagedResultConsumer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.hibernate.type.descriptor.java.JavaType;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static org.hibernate.engine.internal.BatchFetchQueueHelper.removeBatchLoadableEntityKey; import static org.hibernate.engine.internal.BatchFetchQueueHelper.removeBatchLoadableEntityKey;
import static org.hibernate.engine.spi.SubselectFetch.createRegistrationHandler; import static org.hibernate.engine.spi.SubselectFetch.createRegistrationHandler;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic;
import static org.hibernate.loader.ast.internal.LoaderHelper.getReadOnlyFromLoadQueryInfluencers;
import static org.hibernate.loader.ast.internal.LoaderHelper.loadByArrayParameter; import static org.hibernate.loader.ast.internal.LoaderHelper.loadByArrayParameter;
import static org.hibernate.loader.ast.internal.LoaderSelectBuilder.createSelectBySingleArrayParameter; import static org.hibernate.loader.ast.internal.LoaderSelectBuilder.createSelectBySingleArrayParameter;
import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.resolveArrayJdbcMapping; import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.resolveArrayJdbcMapping;
@ -64,11 +48,11 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
super( entityDescriptor, sessionFactory ); super( entityDescriptor, sessionFactory );
final Class<?> arrayClass = createTypedArray( 0 ).getClass(); final Class<?> idArrayClass = idArray.getClass();
arrayJdbcMapping = resolveArrayJdbcMapping( arrayJdbcMapping = resolveArrayJdbcMapping(
getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( arrayClass ), getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( idArrayClass ),
getIdentifierMapping().getJdbcMapping(), getIdentifierMapping().getJdbcMapping(),
arrayClass, idArrayClass,
getSessionFactory() getSessionFactory()
); );
jdbcParameter = new JdbcParameterImpl( arrayJdbcMapping ); jdbcParameter = new JdbcParameterImpl( arrayJdbcMapping );
@ -133,7 +117,7 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(1); final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(1);
jdbcParameterBindings.addBinding( jdbcParameter, jdbcParameterBindings.addBinding( jdbcParameter,
new JdbcParameterBindingImpl( arrayJdbcMapping, idsInBatch.toArray( createTypedArray(0) ) ) ); new JdbcParameterBindingImpl( arrayJdbcMapping, idsInBatch.toArray( idArray ) ) );
getJdbcSelectExecutor().executeQuery( getJdbcSelectExecutor().executeQuery(
getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst ) getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst )
@ -156,36 +140,12 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
} }
@Override @Override
protected <K> List<E> performUnorderedMultiLoad( protected void loadEntitiesWithUnresolvedIds(
K[] ids,
MultiIdLoadOptions loadOptions, MultiIdLoadOptions loadOptions,
EventSource session) { LockOptions lockOptions,
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { EventSource session,
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( Object[] unresolvableIds,
"MultiIdEntityLoaderArrayParam#performUnorderedMultiLoad - %s", List<E> result) {
getLoadable().getEntityName()
);
}
final List<E> result = CollectionHelper.arrayList( ids.length );
final LockOptions lockOptions = loadOptions.getLockOptions() == null
? new LockOptions( LockMode.NONE )
: loadOptions.getLockOptions();
//noinspection unchecked
final K[] idsToLoadFromDatabase = processResolvableEntities(
ids,
(index, entityKey, resolvedEntity) -> result.add( (E) resolvedEntity ),
loadOptions,
lockOptions,
session
);
if ( idsToLoadFromDatabase == null ) {
// all the given ids were already associated with the Session
return result;
}
final SelectStatement sqlAst = createSelectBySingleArrayParameter( final SelectStatement sqlAst = createSelectBySingleArrayParameter(
getLoadable(), getLoadable(),
getIdentifierMapping(), getIdentifierMapping(),
@ -194,12 +154,13 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
jdbcParameter, jdbcParameter,
getSessionFactory() getSessionFactory()
); );
final JdbcOperationQuerySelect jdbcSelectOperation = final JdbcOperationQuerySelect jdbcSelectOperation =
getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst ) getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst )
.translate( NO_BINDINGS, QueryOptions.NONE ); .translate( NO_BINDINGS, QueryOptions.NONE );
final List<E> databaseResults = loadByArrayParameter( final List<E> databaseResults = loadByArrayParameter(
idsToLoadFromDatabase, unresolvableIds,
sqlAst, sqlAst,
jdbcSelectOperation, jdbcSelectOperation,
jdbcParameter, jdbcParameter,
@ -213,108 +174,14 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
); );
result.addAll( databaseResults ); result.addAll( databaseResults );
//noinspection ForLoopReplaceableByForEach for ( Object id : unresolvableIds ) {
for ( int i = 0; i < idsToLoadFromDatabase.length; i++ ) { // skip any of the null padded ids
final Object id = idsToLoadFromDatabase[i]; // (actually we could probably even break on the first null)
if ( id == null ) { if ( id != null ) {
// skip any of the null padded ids // found or not, remove the key from the batch-fetch queue
// - actually we could probably even break here removeBatchLoadableEntityKey( id, getLoadable(), session );
continue;
}
// found or not, remove the key from the batch-fetch queue
removeBatchLoadableEntityKey( id, getLoadable(), session );
}
return result;
}
public interface ResolutionConsumer<T> {
void consume(int position, EntityKey entityKey, T resolvedRef);
}
protected final <R,K> K[] processResolvableEntities(
K[] ids,
ResolutionConsumer<R> resolutionConsumer,
@NonNull MultiIdLoadOptions loadOptions,
@NonNull LockOptions lockOptions,
EventSource session) {
if ( !loadOptions.isSessionCheckingEnabled()
&& !loadOptions.isSecondLevelCacheCheckingEnabled() ) {
// we'll load all of them from the database
return ids;
}
boolean foundAnyResolvedEntities = false;
List<K> nonResolvedIds = null;
final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled();
final JavaType<?> idType = getLoadable().getIdentifierMapping().getJavaType();
for ( int i = 0; i < ids.length; i++ ) {
final Object id = coerce ? idType.coerce( ids[i], session ) : ids[i];
final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() );
final LoadEvent loadEvent = new LoadEvent(
id,
getLoadable().getJavaType().getJavaTypeClass().getName(),
lockOptions,
session,
getReadOnlyFromLoadQueryInfluencers( session )
);
Object managedEntity = null;
// look for it in the Session first
final PersistenceContextEntry persistenceContextEntry =
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
if ( loadOptions.isSessionCheckingEnabled() ) {
managedEntity = persistenceContextEntry.getEntity();
if ( managedEntity != null
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
&& !persistenceContextEntry.isManaged() ) {
foundAnyResolvedEntities = true;
resolutionConsumer.consume( i, entityKey, null );
continue;
}
}
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
loadEvent,
getLoadable().getEntityPersister(),
entityKey
);
}
if ( managedEntity != null ) {
foundAnyResolvedEntities = true;
//noinspection unchecked
resolutionConsumer.consume( i, entityKey, (R) managedEntity);
}
else {
if ( nonResolvedIds == null ) {
nonResolvedIds = new ArrayList<>();
}
//noinspection unchecked
nonResolvedIds.add( (K) id );
} }
} }
if ( foundAnyResolvedEntities ) {
if ( isEmpty( nonResolvedIds ) ) {
// all the given ids were already associated with the Session
return null;
}
return nonResolvedIds.toArray( createTypedArray(0) );
}
return ids;
} }
private <X> X[] createTypedArray(@SuppressWarnings("SameParameterValue") int length) {
//noinspection unchecked
return (X[]) Array.newInstance( getIdentifierMapping().getJavaType().getJavaTypeClass(), length );
}
} }

View File

@ -4,11 +4,8 @@
*/ */
package org.hibernate.loader.ast.internal; package org.hibernate.loader.ast.internal;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.BatchFetchQueue;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
@ -18,9 +15,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.PersistenceContextEntry;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy; import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -32,15 +26,11 @@ import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.type.descriptor.java.JavaType;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static java.lang.System.arraycopy;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.hibernate.engine.spi.SubselectFetch.createRegistrationHandler; import static org.hibernate.engine.spi.SubselectFetch.createRegistrationHandler;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic;
import static org.hibernate.loader.ast.internal.LoaderHelper.getReadOnlyFromLoadQueryInfluencers;
import static org.hibernate.loader.ast.internal.LoaderSelectBuilder.createSelect; import static org.hibernate.loader.ast.internal.LoaderSelectBuilder.createSelect;
import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER;
@ -205,123 +195,22 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
} }
@Override @Override
protected List<T> performUnorderedMultiLoad( protected void loadEntitiesWithUnresolvedIds(
Object[] ids,
MultiIdLoadOptions loadOptions, MultiIdLoadOptions loadOptions,
EventSource session) { LockOptions lockOptions,
assert !loadOptions.isOrderReturnEnabled(); EventSource session,
assert ids != null; Object[] unresolvableIds,
List<T> result) {
if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { final int maxBatchSize = maxBatchSize( unresolvableIds, loadOptions );
MULTI_KEY_LOAD_LOGGER.tracef( "#performUnorderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() ); int numberOfIdsLeft = unresolvableIds.length;
}
final List<T> result = arrayList( ids.length );
final LockOptions lockOptions = loadOptions.getLockOptions() == null
? new LockOptions( LockMode.NONE )
: loadOptions.getLockOptions();
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
// the user requested that we exclude ids corresponding to already managed
// entities from the generated load SQL. So here we will iterate all
// incoming id values and see whether it corresponds to an existing
// entity associated with the PC - if it does we add it to the result
// list immediately and remove its id from the group of ids to load.
boolean foundAnyManagedEntities = false;
final List<Object> nonManagedIds = new ArrayList<>();
final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled();
final JavaType<?> idType = getLoadable().getIdentifierMapping().getJavaType();
for ( int i = 0; i < ids.length; i++ ) {
final Object id = coerce ? idType.coerce( ids[i], session ) : ids[i];
final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() );
final LoadEvent loadEvent = new LoadEvent(
id,
getLoadable().getJavaType().getJavaTypeClass().getName(),
lockOptions,
session,
getReadOnlyFromLoadQueryInfluencers( session )
);
Object managedEntity = null;
// look for it in the Session first
final PersistenceContextEntry persistenceContextEntry =
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
if ( loadOptions.isSessionCheckingEnabled() ) {
managedEntity = persistenceContextEntry.getEntity();
if ( managedEntity != null
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
&& !persistenceContextEntry.isManaged() ) {
foundAnyManagedEntities = true;
result.add( null );
continue;
}
}
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
loadEvent,
getLoadable().getEntityPersister(),
entityKey
);
}
if ( managedEntity != null ) {
foundAnyManagedEntities = true;
//noinspection unchecked
result.add( (T) managedEntity );
}
else {
nonManagedIds.add( id );
}
}
if ( foundAnyManagedEntities ) {
if ( nonManagedIds.isEmpty() ) {
// all the given ids were already associated with the Session
return result;
}
else {
// over-write the ids to be loaded with the collection of
// just non-managed ones
ids = nonManagedIds.toArray(
(Object[]) Array.newInstance(
ids.getClass().getComponentType(),
nonManagedIds.size()
)
);
}
}
}
int numberOfIdsLeft = ids.length;
final int maxBatchSize;
if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) {
maxBatchSize = loadOptions.getBatchSize();
}
else {
maxBatchSize = getBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
getIdentifierMapping().getJdbcTypeCount(),
numberOfIdsLeft,
isInClauseParameterPaddingEnabled()
);
}
int idPosition = 0; int idPosition = 0;
while ( numberOfIdsLeft > 0 ) { while ( numberOfIdsLeft > 0 ) {
final int batchSize = Math.min( numberOfIdsLeft, maxBatchSize ); final int batchSize = Math.min( numberOfIdsLeft, maxBatchSize );
final Object[] idsInBatch = new Object[ batchSize ]; final Object[] idsInBatch = new Object[batchSize];
System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize ); arraycopy( unresolvableIds, idPosition, idsInBatch, 0, batchSize );
result.addAll( listEntitiesById( asList( idsInBatch ), lockOptions, loadOptions, session ) ); result.addAll( listEntitiesById( asList( idsInBatch ), lockOptions, loadOptions, session ) );
numberOfIdsLeft = numberOfIdsLeft - batchSize; numberOfIdsLeft = numberOfIdsLeft - batchSize;
idPosition += batchSize; idPosition += batchSize;
} }
return result;
} }
} }