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;
}
public static boolean isEmpty(Collection collection) {
public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty();
}

View File

@ -4,6 +4,7 @@
*/
package org.hibernate.loader.ast.internal;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
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.type.descriptor.java.JavaType;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
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.LoaderHelper.getReadOnlyFromLoadQueryInfluencers;
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 SessionFactoryImplementor sessionFactory;
private final EntityIdentifierMapping identifierMapping;
protected final Object[] idArray;
public AbstractMultiIdEntityLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.sessionFactory = sessionFactory;
identifierMapping = getLoadable().getIdentifierMapping();
idArray = (Object[]) Array.newInstance( identifierMapping.getJavaType().getJavaTypeClass(), 0 );
}
protected EntityMappingType getEntityDescriptor() {
@ -76,31 +81,43 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
@Override
public final <K> List<T> load(K[] ids, MultiIdLoadOptions loadOptions, EventSource session) {
assert ids != null;
if ( loadOptions.isOrderReturnEnabled() ) {
return performOrderedMultiLoad( ids, loadOptions, session );
}
else {
return performUnorderedMultiLoad( ids, loadOptions, session );
return loadOptions.isOrderReturnEnabled()
? performOrderedMultiLoad( ids, loadOptions, session )
: 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(
Object[] ids,
MultiIdLoadOptions loadOptions,
EventSource session) {
assert loadOptions.isOrderReturnEnabled();
assert ids != null;
if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MULTI_KEY_LOAD_LOGGER.tracef( "#performOrderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() );
}
return orderedMultiLoad( ids, loadOptions, lockOptions( loadOptions ), session );
}
assert loadOptions.isOrderReturnEnabled();
final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled();
private List<T> orderedMultiLoad(
Object[] ids,
MultiIdLoadOptions loadOptions,
LockOptions lockOptions,
EventSource session) {
final boolean idCoercionEnabled = isIdCoercionEnabled();
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 List<Object> result = arrayList( ids.length );
@ -109,10 +126,10 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>();
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() );
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,
// then we need to batch load the entity state.
idsInBatch.add( id );
@ -142,13 +159,27 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
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 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,
EventSource session,
Object id,
@ -157,48 +188,189 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
List<Object> result,
int i) {
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
final LoadEvent loadEvent = new LoadEvent(
id,
getLoadable().getJavaType().getJavaTypeClass().getName(),
lockOptions,
session,
getReadOnlyFromLoadQueryInfluencers( session )
return loadFromCaches( loadOptions, entityKey, result, i,
new LoadEvent(
id,
getLoadable().getJavaType().getJavaTypeClass().getName(),
lockOptions,
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() ) {
// look for it in the Session first
final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry =
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
managedEntity = persistenceContextEntry.getEntity();
if ( loadOptions.isSessionCheckingEnabled() ) {
// look for it in the Session first
final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry =
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
managedEntity = persistenceContextEntry.getEntity();
if ( managedEntity != null
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
&& !persistenceContextEntry.isManaged() ) {
// put a null in the result
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 );
if ( managedEntity != null
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
&& !persistenceContextEntry.isManaged() ) {
// put a null in the result
result.add( i, null );
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;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
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.SqlArrayMultiKeyLoader;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
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.select.SelectStatement;
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.JdbcParameterBindings;
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.spi.ManagedResultConsumer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.hibernate.type.descriptor.java.JavaType;
import static java.lang.Boolean.TRUE;
import static org.hibernate.engine.internal.BatchFetchQueueHelper.removeBatchLoadableEntityKey;
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.LoaderSelectBuilder.createSelectBySingleArrayParameter;
import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.resolveArrayJdbcMapping;
@ -64,11 +48,11 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
EntityMappingType entityDescriptor,
SessionFactoryImplementor sessionFactory) {
super( entityDescriptor, sessionFactory );
final Class<?> arrayClass = createTypedArray( 0 ).getClass();
final Class<?> idArrayClass = idArray.getClass();
arrayJdbcMapping = resolveArrayJdbcMapping(
getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( arrayClass ),
getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( idArrayClass ),
getIdentifierMapping().getJdbcMapping(),
arrayClass,
idArrayClass,
getSessionFactory()
);
jdbcParameter = new JdbcParameterImpl( arrayJdbcMapping );
@ -133,7 +117,7 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(1);
jdbcParameterBindings.addBinding( jdbcParameter,
new JdbcParameterBindingImpl( arrayJdbcMapping, idsInBatch.toArray( createTypedArray(0) ) ) );
new JdbcParameterBindingImpl( arrayJdbcMapping, idsInBatch.toArray( idArray ) ) );
getJdbcSelectExecutor().executeQuery(
getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst )
@ -156,36 +140,12 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
}
@Override
protected <K> List<E> performUnorderedMultiLoad(
K[] ids,
protected void loadEntitiesWithUnresolvedIds(
MultiIdLoadOptions loadOptions,
EventSource session) {
if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) {
MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef(
"MultiIdEntityLoaderArrayParam#performUnorderedMultiLoad - %s",
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;
}
LockOptions lockOptions,
EventSource session,
Object[] unresolvableIds,
List<E> result) {
final SelectStatement sqlAst = createSelectBySingleArrayParameter(
getLoadable(),
getIdentifierMapping(),
@ -194,12 +154,13 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
jdbcParameter,
getSessionFactory()
);
final JdbcOperationQuerySelect jdbcSelectOperation =
getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst )
.translate( NO_BINDINGS, QueryOptions.NONE );
final List<E> databaseResults = loadByArrayParameter(
idsToLoadFromDatabase,
unresolvableIds,
sqlAst,
jdbcSelectOperation,
jdbcParameter,
@ -213,108 +174,14 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
);
result.addAll( databaseResults );
//noinspection ForLoopReplaceableByForEach
for ( int i = 0; i < idsToLoadFromDatabase.length; i++ ) {
final Object id = idsToLoadFromDatabase[i];
if ( id == null ) {
// skip any of the null padded ids
// - actually we could probably even break here
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 );
for ( Object id : unresolvableIds ) {
// skip any of the null padded ids
// (actually we could probably even break on the first null)
if ( id != null ) {
// found or not, remove the key from the batch-fetch queue
removeBatchLoadableEntityKey( id, getLoadable(), session );
}
}
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;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.BatchFetchQueue;
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.SubselectFetch;
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.MultiKeyLoadSizingStrategy;
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.spi.ListResultsConsumer;
import org.hibernate.type.descriptor.java.JavaType;
import static java.lang.Boolean.TRUE;
import static java.lang.System.arraycopy;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
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.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER;
@ -205,123 +195,22 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
}
@Override
protected List<T> performUnorderedMultiLoad(
Object[] ids,
protected void loadEntitiesWithUnresolvedIds(
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() );
}
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()
);
}
LockOptions lockOptions,
EventSource session,
Object[] unresolvableIds,
List<T> result) {
final int maxBatchSize = maxBatchSize( unresolvableIds, loadOptions );
int numberOfIdsLeft = unresolvableIds.length;
int idPosition = 0;
while ( numberOfIdsLeft > 0 ) {
final int batchSize = Math.min( numberOfIdsLeft, maxBatchSize );
final Object[] idsInBatch = new Object[ batchSize ];
System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize );
final Object[] idsInBatch = new Object[batchSize];
arraycopy( unresolvableIds, idPosition, idsInBatch, 0, batchSize );
result.addAll( listEntitiesById( asList( idsInBatch ), lockOptions, loadOptions, session ) );
numberOfIdsLeft = numberOfIdsLeft - batchSize;
idPosition += batchSize;
}
return result;
}
}