From 3d23907bf8c9ac75bf961e1301a30632e118d032 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 25 Oct 2024 11:16:23 +0200 Subject: [PATCH 1/8] HHH-18767 add BatchSize for use with findMultiple() Signed-off-by: Gavin King --- .../main/java/org/hibernate/BatchSize.java | 39 +++++++++++++++++++ .../src/main/java/org/hibernate/Session.java | 3 ++ .../org/hibernate/internal/SessionImpl.java | 9 ++++- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/BatchSize.java diff --git a/hibernate-core/src/main/java/org/hibernate/BatchSize.java b/hibernate-core/src/main/java/org/hibernate/BatchSize.java new file mode 100644 index 000000000000..cf982b6ad139 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/BatchSize.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +import jakarta.persistence.FindOption; + +import java.util.List; + +/** + * Specify a batch size, that is, how many entities should be + * fetched in each request to the database, for an invocation of + * {@link Session#findMultiple(Class, List, FindOption...)}. + * + *

+ * If an explicit batch size is set manually, care should be taken + * to not exceed the capabilities of the underlying database. + *

+ * A batch size is considered a hint. This option has no effect + * on {@link Session#find(Class, Object, FindOption...)}. + * + * @param batchSize The batch size + * + * @see Session#findMultiple + * @see MultiIdentifierLoadAccess#withBatchSize + * + * @since 7.0 + * + * @author Gavin King + */ +public record BatchSize(int batchSize) implements FindOption { +} diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 7f91fda5e7b2..b4ff373e27eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -561,6 +561,9 @@ public interface Session extends SharedSessionContract, EntityManager { * Every object returned by {@code findMultiple()} is either an unproxied instance of the * given entity class, or a fully-fetched proxy object. *

+ * This method accepts {@link BatchSize} as an option, allowing control over the number of + * records retrieved in a single database request. + *

* For more advanced cases, use {@link #byMultipleIds(Class)}, which returns an instance of * {@link MultiIdentifierLoadAccess}. * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index e3dd9532d47c..ae5b295fadb5 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -24,6 +24,7 @@ import jakarta.persistence.PessimisticLockScope; import jakarta.persistence.Timeout; +import org.hibernate.BatchSize; import org.hibernate.CacheMode; import org.hibernate.ConnectionAcquisitionMode; import org.hibernate.EntityFilterException; @@ -950,6 +951,7 @@ private MultiIdentifierLoadAccess multiloadAccessWithOptions(Class ent CacheStoreMode storeMode = getCacheStoreMode(); CacheRetrieveMode retrieveMode = getCacheRetrieveMode(); LockOptions lockOptions = copySessionLockOptions(); + int batchSize = -1; for ( FindOption option : options ) { if ( option instanceof CacheStoreMode cacheStoreMode ) { storeMode = cacheStoreMode; @@ -982,8 +984,13 @@ else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) { else if ( option instanceof ReadOnlyMode ) { loadAccess.withReadOnly( option == ReadOnlyMode.READ_ONLY ); } + else if ( option instanceof BatchSize batchSizeOption ) { + batchSize = batchSizeOption.batchSize(); + } } - loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) ); + loadAccess.with( lockOptions ) + .with( interpretCacheMode( storeMode, retrieveMode ) ) + .withBatchSize( batchSize ); return loadAccess; } From 7ac433190f4b2e29341c37999a9fbe1d17814e15 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 25 Oct 2024 22:01:47 +0200 Subject: [PATCH 2/8] HHH-18767 make MultiIdEntityLoaderArrayParam respect explicit BatchSize keep ignoring the *implicit* upper limit from the Dialect refactor a very long method which was extremely hard to understand Signed-off-by: Gavin King --- .../MultiIdEntityLoaderArrayParam.java | 221 +++++++++++------- .../internal/MultiIdEntityLoaderStandard.java | 182 ++++++++------- .../entity/AbstractEntityPersister.java | 11 +- 3 files changed, 234 insertions(+), 180 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java index 9372168d7992..f5f20e5cbf0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java @@ -40,9 +40,11 @@ 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.internal.util.collections.CollectionHelper.isEmpty; +import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic; /** * @author Steve Ebersole @@ -50,9 +52,11 @@ public class MultiIdEntityLoaderArrayParam extends AbstractMultiIdEntityLoader implements SqlArrayMultiKeyLoader { private final JdbcMapping arrayJdbcMapping; private final JdbcParameter jdbcParameter; + private final int idJdbcTypeCount; - public MultiIdEntityLoaderArrayParam(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) { + public MultiIdEntityLoaderArrayParam(EntityMappingType entityDescriptor, int identifierColumnSpan, SessionFactoryImplementor sessionFactory) { super( entityDescriptor, sessionFactory ); + this.idJdbcTypeCount = identifierColumnSpan; final Class arrayClass = createTypedArray( 0 ).getClass(); arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping( getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( arrayClass ), @@ -77,88 +81,152 @@ protected List performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp ); } + assert loadOptions.isOrderReturnEnabled(); + final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); - final LockOptions lockOptions = (loadOptions.getLockOptions() == null) + 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 result = CollectionHelper.arrayList( ids.length ); - List idsToLoadFromDatabase = null; - List idsToLoadFromDatabaseResultIndexes = null; - for ( int i = 0; i < ids.length; i++ ) { - final Object id; - if ( coerce ) { - id = getLoadable().getIdentifierMapping().getJavaType().coerce( ids[ i ], session ); - } - else { - id = ids[ i ]; - } + final List idsInBatch = new ArrayList<>(); + final List elementPositionsLoadedByBatch = new ArrayList<>(); + 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() ); - if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { - LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers(session) - ); + if ( !loadFromCaches( 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 ); - Object managedEntity = null; - - if ( loadOptions.isSessionCheckingEnabled() ) { - // look for it in the Session first - final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.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 ); - continue; - } + if ( idsInBatch.size() >= maxBatchSize ) { + // we've hit the allotted max-batch-size, perform an "intermediate load" + loadEntitiesById( loadOptions, session, lockOptions, idsInBatch ); + idsInBatch.clear(); } - if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { - // look for it in the SessionFactory - managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey - ); - } + // Save the EntityKey instance for use later + result.add( i, entityKey ); + elementPositionsLoadedByBatch.add( i ); + } + } - if ( managedEntity != null ) { - result.add( i, managedEntity ); - continue; + if ( !idsInBatch.isEmpty() ) { + // we still have ids to load from the processing above since + // the last max-batch-size trigger, perform a load for them + loadEntitiesById( loadOptions, session, lockOptions, idsInBatch ); + } + + // for each result where we set the EntityKey earlier, replace them + handleResults( loadOptions, session, elementPositionsLoadedByBatch, result ); + + //noinspection unchecked + return (List) result; + } + + private boolean loadFromCaches( + MultiIdLoadOptions loadOptions, + EventSource session, + Object id, + LockOptions lockOptions, + EntityKey entityKey, + List result, + int i) { + if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { + final LoadEvent loadEvent = new LoadEvent( + id, + getLoadable().getJavaType().getJavaTypeClass().getName(), + lockOptions, + session, + LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) + ); + + Object managedEntity = null; + + if ( loadOptions.isSessionCheckingEnabled() ) { + // look for it in the Session first + final 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 we did not hit any of the continues above, then we need to batch - // load the entity state. - if ( idsToLoadFromDatabase == null ) { - idsToLoadFromDatabase = new ArrayList<>(); - idsToLoadFromDatabaseResultIndexes = new ArrayList<>(); + 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; } - // hold its place in the result with the EntityKey, we'll come back to it later - result.add( i, entityKey ); - idsToLoadFromDatabase.add( id ); - idsToLoadFromDatabaseResultIndexes.add( i ); } + return false; + } - if ( idsToLoadFromDatabase == null ) { - // all the given ids were already associated with the Session - //noinspection unchecked - return (List) result; + private static void handleResults( + MultiIdLoadOptions loadOptions, + EventSource session, + List elementPositionsLoadedByBatch, + List result) { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + for ( Integer position : elementPositionsLoadedByBatch ) { + // the element value at this position in the result List should be + // the EntityKey for that entity - reuse it + final EntityKey entityKey = (EntityKey) result.get( position ); + BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session ); + Object entity = persistenceContext.getEntity( entityKey ); + if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) { + // make sure it is not DELETED + final EntityEntry entry = persistenceContext.getEntry( entity ); + if ( entry.getStatus().isDeletedOrGone() ) { + // the entity is locally deleted, and the options ask that we not return such entities... + entity = null; + } + else { + entity = persistenceContext.proxyFor( entity ); + } + } + result.set( position, entity ); + } + } + + private int maxBatchSize(K[] ids, MultiIdLoadOptions loadOptions) { + if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { + return loadOptions.getBatchSize(); + } + else { + // disable batching by default + return ids.length; +// return getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect() +// .getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( +// idJdbcTypeCount, +// ids.length, +// getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() +// ); } + } + private void loadEntitiesById( + MultiIdLoadOptions loadOptions, EventSource session, LockOptions lockOptions, List idsToLoadFromDatabase) { final SelectStatement sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter( getLoadable(), getIdentifierMapping(), @@ -194,37 +262,12 @@ protected List performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp jdbcParameterBindings, new ExecutionContextWithSubselectFetchHandler( session, subSelectFetchableKeysHandler, - TRUE.equals( loadOptions.getReadOnly(session) ) ), + TRUE.equals( loadOptions.getReadOnly( session ) ) ), RowTransformerStandardImpl.instance(), null, idsToLoadFromDatabase.size(), ManagedResultConsumer.INSTANCE ); - - for ( int i = 0; i < idsToLoadFromDatabaseResultIndexes.size(); i++ ) { - final Integer resultIndex = idsToLoadFromDatabaseResultIndexes.get(i); - - // the element value at this position in the result List should be - // the EntityKey for that entity - reuse it - final EntityKey entityKey = (EntityKey) result.get( resultIndex ); - BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session ); - Object entity = persistenceContext.getEntity( entityKey ); - if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) { - // make sure it is not DELETED - final EntityEntry entry = persistenceContext.getEntry( entity ); - if ( entry.getStatus().isDeletedOrGone() ) { - // the entity is locally deleted, and the options ask that we not return such entities... - entity = null; - } - else { - entity = persistenceContext.proxyFor( entity ); - } - } - result.set( resultIndex, entity ); - } - - //noinspection unchecked - return (List) result; } @@ -345,7 +388,7 @@ protected final K[] processResolvableEntities( Object resolvedEntity = null; // look for it in the Session first - final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.loadFromSessionCacheStatic( + final PersistenceContextEntry persistenceContextEntry = loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java index 391ef3ce2fe0..e817bb6a1ff0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java @@ -12,8 +12,6 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; @@ -24,6 +22,7 @@ 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.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryOptions; @@ -36,9 +35,11 @@ import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; +import org.hibernate.type.descriptor.java.JavaType; import org.jboss.logging.Logger; import static java.lang.Boolean.TRUE; +import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic; /** * Standard MultiIdEntityLoader @@ -71,108 +72,110 @@ protected List performOrderedMultiLoad( assert loadOptions.isOrderReturnEnabled(); - final JdbcEnvironment jdbcEnvironment = getSessionFactory().getJdbcServices().getJdbcEnvironment(); - final Dialect dialect = jdbcEnvironment.getDialect(); - - final List result = CollectionHelper.arrayList( ids.length ); + final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); + final JavaType idType = getLoadable().getIdentifierMapping().getJavaType(); - final LockOptions lockOptions = (loadOptions.getLockOptions() == null) + final LockOptions lockOptions = loadOptions.getLockOptions() == null ? new LockOptions( LockMode.NONE ) : loadOptions.getLockOptions(); - final int maxBatchSize; - if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { - maxBatchSize = loadOptions.getBatchSize(); - } - else { - maxBatchSize = dialect.getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( - idJdbcTypeCount, - ids.length, - getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() - ); - } + final int maxBatchSize = maxBatchSize( ids, loadOptions ); + + final List result = CollectionHelper.arrayList( ids.length ); final List idsInBatch = new ArrayList<>(); final List elementPositionsLoadedByBatch = new ArrayList<>(); - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); for ( int i = 0; i < ids.length; i++ ) { - final Object id; - if ( coerce ) { - id = getLoadable().getIdentifierMapping().getJavaType().coerce( ids[i], session ); - } - else { - id = ids[i]; - } + final Object id = coerce ? idType.coerce( ids[i], session ) : ids[i]; final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); - if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { - LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers(session) - ); + if ( !loadFromCaches( 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 ); - Object managedEntity = null; + if ( idsInBatch.size() >= maxBatchSize ) { + // we've hit the allotted max-batch-size, perform an "intermediate load" + loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); + idsInBatch.clear(); + } - if ( loadOptions.isSessionCheckingEnabled() ) { - // look for it in the Session first - CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE - .loadFromSessionCache( - loadEvent, - entityKey, - LoadEventListener.GET - ); - managedEntity = persistenceContextEntry.getEntity(); + // Save the EntityKey instance for use later + result.add( i, entityKey ); + elementPositionsLoadedByBatch.add( i ); + } + } - if ( managedEntity != null - && !loadOptions.isReturnOfDeletedEntitiesEnabled() - && !persistenceContextEntry.isManaged() ) { - // put a null in the result - result.add( i, null ); - continue; - } - } + if ( !idsInBatch.isEmpty() ) { + // we still have ids to load from the processing above since + // the last max-batch-size trigger, perform a load for them + loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); + } - if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { - // look for it in the SessionFactory - managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey - ); - } + // for each result where we set the EntityKey earlier, replace them + handleResults( loadOptions, session, elementPositionsLoadedByBatch, result ); - if ( managedEntity != null ) { - result.add( i, managedEntity ); - continue; - } - } + //noinspection unchecked + return (List) result; + } + + private boolean loadFromCaches( + MultiIdLoadOptions loadOptions, + EventSource session, + Object id, + LockOptions lockOptions, + EntityKey entityKey, + List result, + int i) { + if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { + final LoadEvent loadEvent = new LoadEvent( + id, + getLoadable().getJavaType().getJavaTypeClass().getName(), + lockOptions, + session, + LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) + ); - // if we did not hit any of the continues above, then we need to batch - // load the entity state. - idsInBatch.add( id ); + Object managedEntity = null; - if ( idsInBatch.size() >= maxBatchSize ) { - // we've hit the allotted max-batch-size, perform an "intermediate load" - loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); - idsInBatch.clear(); + if ( loadOptions.isSessionCheckingEnabled() ) { + // look for it in the Session first + final 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; + } } - // Save the EntityKey instance for use later - result.add( i, entityKey ); - elementPositionsLoadedByBatch.add( i ); - } + if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { + // look for it in the SessionFactory + managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + loadEvent, + getLoadable().getEntityPersister(), + entityKey + ); + } - if ( !idsInBatch.isEmpty() ) { - // we still have ids to load from the processing above since the last max-batch-size trigger, - // perform a load for them - loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); + if ( managedEntity != null ) { + result.add( i, managedEntity ); + return true; + } } + return false; + } - // for each result where we set the EntityKey earlier, replace them + private static void handleResults( + MultiIdLoadOptions loadOptions, + EventSource session, + List elementPositionsLoadedByBatch, + List result) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); for ( Integer position : elementPositionsLoadedByBatch ) { // the element value at this position in the result List should be @@ -192,9 +195,20 @@ protected List performOrderedMultiLoad( } result.set( position, entity ); } + } - //noinspection unchecked - return (List) result; + private int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { + if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { + return loadOptions.getBatchSize(); + } + else { + return getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect() + .getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( + idJdbcTypeCount, + ids.length, + getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() + ); + } } private List loadEntitiesById( @@ -334,7 +348,7 @@ protected List performUnorderedMultiLoad( Object managedEntity = null; // look for it in the Session first - CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE + PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE .loadFromSessionCache( loadEvent, entityKey, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 67070b132524..79b631871519 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -855,13 +855,10 @@ public static Map getEntityNameByTableNameMap( } protected MultiIdEntityLoader buildMultiIdLoader() { - if ( getIdentifierType() instanceof BasicType - && supportsSqlArrayType( factory.getJdbcServices().getDialect() ) ) { - return new MultiIdEntityLoaderArrayParam<>( this, factory ); - } - else { - return new MultiIdEntityLoaderStandard<>( this, identifierColumnSpan, factory ); - } + final Dialect dialect = factory.getJdbcServices().getDialect(); + return getIdentifierType() instanceof BasicType && supportsSqlArrayType( dialect ) + ? new MultiIdEntityLoaderArrayParam<>( this, identifierColumnSpan, factory ) + : new MultiIdEntityLoaderStandard<>( this, identifierColumnSpan, factory ); } private String getIdentitySelectString(Dialect dialect) { From 61fa7004616a621f4d6eb08c693f6390295eba93 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 25 Oct 2024 23:01:37 +0200 Subject: [PATCH 3/8] partial refactoring of AbstractMultiIdEntityLoader and children Signed-off-by: Gavin King --- .../internal/AbstractMultiIdEntityLoader.java | 126 +++++++- .../MultiIdEntityLoaderArrayParam.java | 161 ++-------- .../internal/MultiIdEntityLoaderStandard.java | 281 ++++++------------ 3 files changed, 236 insertions(+), 332 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java index 38501fcae14f..06bbafbeea81 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java @@ -4,15 +4,25 @@ */ package org.hibernate.loader.ast.internal; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.EntityKey; 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.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.type.descriptor.java.JavaType; +import java.util.ArrayList; import java.util.List; +import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic; + /** * Base support for {@link MultiIdEntityLoader} implementations. * @@ -57,7 +67,121 @@ public final List load(K[] ids, MultiIdLoadOptions loadOptions, EventSour } } - protected abstract List performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session); + protected List performOrderedMultiLoad( + Object[] ids, + MultiIdLoadOptions loadOptions, + EventSource session) { + if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { + MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "#performOrderedMultiLoad(`%s`, ..)", + getLoadable().getEntityName() ); + } + + assert loadOptions.isOrderReturnEnabled(); + + final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); + 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 result = CollectionHelper.arrayList( ids.length ); + + final List idsInBatch = new ArrayList<>(); + final List elementPositionsLoadedByBatch = new ArrayList<>(); + + 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() ); + + if ( !loadFromCaches( 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 ); + + if ( idsInBatch.size() >= maxBatchSize ) { + // we've hit the allotted max-batch-size, perform an "intermediate load" + loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); + idsInBatch.clear(); + } + + // Save the EntityKey instance for use later + result.add( i, entityKey ); + elementPositionsLoadedByBatch.add( i ); + } + } + + if ( !idsInBatch.isEmpty() ) { + // we still have ids to load from the processing above since + // the last max-batch-size trigger, perform a load for them + loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); + } + + // for each result where we set the EntityKey earlier, replace them + handleResults( loadOptions, session, elementPositionsLoadedByBatch, result ); + + //noinspection unchecked + return (List) result; + } + + protected abstract int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions); + + protected abstract void handleResults(MultiIdLoadOptions loadOptions, EventSource session, List elementPositionsLoadedByBatch, List result); + + protected abstract void loadEntitiesById(List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, EventSource session); + + protected boolean loadFromCaches( + MultiIdLoadOptions loadOptions, + EventSource session, + Object id, + LockOptions lockOptions, + EntityKey entityKey, + List result, + int i) { + if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { + final LoadEvent loadEvent = new LoadEvent( + id, + getLoadable().getJavaType().getJavaTypeClass().getName(), + lockOptions, + session, + LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) + ); + + 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 ( 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 ); + return true; + } + } + return false; + } protected abstract List performUnorderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java index f5f20e5cbf0f..e5c10d1ff4e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java @@ -73,116 +73,7 @@ public BasicEntityIdentifierMapping getIdentifierMapping() { } @Override - protected List performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session) { - if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { - MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( - "MultiIdEntityLoaderArrayParam#performOrderedMultiLoad - %s", - getLoadable().getEntityName() - ); - } - - assert loadOptions.isOrderReturnEnabled(); - - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); - 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 result = CollectionHelper.arrayList( ids.length ); - - final List idsInBatch = new ArrayList<>(); - final List elementPositionsLoadedByBatch = new ArrayList<>(); - - 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() ); - - if ( !loadFromCaches( 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 ); - - if ( idsInBatch.size() >= maxBatchSize ) { - // we've hit the allotted max-batch-size, perform an "intermediate load" - loadEntitiesById( loadOptions, session, lockOptions, idsInBatch ); - idsInBatch.clear(); - } - - // Save the EntityKey instance for use later - result.add( i, entityKey ); - elementPositionsLoadedByBatch.add( i ); - } - } - - if ( !idsInBatch.isEmpty() ) { - // we still have ids to load from the processing above since - // the last max-batch-size trigger, perform a load for them - loadEntitiesById( loadOptions, session, lockOptions, idsInBatch ); - } - - // for each result where we set the EntityKey earlier, replace them - handleResults( loadOptions, session, elementPositionsLoadedByBatch, result ); - - //noinspection unchecked - return (List) result; - } - - private boolean loadFromCaches( - MultiIdLoadOptions loadOptions, - EventSource session, - Object id, - LockOptions lockOptions, - EntityKey entityKey, - List result, - int i) { - if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { - final LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) - ); - - Object managedEntity = null; - - if ( loadOptions.isSessionCheckingEnabled() ) { - // look for it in the Session first - final 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 ); - return true; - } - } - return false; - } - - private static void handleResults( + protected void handleResults( MultiIdLoadOptions loadOptions, EventSource session, List elementPositionsLoadedByBatch, @@ -209,7 +100,8 @@ private static void handleResults( } } - private int maxBatchSize(K[] ids, MultiIdLoadOptions loadOptions) { + @Override + protected int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { return loadOptions.getBatchSize(); } @@ -225,8 +117,12 @@ private int maxBatchSize(K[] ids, MultiIdLoadOptions loadOptions) { } } - private void loadEntitiesById( - MultiIdLoadOptions loadOptions, EventSource session, LockOptions lockOptions, List idsToLoadFromDatabase) { + @Override + protected void loadEntitiesById( + List idsToLoadFromDatabase, + LockOptions lockOptions, + MultiIdLoadOptions loadOptions, + EventSource session) { final SelectStatement sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter( getLoadable(), getIdentifierMapping(), @@ -361,22 +257,16 @@ protected final K[] processResolvableEntities( return ids; } - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); - boolean foundAnyResolvedEntities = false; List nonResolvedIds = null; - for ( int i = 0; i < ids.length; i++ ) { - final Object id; - if ( coerce ) { - //noinspection unchecked - id = (K) getLoadable().getIdentifierMapping().getJavaType().coerce( ids[i], session ); - } - else { - id = ids[i]; - } + 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(), @@ -385,18 +275,15 @@ protected final K[] processResolvableEntities( LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) ); - Object resolvedEntity = null; + Object managedEntity = null; // look for it in the Session first - final PersistenceContextEntry persistenceContextEntry = loadFromSessionCacheStatic( - loadEvent, - entityKey, - LoadEventListener.GET - ); + final PersistenceContextEntry persistenceContextEntry = + loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET ); if ( loadOptions.isSessionCheckingEnabled() ) { - resolvedEntity = persistenceContextEntry.getEntity(); + managedEntity = persistenceContextEntry.getEntity(); - if ( resolvedEntity != null + if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry.isManaged() ) { foundAnyResolvedEntities = true; @@ -405,25 +292,25 @@ protected final K[] processResolvableEntities( } } - if ( resolvedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { - resolvedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { + managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( loadEvent, getLoadable().getEntityPersister(), entityKey ); } - if ( resolvedEntity != null ) { + if ( managedEntity != null ) { foundAnyResolvedEntities = true; //noinspection unchecked - resolutionConsumer.consume( i, entityKey, (R) resolvedEntity); + resolutionConsumer.consume( i, entityKey, (R) managedEntity); } else { if ( nonResolvedIds == null ) { nonResolvedIds = new ArrayList<>(); } - //noinspection unchecked,CastCanBeRemovedNarrowingVariableType + //noinspection unchecked nonResolvedIds.add( (K) id ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java index e817bb6a1ff0..5533a19fc977 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java @@ -6,7 +6,6 @@ import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,6 +38,7 @@ import org.jboss.logging.Logger; import static java.lang.Boolean.TRUE; +import static java.util.Arrays.asList; import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic; /** @@ -62,116 +62,7 @@ public MultiIdEntityLoaderStandard( } @Override - protected List performOrderedMultiLoad( - Object[] ids, - MultiIdLoadOptions loadOptions, - EventSource session) { - if ( log.isTraceEnabled() ) { - log.tracef( "#performOrderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() ); - } - - assert loadOptions.isOrderReturnEnabled(); - - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); - 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 result = CollectionHelper.arrayList( ids.length ); - - final List idsInBatch = new ArrayList<>(); - final List elementPositionsLoadedByBatch = new ArrayList<>(); - - 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() ); - - if ( !loadFromCaches( 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 ); - - if ( idsInBatch.size() >= maxBatchSize ) { - // we've hit the allotted max-batch-size, perform an "intermediate load" - loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); - idsInBatch.clear(); - } - - // Save the EntityKey instance for use later - result.add( i, entityKey ); - elementPositionsLoadedByBatch.add( i ); - } - } - - if ( !idsInBatch.isEmpty() ) { - // we still have ids to load from the processing above since - // the last max-batch-size trigger, perform a load for them - loadEntitiesById( idsInBatch, lockOptions, loadOptions, session ); - } - - // for each result where we set the EntityKey earlier, replace them - handleResults( loadOptions, session, elementPositionsLoadedByBatch, result ); - - //noinspection unchecked - return (List) result; - } - - private boolean loadFromCaches( - MultiIdLoadOptions loadOptions, - EventSource session, - Object id, - LockOptions lockOptions, - EntityKey entityKey, - List result, - int i) { - if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { - final LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) - ); - - Object managedEntity = null; - - if ( loadOptions.isSessionCheckingEnabled() ) { - // look for it in the Session first - final 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 ); - return true; - } - } - return false; - } - - private static void handleResults( + protected void handleResults( MultiIdLoadOptions loadOptions, EventSource session, List elementPositionsLoadedByBatch, @@ -197,7 +88,8 @@ private static void handleResults( } } - private int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { + @Override + protected int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { return loadOptions.getBatchSize(); } @@ -211,86 +103,97 @@ private int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { } } - private List loadEntitiesById( + @Override + protected void loadEntitiesById( List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, EventSource session) { assert idsInBatch != null; - assert ! idsInBatch.isEmpty(); + assert !idsInBatch.isEmpty(); + listEntitiesById( idsInBatch, lockOptions, loadOptions, session ); + } + + private List listEntitiesById( + List idsInBatch, + LockOptions lockOptions, + MultiIdLoadOptions loadOptions, + EventSource session) { final int numberOfIdsInBatch = idsInBatch.size(); if ( numberOfIdsInBatch == 1 ) { return performSingleMultiLoad( idsInBatch.get( 0 ), lockOptions, session ); } + else { + if ( log.isTraceEnabled() ) { + log.tracef( "#loadEntitiesById(`%s`, `%s`, ..)", getLoadable().getEntityName(), numberOfIdsInBatch ); + } - if ( log.isTraceEnabled() ) { - log.tracef( "#loadEntitiesById(`%s`, `%s`, ..)", getLoadable().getEntityName(), numberOfIdsInBatch ); - } + final JdbcParametersList.Builder jdbcParametersBuilder = + JdbcParametersList.newBuilder( numberOfIdsInBatch * idJdbcTypeCount ); - JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder( numberOfIdsInBatch * idJdbcTypeCount ); - - final SelectStatement sqlAst = LoaderSelectBuilder.createSelect( - getLoadable(), - // null here means to select everything - null, - getLoadable().getIdentifierMapping(), - null, - numberOfIdsInBatch, - session.getLoadQueryInfluencers(), - lockOptions, - jdbcParametersBuilder::add, - getSessionFactory() - ); - JdbcParametersList jdbcParameters = jdbcParametersBuilder.build(); - - final SqlAstTranslatorFactory sqlAstTranslatorFactory = - getSessionFactory().getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); - - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); - int offset = 0; - - for ( int i = 0; i < numberOfIdsInBatch; i++ ) { - final Object id = idsInBatch.get( i ); - - offset += jdbcParameterBindings.registerParametersForEachJdbcValue( - id, - offset, + final SelectStatement sqlAst = LoaderSelectBuilder.createSelect( + getLoadable(), + // null here means to select everything + null, getLoadable().getIdentifierMapping(), - jdbcParameters, - session + null, + numberOfIdsInBatch, + session.getLoadQueryInfluencers(), + lockOptions, + jdbcParametersBuilder::add, + getSessionFactory() ); - } + JdbcParametersList jdbcParameters = jdbcParametersBuilder.build(); + + final SqlAstTranslatorFactory sqlAstTranslatorFactory = + getSessionFactory().getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); - // we should have used all the JdbcParameter references (created bindings for all) - assert offset == jdbcParameters.size(); - final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( getSessionFactory(), sqlAst ) - .translate( jdbcParameterBindings, QueryOptions.NONE ); - - final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; - if ( session.getLoadQueryInfluencers().hasSubselectLoadableCollections( getLoadable().getEntityPersister() ) ) { - subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler( - session.getPersistenceContext().getBatchFetchQueue(), - sqlAst, - jdbcParameters, - jdbcParameterBindings + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); + int offset = 0; + + for ( int i = 0; i < numberOfIdsInBatch; i++ ) { + final Object id = idsInBatch.get( i ); + + offset += jdbcParameterBindings.registerParametersForEachJdbcValue( + id, + offset, + getLoadable().getIdentifierMapping(), + jdbcParameters, + session + ); + } + + // we should have used all the JdbcParameter references (created bindings for all) + assert offset == jdbcParameters.size(); + final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( getSessionFactory(), sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); + + final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; + if ( session.getLoadQueryInfluencers().hasSubselectLoadableCollections( getLoadable().getEntityPersister() ) ) { + subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext().getBatchFetchQueue(), + sqlAst, + jdbcParameters, + jdbcParameterBindings + ); + } + else { + subSelectFetchableKeysHandler = null; + } + + return session.getJdbcServices().getJdbcSelectExecutor().list( + jdbcSelect, + jdbcParameterBindings, + new ExecutionContextWithSubselectFetchHandler( session, + subSelectFetchableKeysHandler, + TRUE.equals( loadOptions.getReadOnly( session ) ) ), + RowTransformerStandardImpl.instance(), + null, + ListResultsConsumer.UniqueSemantic.FILTER, + idsInBatch.size() ); } - else { - subSelectFetchableKeysHandler = null; - } - - return session.getJdbcServices().getJdbcSelectExecutor().list( - jdbcSelect, - jdbcParameterBindings, - new ExecutionContextWithSubselectFetchHandler( session, - subSelectFetchableKeysHandler, - TRUE.equals( loadOptions.getReadOnly(session) ) ), - RowTransformerStandardImpl.instance(), - null, - ListResultsConsumer.UniqueSemantic.FILTER, - idsInBatch.size() - ); } private List performSingleMultiLoad(Object id, LockOptions lockOptions, SharedSessionContractImplementor session) { @@ -313,7 +216,7 @@ protected List performUnorderedMultiLoad( final List result = CollectionHelper.arrayList( ids.length ); - final LockOptions lockOptions = (loadOptions.getLockOptions() == null) + final LockOptions lockOptions = loadOptions.getLockOptions() == null ? new LockOptions( LockMode.NONE ) : loadOptions.getLockOptions(); @@ -327,17 +230,13 @@ protected List performUnorderedMultiLoad( final List 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; - if ( coerce ) { - id = getLoadable().getIdentifierMapping().getJavaType().coerce( ids[i], session ); - } - else { - id = ids[i]; - } + final Object id = coerce ? idType.coerce( ids[i], session ) : ids[i]; final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); - LoadEvent loadEvent = new LoadEvent( + final LoadEvent loadEvent = new LoadEvent( id, getLoadable().getJavaType().getJavaTypeClass().getName(), lockOptions, @@ -348,12 +247,8 @@ protected List performUnorderedMultiLoad( Object managedEntity = null; // look for it in the Session first - PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE - .loadFromSessionCache( - loadEvent, - entityKey, - LoadEventListener.GET - ); + final PersistenceContextEntry persistenceContextEntry = + loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET ); if ( loadOptions.isSessionCheckingEnabled() ) { managedEntity = persistenceContextEntry.getEntity(); @@ -386,7 +281,7 @@ protected List performUnorderedMultiLoad( if ( foundAnyManagedEntities ) { if ( nonManagedIds.isEmpty() ) { - // all of the given ids were already associated with the Session + // all the given ids were already associated with the Session return result; } else { @@ -422,9 +317,7 @@ protected List performUnorderedMultiLoad( final Object[] idsInBatch = new Object[ batchSize ]; System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize ); - result.addAll( - loadEntitiesById( Arrays.asList( idsInBatch ), lockOptions, loadOptions, session ) - ); + result.addAll( listEntitiesById( asList( idsInBatch ), lockOptions, loadOptions, session ) ); numberOfIdsLeft = numberOfIdsLeft - batchSize; idPosition += batchSize; From 875b99c4ecd35161c1d5fdaa0202a4cceeb7e0b6 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 26 Oct 2024 10:12:03 +0200 Subject: [PATCH 4/8] further cleanups on multi id loaders Signed-off-by: Gavin King --- .../internal/AbstractMultiIdEntityLoader.java | 28 ++- .../MultiIdEntityLoaderArrayParam.java | 102 ++++----- .../internal/MultiIdEntityLoaderStandard.java | 196 +++++++++--------- .../entity/AbstractEntityPersister.java | 2 +- 4 files changed, 164 insertions(+), 164 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java index 06bbafbeea81..84f089c297d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java @@ -6,22 +6,27 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityKey; 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.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.type.descriptor.java.JavaType; import java.util.ArrayList; import java.util.List; +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.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; /** * Base support for {@link MultiIdEntityLoader} implementations. @@ -51,6 +56,18 @@ public EntityIdentifierMapping getIdentifierMapping() { return identifierMapping; } + protected JdbcServices getJdbcServices() { + return getSessionFactory().getJdbcServices(); + } + + protected SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); + } + + protected JdbcSelectExecutor getJdbcSelectExecutor() { + return getJdbcServices().getJdbcSelectExecutor(); + } + @Override public EntityMappingType getLoadable() { return getEntityDescriptor(); @@ -71,9 +88,8 @@ protected List performOrderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, EventSource session) { - if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { - MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "#performOrderedMultiLoad(`%s`, ..)", - getLoadable().getEntityName() ); + if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { + MULTI_KEY_LOAD_LOGGER.tracef( "#performOrderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() ); } assert loadOptions.isOrderReturnEnabled(); @@ -87,7 +103,7 @@ protected List performOrderedMultiLoad( final int maxBatchSize = maxBatchSize( ids, loadOptions ); - final List result = CollectionHelper.arrayList( ids.length ); + final List result = arrayList( ids.length ); final List idsInBatch = new ArrayList<>(); final List elementPositionsLoadedByBatch = new ArrayList<>(); @@ -146,7 +162,7 @@ protected boolean loadFromCaches( getLoadable().getJavaType().getJavaTypeClass().getName(), lockOptions, session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) + getReadOnlyFromLoadQueryInfluencers( session ) ); Object managedEntity = null; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java index e5c10d1ff4e2..cd8b428bf64d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java @@ -10,13 +10,11 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; -import org.hibernate.engine.spi.BatchFetchQueue; +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.engine.spi.SubselectFetch; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; @@ -28,6 +26,7 @@ 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; @@ -36,6 +35,7 @@ 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; @@ -43,8 +43,15 @@ 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; +import static org.hibernate.sql.exec.spi.JdbcParameterBindings.NO_BINDINGS; /** * @author Steve Ebersole @@ -52,13 +59,13 @@ public class MultiIdEntityLoaderArrayParam extends AbstractMultiIdEntityLoader implements SqlArrayMultiKeyLoader { private final JdbcMapping arrayJdbcMapping; private final JdbcParameter jdbcParameter; - private final int idJdbcTypeCount; - public MultiIdEntityLoaderArrayParam(EntityMappingType entityDescriptor, int identifierColumnSpan, SessionFactoryImplementor sessionFactory) { + public MultiIdEntityLoaderArrayParam( + EntityMappingType entityDescriptor, + SessionFactoryImplementor sessionFactory) { super( entityDescriptor, sessionFactory ); - this.idJdbcTypeCount = identifierColumnSpan; final Class arrayClass = createTypedArray( 0 ).getClass(); - arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping( + arrayJdbcMapping = resolveArrayJdbcMapping( getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( arrayClass ), getIdentifierMapping().getJdbcMapping(), arrayClass, @@ -83,7 +90,7 @@ protected void handleResults( // the element value at this position in the result List should be // the EntityKey for that entity - reuse it final EntityKey entityKey = (EntityKey) result.get( position ); - BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session ); + removeBatchLoadableEntityKey( entityKey, session ); Object entity = persistenceContext.getEntity( entityKey ); if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) { // make sure it is not DELETED @@ -102,28 +109,20 @@ protected void handleResults( @Override protected int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { - if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { - return loadOptions.getBatchSize(); - } - else { - // disable batching by default - return ids.length; -// return getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect() -// .getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( -// idJdbcTypeCount, -// ids.length, -// getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() -// ); - } + final Integer explicitBatchSize = loadOptions.getBatchSize(); + return explicitBatchSize != null && explicitBatchSize > 0 + ? explicitBatchSize + // disable batching by default + : ids.length; } @Override protected void loadEntitiesById( - List idsToLoadFromDatabase, + List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, EventSource session) { - final SelectStatement sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter( + final SelectStatement sqlAst = createSelectBySingleArrayParameter( getLoadable(), getIdentifierMapping(), session.getLoadQueryInfluencers(), @@ -131,42 +130,31 @@ protected void loadEntitiesById( jdbcParameter, getSessionFactory() ); - final JdbcOperationQuerySelect jdbcSelectOperation = getSessionFactory().getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( getSessionFactory(), sqlAst ) - .translate( JdbcParameterBindings.NO_BINDINGS, QueryOptions.NONE ); final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(1); - jdbcParameterBindings.addBinding( - jdbcParameter, - new JdbcParameterBindingImpl( arrayJdbcMapping, idsToLoadFromDatabase.toArray( createTypedArray(0 ) ) ) - ); - - final PersistenceContext persistenceContext = session.getPersistenceContext(); - final BatchFetchQueue batchFetchQueue = persistenceContext.getBatchFetchQueue(); + jdbcParameterBindings.addBinding( jdbcParameter, + new JdbcParameterBindingImpl( arrayJdbcMapping, idsInBatch.toArray( createTypedArray(0) ) ) ); - final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler( - batchFetchQueue, - sqlAst, - JdbcParametersList.singleton( jdbcParameter ), - jdbcParameterBindings - ); - - session.getJdbcServices().getJdbcSelectExecutor().executeQuery( - jdbcSelectOperation, + getJdbcSelectExecutor().executeQuery( + getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst ) + .translate( NO_BINDINGS, QueryOptions.NONE ), jdbcParameterBindings, - new ExecutionContextWithSubselectFetchHandler( session, - subSelectFetchableKeysHandler, + new ExecutionContextWithSubselectFetchHandler( + session, + createRegistrationHandler( + session.getPersistenceContext().getBatchFetchQueue(), + sqlAst, + JdbcParametersList.singleton( jdbcParameter ), + jdbcParameterBindings + ), TRUE.equals( loadOptions.getReadOnly( session ) ) ), RowTransformerStandardImpl.instance(), null, - idsToLoadFromDatabase.size(), + idsInBatch.size(), ManagedResultConsumer.INSTANCE ); } - @Override protected List performUnorderedMultiLoad( K[] ids, @@ -180,7 +168,7 @@ protected List performUnorderedMultiLoad( } final List result = CollectionHelper.arrayList( ids.length ); - final LockOptions lockOptions = (loadOptions.getLockOptions() == null) + final LockOptions lockOptions = loadOptions.getLockOptions() == null ? new LockOptions( LockMode.NONE ) : loadOptions.getLockOptions(); @@ -198,7 +186,7 @@ protected List performUnorderedMultiLoad( return result; } - final SelectStatement sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter( + final SelectStatement sqlAst = createSelectBySingleArrayParameter( getLoadable(), getIdentifierMapping(), session.getLoadQueryInfluencers(), @@ -206,13 +194,11 @@ protected List performUnorderedMultiLoad( jdbcParameter, getSessionFactory() ); - final JdbcOperationQuerySelect jdbcSelectOperation = getSessionFactory().getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( getSessionFactory(), sqlAst ) - .translate( JdbcParameterBindings.NO_BINDINGS, QueryOptions.NONE ); + final JdbcOperationQuerySelect jdbcSelectOperation = + getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst ) + .translate( NO_BINDINGS, QueryOptions.NONE ); - final List databaseResults = LoaderHelper.loadByArrayParameter( + final List databaseResults = loadByArrayParameter( idsToLoadFromDatabase, sqlAst, jdbcSelectOperation, @@ -236,7 +222,7 @@ protected List performUnorderedMultiLoad( continue; } // found or not, remove the key from the batch-fetch queue - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, getLoadable(), session ); + removeBatchLoadableEntityKey( id, getLoadable(), session ); } return result; @@ -272,7 +258,7 @@ protected final K[] processResolvableEntities( getLoadable().getJavaType().getJavaTypeClass().getName(), lockOptions, session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) + getReadOnlyFromLoadQueryInfluencers( session ) ); Object managedEntity = null; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java index 5533a19fc977..b32821bb69e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java @@ -6,11 +6,11 @@ import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; @@ -20,26 +20,29 @@ 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.MultiKeyLoadSizingStrategy; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -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.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.type.descriptor.java.JavaType; -import org.jboss.logging.Logger; import static java.lang.Boolean.TRUE; 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; /** * Standard MultiIdEntityLoader @@ -47,7 +50,6 @@ * @author Steve Ebersole */ public class MultiIdEntityLoaderStandard extends AbstractMultiIdEntityLoader { - private static final Logger log = Logger.getLogger( MultiIdEntityLoaderStandard.class ); private final int idJdbcTypeCount; @@ -57,10 +59,17 @@ public MultiIdEntityLoaderStandard( SessionFactoryImplementor sessionFactory) { super( entityDescriptor, sessionFactory ); this.idJdbcTypeCount = idColumnSpan; - assert idJdbcTypeCount > 0; } + private boolean isInClauseParameterPaddingEnabled() { + return getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled(); + } + + private MultiKeyLoadSizingStrategy getBatchLoadSizingStrategy() { + return getJdbcServices().getJdbcEnvironment().getDialect().getBatchLoadSizingStrategy(); + } + @Override protected void handleResults( MultiIdLoadOptions loadOptions, @@ -90,17 +99,12 @@ protected void handleResults( @Override protected int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions) { - if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) { - return loadOptions.getBatchSize(); - } - else { - return getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect() - .getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( - idJdbcTypeCount, - ids.length, - getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() - ); - } + final Integer explicitBatchSize = loadOptions.getBatchSize(); + return explicitBatchSize != null && explicitBatchSize > 0 + ? explicitBatchSize + : getBatchLoadSizingStrategy() + .determineOptimalBatchLoadSize( idJdbcTypeCount, ids.length, + isInClauseParameterPaddingEnabled() ); } @Override @@ -111,7 +115,6 @@ protected void loadEntitiesById( EventSource session) { assert idsInBatch != null; assert !idsInBatch.isEmpty(); - listEntitiesById( idsInBatch, lockOptions, loadOptions, session ); } @@ -121,85 +124,84 @@ private List listEntitiesById( MultiIdLoadOptions loadOptions, EventSource session) { final int numberOfIdsInBatch = idsInBatch.size(); - if ( numberOfIdsInBatch == 1 ) { - return performSingleMultiLoad( idsInBatch.get( 0 ), lockOptions, session ); - } - else { - if ( log.isTraceEnabled() ) { - log.tracef( "#loadEntitiesById(`%s`, `%s`, ..)", getLoadable().getEntityName(), numberOfIdsInBatch ); - } + return numberOfIdsInBatch == 1 + ? performSingleMultiLoad( idsInBatch.get( 0 ), lockOptions, session ) + : performRegularMultiLoad( idsInBatch, lockOptions, loadOptions, session, numberOfIdsInBatch ); + } - final JdbcParametersList.Builder jdbcParametersBuilder = - JdbcParametersList.newBuilder( numberOfIdsInBatch * idJdbcTypeCount ); + private List performRegularMultiLoad( + List idsInBatch, + LockOptions lockOptions, + MultiIdLoadOptions loadOptions, + EventSource session, + int numberOfIdsInBatch) { + if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { + MULTI_KEY_LOAD_LOGGER.tracef( "#loadEntitiesById(`%s`, `%s`, ..)", + getLoadable().getEntityName(), numberOfIdsInBatch ); + } - final SelectStatement sqlAst = LoaderSelectBuilder.createSelect( - getLoadable(), - // null here means to select everything - null, + final JdbcParametersList.Builder jdbcParametersBuilder = + JdbcParametersList.newBuilder( numberOfIdsInBatch * idJdbcTypeCount ); + + final SelectStatement sqlAst = createSelect( + getLoadable(), + // null here means to select everything + null, + getLoadable().getIdentifierMapping(), + null, + numberOfIdsInBatch, + session.getLoadQueryInfluencers(), + lockOptions, + jdbcParametersBuilder::add, + getSessionFactory() + ); + + final JdbcParametersList jdbcParameters = jdbcParametersBuilder.build(); + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); + int offset = 0; + for ( int i = 0; i < numberOfIdsInBatch; i++ ) { + offset += jdbcParameterBindings.registerParametersForEachJdbcValue( + idsInBatch.get( i ), + offset, getLoadable().getIdentifierMapping(), - null, - numberOfIdsInBatch, - session.getLoadQueryInfluencers(), - lockOptions, - jdbcParametersBuilder::add, - getSessionFactory() - ); - JdbcParametersList jdbcParameters = jdbcParametersBuilder.build(); - - final SqlAstTranslatorFactory sqlAstTranslatorFactory = - getSessionFactory().getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); - - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); - int offset = 0; - - for ( int i = 0; i < numberOfIdsInBatch; i++ ) { - final Object id = idsInBatch.get( i ); - - offset += jdbcParameterBindings.registerParametersForEachJdbcValue( - id, - offset, - getLoadable().getIdentifierMapping(), - jdbcParameters, - session - ); - } - - // we should have used all the JdbcParameter references (created bindings for all) - assert offset == jdbcParameters.size(); - final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( getSessionFactory(), sqlAst ) - .translate( jdbcParameterBindings, QueryOptions.NONE ); - - final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; - if ( session.getLoadQueryInfluencers().hasSubselectLoadableCollections( getLoadable().getEntityPersister() ) ) { - subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler( - session.getPersistenceContext().getBatchFetchQueue(), - sqlAst, - jdbcParameters, - jdbcParameterBindings - ); - } - else { - subSelectFetchableKeysHandler = null; - } - - return session.getJdbcServices().getJdbcSelectExecutor().list( - jdbcSelect, - jdbcParameterBindings, - new ExecutionContextWithSubselectFetchHandler( session, - subSelectFetchableKeysHandler, - TRUE.equals( loadOptions.getReadOnly( session ) ) ), - RowTransformerStandardImpl.instance(), - null, - ListResultsConsumer.UniqueSemantic.FILTER, - idsInBatch.size() + jdbcParameters, + session ); } + // we should have used all the JdbcParameter references (created bindings for all) + assert offset == jdbcParameters.size(); + + return getJdbcSelectExecutor().list( + getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ), + jdbcParameterBindings, + new ExecutionContextWithSubselectFetchHandler( + session, + fetchableKeysHandler( session, sqlAst, jdbcParameters, jdbcParameterBindings ), + TRUE.equals( loadOptions.getReadOnly( session ) ) + ), + RowTransformerStandardImpl.instance(), + null, + ListResultsConsumer.UniqueSemantic.FILTER, + idsInBatch.size() + ); + } + + private SubselectFetch.RegistrationHandler fetchableKeysHandler( + EventSource session, + SelectStatement sqlAst, + JdbcParametersList jdbcParameters, + JdbcParameterBindings jdbcParameterBindings) { + final BatchFetchQueue batchFetchQueue = session.getPersistenceContext().getBatchFetchQueue(); + return session.getLoadQueryInfluencers().hasSubselectLoadableCollections( getLoadable().getEntityPersister() ) + ? createRegistrationHandler( batchFetchQueue, sqlAst, jdbcParameters, jdbcParameterBindings ) + : null; } private List performSingleMultiLoad(Object id, LockOptions lockOptions, SharedSessionContractImplementor session) { - //noinspection unchecked + @SuppressWarnings("unchecked") T loaded = (T) getLoadable().getEntityPersister().load( id, null, lockOptions, session ); - return Collections.singletonList( loaded ); + return singletonList( loaded ); } @Override @@ -210,11 +212,11 @@ protected List performUnorderedMultiLoad( assert !loadOptions.isOrderReturnEnabled(); assert ids != null; - if ( log.isTraceEnabled() ) { - log.tracef( "#performUnorderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() ); + if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { + MULTI_KEY_LOAD_LOGGER.tracef( "#performUnorderedMultiLoad(`%s`, ..)", getLoadable().getEntityName() ); } - final List result = CollectionHelper.arrayList( ids.length ); + final List result = arrayList( ids.length ); final LockOptions lockOptions = loadOptions.getLockOptions() == null ? new LockOptions( LockMode.NONE ) @@ -241,7 +243,7 @@ protected List performUnorderedMultiLoad( getLoadable().getJavaType().getJavaTypeClass().getName(), lockOptions, session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) + getReadOnlyFromLoadQueryInfluencers( session ) ); Object managedEntity = null; @@ -303,27 +305,23 @@ protected List performUnorderedMultiLoad( maxBatchSize = loadOptions.getBatchSize(); } else { - maxBatchSize = session.getJdbcServices().getJdbcEnvironment().getDialect().getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( + maxBatchSize = getBatchLoadSizingStrategy().determineOptimalBatchLoadSize( getIdentifierMapping().getJdbcTypeCount(), numberOfIdsLeft, - getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() + isInClauseParameterPaddingEnabled() ); } 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 ); - result.addAll( listEntitiesById( asList( idsInBatch ), lockOptions, loadOptions, session ) ); - numberOfIdsLeft = numberOfIdsLeft - batchSize; idPosition += batchSize; } return result; } - } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 79b631871519..36e28ac2319f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -857,7 +857,7 @@ public static Map getEntityNameByTableNameMap( protected MultiIdEntityLoader buildMultiIdLoader() { final Dialect dialect = factory.getJdbcServices().getDialect(); return getIdentifierType() instanceof BasicType && supportsSqlArrayType( dialect ) - ? new MultiIdEntityLoaderArrayParam<>( this, identifierColumnSpan, factory ) + ? new MultiIdEntityLoaderArrayParam<>( this, factory ) : new MultiIdEntityLoaderStandard<>( this, identifierColumnSpan, factory ); } From 6f64c1cd606995133076324ac1b31e7aa56ad557 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 26 Oct 2024 10:30:51 +0200 Subject: [PATCH 5/8] document performance implications of id batching i.e. BatchSize Signed-off-by: Gavin King --- .../src/main/java/org/hibernate/BatchSize.java | 11 +++++++++++ .../org/hibernate/MultiIdentifierLoadAccess.java | 12 ++++++++++++ .../src/main/java/org/hibernate/Session.java | 11 ++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/BatchSize.java b/hibernate-core/src/main/java/org/hibernate/BatchSize.java index cf982b6ad139..1c848dc59d13 100644 --- a/hibernate-core/src/main/java/org/hibernate/BatchSize.java +++ b/hibernate-core/src/main/java/org/hibernate/BatchSize.java @@ -23,6 +23,17 @@ * If an explicit batch size is set manually, care should be taken * to not exceed the capabilities of the underlying database. *

+ * The performance impact of setting a batch size depends on whether + * a SQL array may be used to pass the list of identifiers to the + * database: + *

    + *
  • for databases which support standard SQL arrays, a smaller + * batch size might be extremely inefficient compared to a very + * large batch size or no batching at all, but + *
  • on the other hand, for databases with no SQL array type, a + * large batch size results in long SQL statements with many JDBC + * parameters. + *

    * A batch size is considered a hint. This option has no effect * on {@link Session#find(Class, Object, FindOption...)}. * diff --git a/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java index bab6eb4db9df..657b4fcb3169 100644 --- a/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java @@ -133,6 +133,18 @@ default MultiIdentifierLoadAccess with(RootGraph graph) { * If an explicit batch size is set manually, care should be taken * to not exceed the capabilities of the underlying database. *

    + * The performance impact of setting a batch size depends on whether + * a SQL array may be used to pass the list of identifiers to the + * database: + *

      + *
    • for databases which support standard SQL arrays, a smaller + * batch size might be extremely inefficient compared to a very + * large batch size or no batching at all, but + *
    • on the other hand, for databases with no SQL array type, a + * large batch size results in long SQL statements with many JDBC + * parameters. + *
    + *

    * A batch size is considered a hint. * * @param batchSize The batch size diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index b4ff373e27eb..669e0b16ae34 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -562,7 +562,16 @@ public interface Session extends SharedSessionContract, EntityManager { * given entity class, or a fully-fetched proxy object. *

    * This method accepts {@link BatchSize} as an option, allowing control over the number of - * records retrieved in a single database request. + * records retrieved in a single database request. The performance impact of setting a batch + * size depends on whether a SQL array may be used to pass the list of identifiers to the + * database: + *

      + *
    • for databases which {@linkplain org.hibernate.dialect.Dialect#supportsStandardArrays + * support standard SQL arrays}, a smaller batch size might be extremely inefficient + * compared to a very large batch size or no batching at all, but + *
    • on the other hand, for databases with no SQL array type, a large batch size results + * in long SQL statements with many JDBC parameters. + *
    *

    * For more advanced cases, use {@link #byMultipleIds(Class)}, which returns an instance of * {@link MultiIdentifierLoadAccess}. From 32a9129b4e8a090873ffe0990468648b472c7162 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 26 Oct 2024 10:55:32 +0200 Subject: [PATCH 6/8] minor cleanup in Dialect Signed-off-by: Gavin King --- .../java/org/hibernate/dialect/Dialect.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 21fe552bfcf2..6778f27518eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -71,7 +71,6 @@ import org.hibernate.exception.spi.SQLExceptionConverter; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.MathHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy; @@ -208,6 +207,7 @@ import static org.hibernate.cfg.AvailableSettings.NON_CONTEXTUAL_LOB_CREATION; import static org.hibernate.cfg.AvailableSettings.STATEMENT_BATCH_SIZE; import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS; +import static org.hibernate.internal.util.MathHelper.ceilingPowerOfTwo; import static org.hibernate.internal.util.StringHelper.splitAtCommas; import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY; import static org.hibernate.type.SqlTypes.*; @@ -4385,21 +4385,13 @@ public MultiKeyLoadSizingStrategy getBatchLoadSizingStrategy() { return getMultiKeyLoadSizingStrategy(); } - protected final MultiKeyLoadSizingStrategy STANDARD_MULTI_KEY_LOAD_SIZING_STRATEGY = (numberOfColumns, numberOfKeys, pad) -> { - numberOfKeys = pad ? MathHelper.ceilingPowerOfTwo( numberOfKeys ) : numberOfKeys; - - final long parameterCount = (long) numberOfColumns * numberOfKeys; - final int limit = getParameterCountLimit(); - - if ( limit > 0 ) { - // the Dialect reported a limit - see if the parameter count exceeds the limit - if ( parameterCount >= limit ) { - return limit / numberOfColumns; - } - } + private int calculateBatchSize(int numberOfColumns, int numberOfKeys, boolean padToPowerOfTwo) { + final int batchSize = padToPowerOfTwo ? ceilingPowerOfTwo( numberOfKeys ) : numberOfKeys; + final int maxBatchSize = getParameterCountLimit() / numberOfColumns; + return maxBatchSize > 0 && batchSize > maxBatchSize ? maxBatchSize : batchSize; + } - return numberOfKeys; - }; + protected final MultiKeyLoadSizingStrategy STANDARD_MULTI_KEY_LOAD_SIZING_STRATEGY = this::calculateBatchSize; /** * Is JDBC statement warning logging enabled by default? From 670c6d984763c8b34ca30ab130b51765bc8930f8 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 26 Oct 2024 12:26:35 +0200 Subject: [PATCH 7/8] finish big refactor of AbstractMultiIdEntityLoader and children Signed-off-by: Gavin King --- .../util/collections/CollectionHelper.java | 2 +- .../internal/AbstractMultiIdEntityLoader.java | 272 ++++++++++++++---- .../MultiIdEntityLoaderArrayParam.java | 167 ++--------- .../internal/MultiIdEntityLoaderStandard.java | 131 +-------- 4 files changed, 250 insertions(+), 322 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index 9090068dc9c3..91211ce1c2ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -262,7 +262,7 @@ public static Set makeCopy(Set source) { return copy; } - public static boolean isEmpty(Collection collection) { + public static boolean isEmpty(Collection collection) { return collection == null || collection.isEmpty(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java index 84f089c297d9..3758d3d993ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java @@ -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.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 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 EntityMappingType getLoadable() { @Override public final List 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 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 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 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 result = arrayList( ids.length ); @@ -109,10 +126,10 @@ protected List performOrderedMultiLoad( final List 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 @@ protected List performOrderedMultiLoad( return (List) 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 elementPositionsLoadedByBatch, List result); + protected abstract void handleResults( + MultiIdLoadOptions loadOptions, + EventSource session, + List elementPositionsLoadedByBatch, + List result); - protected abstract void loadEntitiesById(List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, EventSource session); + protected abstract void loadEntitiesById( + List idsInBatch, + LockOptions lockOptions, + MultiIdLoadOptions loadOptions, + EventSource session); - protected boolean loadFromCaches( + protected boolean loadFromEnabledCaches( MultiIdLoadOptions loadOptions, EventSource session, Object id, @@ -157,48 +188,189 @@ protected boolean loadFromCaches( List 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 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 ( managedEntity != null + && !loadOptions.isReturnOfDeletedEntitiesEnabled() + && !persistenceContextEntry.isManaged() ) { + // put a null in the result + result.add( i, null ); + return true; + } + } - 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.isSecondLevelCacheCheckingEnabled() ) { + // look for it in the SessionFactory + managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + loadEvent, + getLoadable().getEntityPersister(), + entityKey + ); + } - if ( managedEntity != null - && !loadOptions.isReturnOfDeletedEntitiesEnabled() - && !persistenceContextEntry.isManaged() ) { - // put a null in the result - result.add( i, null ); - return true; - } - } + if ( managedEntity != null ) { + result.add( i, managedEntity ); + return true; + } + else { + return false; + } + } + + protected List unorderedMultiLoad( + Object[] ids, + MultiIdLoadOptions loadOptions, + LockOptions lockOptions, + EventSource session) { + final List 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 result); + + protected final Object[] resolveInCachesIfEnabled( + Object[] ids, + @NonNull MultiIdLoadOptions loadOptions, + @NonNull LockOptions lockOptions, + EventSource session, + ResolutionConsumer 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 Object[] resolveInCaches( + Object[] ids, + MultiIdLoadOptions loadOptions, + LockOptions lockOptions, + EventSource session, + ResolutionConsumer resolutionConsumer) { + + final boolean idCoercionEnabled = isIdCoercionEnabled(); + final JavaType idType = getLoadable().getIdentifierMapping().getJavaType(); - if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { - // look for it in the SessionFactory - managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey - ); + List 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 { + void consume(int position, EntityKey entityKey, T resolvedRef); + } + private List loadFromCaches( + K id, EntityKey entityKey, int i, + List unresolvedIds, + MultiIdLoadOptions loadOptions, + ResolutionConsumer 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 ( managedEntity != null ) { - result.add( i, managedEntity ); - return true; + 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 false; + return unresolvedIds; } - protected abstract List performUnorderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session); - } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java index cd8b428bf64d..00c9b714f765 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java @@ -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.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 MultiIdEntityLoaderArrayParam( 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 @@ protected void loadEntitiesById( 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 @@ protected void loadEntitiesById( } @Override - protected List 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 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 result) { final SelectStatement sqlAst = createSelectBySingleArrayParameter( getLoadable(), getIdentifierMapping(), @@ -194,12 +154,13 @@ protected List performUnorderedMultiLoad( jdbcParameter, getSessionFactory() ); + final JdbcOperationQuerySelect jdbcSelectOperation = getSqlAstTranslatorFactory().buildSelectTranslator( getSessionFactory(), sqlAst ) .translate( NO_BINDINGS, QueryOptions.NONE ); final List databaseResults = loadByArrayParameter( - idsToLoadFromDatabase, + unresolvableIds, sqlAst, jdbcSelectOperation, jdbcParameter, @@ -213,108 +174,14 @@ protected List performUnorderedMultiLoad( ); 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; + 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 ); } - // found or not, remove the key from the batch-fetch queue - removeBatchLoadableEntityKey( id, getLoadable(), session ); } - - return result; - } - public interface ResolutionConsumer { - void consume(int position, EntityKey entityKey, T resolvedRef); } - protected final K[] processResolvableEntities( - K[] ids, - ResolutionConsumer 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 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[] createTypedArray(@SuppressWarnings("SameParameterValue") int length) { - //noinspection unchecked - return (X[]) Array.newInstance( getIdentifierMapping().getJavaType().getJavaTypeClass(), length ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java index b32821bb69e3..37f2c152bd57 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java @@ -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.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.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 @@ private List performSingleMultiLoad(Object id, LockOptions lockOptions, Share } @Override - protected List 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 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 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 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; } } From 33e18a343daedc40ae3ae244af8694e2805ea435 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 26 Oct 2024 12:51:06 +0200 Subject: [PATCH 8/8] HHH-18772 introduce AuthException and simplify SQLStateConversionDelegate Signed-off-by: Gavin King --- .../hibernate/exception/AuthException.java | 37 ++++++ .../hibernate/exception/DataException.java | 6 +- .../internal/SQLStateConversionDelegate.java | 123 +++++++----------- .../internal/util/JdbcExceptionHelper.java | 5 +- 4 files changed, 90 insertions(+), 81 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/exception/AuthException.java diff --git a/hibernate-core/src/main/java/org/hibernate/exception/AuthException.java b/hibernate-core/src/main/java/org/hibernate/exception/AuthException.java new file mode 100644 index 000000000000..64637bb7eea6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/exception/AuthException.java @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.exception; + +import org.hibernate.JDBCException; + +import java.sql.SQLException; + +/** + * A {@link JDBCException} indicating an authentication or authorization failure. + * + * @since 7.0 + * + * @author Gavin King + */ +public class AuthException extends JDBCException { + /** + * Constructor for AuthException. + * + * @param root The underlying exception. + */ + public AuthException(String message, SQLException root) { + super( message, root ); + } + + /** + * Constructor for AuthException. + * + * @param message Optional message. + * @param root The underlying exception. + */ + public AuthException(String message, SQLException root, String sql) { + super( message, root, sql ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/exception/DataException.java b/hibernate-core/src/main/java/org/hibernate/exception/DataException.java index c77a9e8b2320..4b7a101798e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/exception/DataException.java +++ b/hibernate-core/src/main/java/org/hibernate/exception/DataException.java @@ -9,7 +9,7 @@ import org.hibernate.JDBCException; /** - * Extends {@link JDBCException} indicating that evaluation of the + * A {@link JDBCException} indicating that evaluation of the * valid SQL statement against the given data resulted in some * illegal operation, mismatched types or incorrect cardinality. * @@ -17,7 +17,7 @@ */ public class DataException extends JDBCException { /** - * Constructor for JDBCException. + * Constructor for DataException. * * @param root The underlying exception. */ @@ -26,7 +26,7 @@ public DataException(String message, SQLException root) { } /** - * Constructor for JDBCException. + * Constructor for DataException. * * @param message Optional message. * @param root The underlying exception. diff --git a/hibernate-core/src/main/java/org/hibernate/exception/internal/SQLStateConversionDelegate.java b/hibernate-core/src/main/java/org/hibernate/exception/internal/SQLStateConversionDelegate.java index ee52df5fbea1..97afadfcab03 100644 --- a/hibernate-core/src/main/java/org/hibernate/exception/internal/SQLStateConversionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/exception/internal/SQLStateConversionDelegate.java @@ -5,11 +5,11 @@ package org.hibernate.exception.internal; import java.sql.SQLException; -import java.util.Set; import org.hibernate.JDBCException; import org.hibernate.PessimisticLockException; import org.hibernate.QueryTimeoutException; +import org.hibernate.exception.AuthException; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.DataException; import org.hibernate.exception.JDBCConnectionException; @@ -17,108 +17,83 @@ import org.hibernate.exception.SQLGrammarException; import org.hibernate.exception.spi.AbstractSQLExceptionConversionDelegate; import org.hibernate.exception.spi.ConversionContext; -import org.hibernate.internal.util.JdbcExceptionHelper; import org.checkerframework.checker.nullness.qual.Nullable; +import static org.hibernate.internal.util.JdbcExceptionHelper.determineSqlStateClassCode; +import static org.hibernate.internal.util.JdbcExceptionHelper.extractErrorCode; +import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState; + /** * A {@link org.hibernate.exception.spi.SQLExceptionConverter} implementation which performs conversion based * on the underlying SQLState. Interpretation of a SQL error based on SQLState is not nearly as accurate as * using the ErrorCode (which is, however, vendor-specific). - *

    - * SQLState codes are defined by both ANSI SQL specs and X/Open. Some "classes" are shared, others are - * specific to one or another, yet others are custom vendor classes. Unfortunately I have not been able to - * find a "blessed" list of X/Open codes. These codes are cobbled together between ANSI SQL spec and error + * + * @implNote + * SQLState codes are defined by both ANSI SQL specs and X/Open. Some "classes" are shared, others are + * specific to one or another, yet others are custom vendor classes. Unfortunately I have not been able to + * find a "blessed" list of X/Open codes. These codes are cobbled together between ANSI SQL spec and error * code tables from few vendor's documentation. * * @author Steve Ebersole */ public class SQLStateConversionDelegate extends AbstractSQLExceptionConversionDelegate { - private static final Set SQL_GRAMMAR_CATEGORIES = buildGrammarCategories(); - private static Set buildGrammarCategories() { - return Set.of( - "07", // "dynamic SQL error" - "20", - "2A", // "direct SQL syntax error or access rule violation" - "37", // "dynamic SQL syntax error or access rule violation" - "42", // "syntax error or access rule violation" - "65", // Oracle specific as far as I can tell - "S0" // MySQL specific as far as I can tell - ); - } - - private static final Set DATA_CATEGORIES = buildDataCategories(); - private static Set buildDataCategories() { - return Set.of( - "21", // "cardinality violation" - "22" // "data exception" - ); - } - - private static final Set INTEGRITY_VIOLATION_CATEGORIES = buildContraintCategories(); - private static Set buildContraintCategories() { - return Set.of( - "23", // "integrity constraint violation" - "27", // "triggered data change violation" - "44" // "with check option violation" - ); - } - - private static final Set CONNECTION_CATEGORIES = buildConnectionCategories(); - private static Set buildConnectionCategories() { - return Set.of( - "08" // "connection exception" - ); - } - public SQLStateConversionDelegate(ConversionContext conversionContext) { super( conversionContext ); } @Override public @Nullable JDBCException convert(SQLException sqlException, String message, String sql) { - final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException ); - final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException ); - + final String sqlState = extractSqlState( sqlException ); if ( sqlState != null ) { - String sqlStateClassCode = JdbcExceptionHelper.determineSqlStateClassCode( sqlState ); - - if ( sqlStateClassCode != null ) { - if ( SQL_GRAMMAR_CATEGORIES.contains( sqlStateClassCode ) ) { + switch ( sqlState ) { + case "42501": + return new AuthException( message, sqlException, sql ); + case "40001": + return new LockAcquisitionException( message, sqlException, sql ); + case "40XL1", "40XL2": + // Derby "A lock could not be obtained within the time requested." + return new PessimisticLockException( message, sqlException, sql ); + case "70100": + // MySQL Query execution was interrupted + return new QueryTimeoutException( message, sqlException, sql ); + case "72000": + if ( extractErrorCode( sqlException ) == 1013 ) { + // Oracle user requested cancel of current operation + return new QueryTimeoutException( message, sqlException, sql ); + } + } + switch ( determineSqlStateClassCode( sqlState ) ) { + case + "07", // "dynamic SQL error" + "20", + "2A", // "direct SQL syntax error or access rule violation" + "37", // "dynamic SQL syntax error or access rule violation" + "42", // "syntax error or access rule violation" + "65", // Oracle specific as far as I can tell + "S0": // MySQL specific as far as I can tell return new SQLGrammarException( message, sqlException, sql ); - } - else if ( INTEGRITY_VIOLATION_CATEGORIES.contains( sqlStateClassCode ) ) { + case + "23", // "integrity constraint violation" + "27", // "triggered data change violation" + "44": // "with check option violation" final String constraintName = getConversionContext() .getViolatedConstraintNameExtractor() .extractConstraintName( sqlException ); return new ConstraintViolationException( message, sqlException, sql, constraintName ); - } - else if ( CONNECTION_CATEGORIES.contains( sqlStateClassCode ) ) { + case + "08": // "connection exception" return new JDBCConnectionException( message, sqlException, sql ); - } - else if ( DATA_CATEGORIES.contains( sqlStateClassCode ) ) { + case + "21", // "cardinality violation" + "22": // "data exception" return new DataException( message, sqlException, sql ); - } - } - - if ( "40001".equals( sqlState ) ) { - return new LockAcquisitionException( message, sqlException, sql ); - } - - if ( "40XL1".equals( sqlState ) || "40XL2".equals( sqlState )) { - // Derby "A lock could not be obtained within the time requested." - return new PessimisticLockException( message, sqlException, sql ); - } - - // MySQL Query execution was interrupted - if ( "70100".equals( sqlState ) || - // Oracle user requested cancel of current operation - ( "72000".equals( sqlState ) && errorCode == 1013 ) ) { - return new QueryTimeoutException( message, sqlException, sql ); + case + "28": // "authentication failure" + return new AuthException( message, sqlException, sql ); } } - return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/JdbcExceptionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/JdbcExceptionHelper.java index f02744f6013a..fba9a3d0872d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/JdbcExceptionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/JdbcExceptionHelper.java @@ -56,9 +56,6 @@ public static String extractSqlStateClassCode(SQLException sqlException) { } public static String determineSqlStateClassCode(String sqlState) { - if ( sqlState == null || sqlState.length() < 2 ) { - return sqlState; - } - return sqlState.substring( 0, 2 ); + return sqlState == null || sqlState.length() < 2 ? sqlState : sqlState.substring( 0, 2 ); } }