From 13fb23d44e02e8b9251e608392a97aa961f6a2f3 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 10 Nov 2020 08:36:01 -0600 Subject: [PATCH] HHH-14312 - entity graph is ignored for 'padded' and 'dynamic' batch style entity loader --- .../main/java/org/hibernate/LockOptions.java | 16 ++ .../entity/BatchingEntityLoaderBuilder.java | 8 +- .../LegacyBatchingEntityLoaderBuilder.java | 132 ---------------- .../PaddedBatchingEntityLoaderBuilder.java | 135 ----------------- .../plan/DynamicBatchingEntityLoader.java | 100 ++++++++++++ .../DynamicBatchingEntityLoaderBuilder.java | 44 ++++++ .../plan/PaddedBatchingEntityLoader.java | 120 +++++++++++++++ .../PaddedBatchingEntityLoaderBuilder.java | 44 ++++++ .../loader/entity/plan/package-info.java | 12 ++ .../EntityGraphDynamicBatchStyleTest.java | 143 ++++++++++++++++++ .../EntityGraphPaddedBatchStyleTest.java | 87 ++++++++--- 11 files changed, 546 insertions(+), 295 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java create mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoaderBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoader.java create mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoaderBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/loader/entity/plan/package-info.java create mode 100644 hibernate-core/src/test/java/org/hibernate/graph/EntityGraphDynamicBatchStyleTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/LockOptions.java b/hibernate-core/src/main/java/org/hibernate/LockOptions.java index 9882172004..a25fc84b71 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/LockOptions.java @@ -329,4 +329,20 @@ public class LockOptions implements Serializable { destination.setFollowOnLocking( source.getFollowOnLocking() ); return destination; } + + public static LockOptions interpret(LockMode lockMode) { + if ( lockMode == null || lockMode == LockMode.NONE ) { + return NONE; + } + + if ( lockMode == LockMode.READ ) { + return READ; + } + + if ( lockMode.greaterThan( LockMode.UPGRADE_NOWAIT ) ) { + return UPGRADE; + } + + return new LockOptions( lockMode ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java index dd6b4579bc..738167b34f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java @@ -10,6 +10,9 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.entity.plan.DynamicBatchingEntityLoaderBuilder; +import org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder; +import org.hibernate.loader.entity.plan.PaddedBatchingEntityLoaderBuilder; import org.hibernate.persister.entity.OuterJoinLoadable; /** @@ -23,7 +26,7 @@ import org.hibernate.persister.entity.OuterJoinLoadable; */ public abstract class BatchingEntityLoaderBuilder { public static BatchingEntityLoaderBuilder getBuilder(SessionFactoryImplementor factory) { - switch ( factory.getSettings().getBatchFetchStyle() ) { + switch ( factory.getSessionFactoryOptions().getBatchFetchStyle() ) { case PADDED: { return PaddedBatchingEntityLoaderBuilder.INSTANCE; } @@ -31,8 +34,7 @@ public abstract class BatchingEntityLoaderBuilder { return DynamicBatchingEntityLoaderBuilder.INSTANCE; } default: { - return org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder.INSTANCE; -// return LegacyBatchingEntityLoaderBuilder.INSTANCE; + return LegacyBatchingEntityLoaderBuilder.INSTANCE; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java deleted file mode 100644 index 35f8038d03..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.loader.entity; - -import java.io.Serializable; -import java.util.List; - -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.loader.Loader; -import org.hibernate.persister.entity.OuterJoinLoadable; - -/** - * No longer used, see {@link org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder} instead. - * - * @author Steve Ebersole - */ -public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder { - public static final LegacyBatchingEntityLoaderBuilder INSTANCE = new LegacyBatchingEntityLoaderBuilder(); - - @Override - protected UniqueEntityLoader buildBatchingLoader( - OuterJoinLoadable persister, - int batchSize, - LockMode lockMode, - SessionFactoryImplementor factory, - LoadQueryInfluencers influencers) { - return new LegacyBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers ); - } - - @Override - protected UniqueEntityLoader buildBatchingLoader( - OuterJoinLoadable persister, - int batchSize, - LockOptions lockOptions, - SessionFactoryImplementor factory, - LoadQueryInfluencers influencers) { - return new LegacyBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers ); - } - - public static class LegacyBatchingEntityLoader extends BatchingEntityLoader implements UniqueEntityLoader { - private final int[] batchSizes; - private final Loader[] loaders; - - public LegacyBatchingEntityLoader( - OuterJoinLoadable persister, - int maxBatchSize, - LockMode lockMode, - SessionFactoryImplementor factory, - LoadQueryInfluencers loadQueryInfluencers) { - super( persister ); - this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); - this.loaders = new Loader[ batchSizes.length ]; - for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers); - } - } - - public LegacyBatchingEntityLoader( - OuterJoinLoadable persister, - int maxBatchSize, - LockOptions lockOptions, - SessionFactoryImplementor factory, - LoadQueryInfluencers loadQueryInfluencers) { - super( persister ); - this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); - this.loaders = new Loader[ batchSizes.length ]; - for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers); - } - } - - @Override - public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { - return load( id, optionalObject, session, lockOptions, null ); - } - - @Override - public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { - final Serializable[] batch = session.getPersistenceContextInternal() - .getBatchFetchQueue() - .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); - - for ( int i = 0; i < batchSizes.length-1; i++) { - final int smallBatchSize = batchSizes[i]; - if ( batch[smallBatchSize-1] != null ) { - Serializable[] smallBatch = new Serializable[smallBatchSize]; - System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize); - // for now... - final List results = loaders[i].loadEntityBatch( - session, - smallBatch, - persister().getIdentifierType(), - optionalObject, - persister().getEntityName(), - id, - persister(), - lockOptions, - readOnly - ); - // The EntityKey for any entity that is not found will remain in the batch. - // Explicitly remove the EntityKeys for entities that were not found to - // avoid including them in future batches that get executed. - BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( - smallBatch, - results, - persister(), - session - ); - return getObjectFromList(results, id, session); //EARLY EXIT - } - } - final Object result = ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session); - if ( result == null ) { - // There was no entity with the specified ID. Make sure the EntityKey does not remain - // in the batch to avoid including it in future batches that get executed. - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); - } - return result; - } - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java deleted file mode 100644 index 9c605d9fd0..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.loader.entity; - -import java.io.Serializable; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.loader.Loader; -import org.hibernate.persister.entity.OuterJoinLoadable; - -/** -* @author Steve Ebersole -*/ -class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder { - public static final PaddedBatchingEntityLoaderBuilder INSTANCE = new PaddedBatchingEntityLoaderBuilder(); - - @Override - protected UniqueEntityLoader buildBatchingLoader( - OuterJoinLoadable persister, - int batchSize, - LockMode lockMode, - SessionFactoryImplementor factory, - LoadQueryInfluencers influencers) { - return new PaddedBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers ); - } - - @Override - protected UniqueEntityLoader buildBatchingLoader( - OuterJoinLoadable persister, - int batchSize, - LockOptions lockOptions, - SessionFactoryImplementor factory, - LoadQueryInfluencers influencers) { - return new PaddedBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers ); - } - - public static class PaddedBatchingEntityLoader extends BatchingEntityLoader { - private final int[] batchSizes; - private final Loader[] loaders; - - public PaddedBatchingEntityLoader( - OuterJoinLoadable persister, - int maxBatchSize, - LockMode lockMode, - SessionFactoryImplementor factory, - LoadQueryInfluencers loadQueryInfluencers) { - super( persister ); - this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); - this.loaders = new Loader[ batchSizes.length ]; - for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers); - } - validate( maxBatchSize ); - } - - private void validate(int max) { - // these are more indicative of internal problems then user error... - if ( batchSizes[0] != max ) { - throw new HibernateException( "Unexpected batch size spread" ); - } - if ( batchSizes[batchSizes.length-1] != 1 ) { - throw new HibernateException( "Unexpected batch size spread" ); - } - } - - public PaddedBatchingEntityLoader( - OuterJoinLoadable persister, - int maxBatchSize, - LockOptions lockOptions, - SessionFactoryImplementor factory, - LoadQueryInfluencers loadQueryInfluencers) { - super( persister ); - this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); - this.loaders = new Loader[ batchSizes.length ]; - for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers); - } - validate( maxBatchSize ); - } - - @Override - public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { - return load( id, optionalObject, session, lockOptions, null ); - } - - @Override - public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { - final Serializable[] batch = session.getPersistenceContextInternal() - .getBatchFetchQueue() - .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); - - final int numberOfIds = ArrayHelper.countNonNull( batch ); - if ( numberOfIds <= 1 ) { - final Object result = ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load( id, optionalObject, session ); - if ( result == null ) { - // There was no entity with the specified ID. Make sure the EntityKey does not remain - // in the batch to avoid including it in future batches that get executed. - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); - } - return result; - } - - // Uses the first batch-size bigger than the number of actual ids in the batch - int indexToUse = batchSizes.length-1; - for ( int i = 0; i < batchSizes.length-1; i++ ) { - if ( batchSizes[i] >= numberOfIds ) { - indexToUse = i; - } - else { - break; - } - } - - final Serializable[] idsToLoad = new Serializable[ batchSizes[indexToUse] ]; - System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds ); - for ( int i = numberOfIds; i < batchSizes[indexToUse]; i++ ) { - idsToLoad[i] = id; - } - - return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions, readOnly ); - } - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java new file mode 100644 index 0000000000..2cf4dbb71b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.loader.entity.plan; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.OuterJoinLoadable; +import org.hibernate.pretty.MessageHelper; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public class DynamicBatchingEntityLoader extends BatchingEntityLoader { + private static final Logger log = Logger.getLogger( DynamicBatchingEntityLoader.class ); + + private final int maxBatchSize; + private final EntityLoader.Builder entityLoaderBuilder; + + public DynamicBatchingEntityLoader( + OuterJoinLoadable persister, + int maxBatchSize, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers) { + super( persister ); + this.maxBatchSize = maxBatchSize; + + entityLoaderBuilder = EntityLoader.forEntity( persister ) + .withInfluencers( loadQueryInfluencers ) + .withLockOptions( lockOptions ); + } + + @Override + public Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session, + LockOptions lockOptions) { + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { + final Serializable[] batch = session.getPersistenceContextInternal() + .getBatchFetchQueue() + .getEntityBatch( persister(), id, maxBatchSize, persister().getEntityMode() ); + + final int numberOfIds = ArrayHelper.countNonNull( batch ); + final Serializable[] idsToLoad = new Serializable[ numberOfIds ]; + + System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds ); + + if ( log.isDebugEnabled() ) { + log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister(), idsToLoad, session.getFactory() ) ); + } + + final EntityLoader dynamicLoader = entityLoaderBuilder.withBatchSize( idsToLoad.length ).byPrimaryKey(); + final QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions ); + + + + final List results = dynamicLoader.loadEntityBatch( + session, + idsToLoad, + persister().getIdentifierType(), + optionalObject, + persister().getEntityName(), + id, + persister(), + lockOptions, + readOnly + ); + + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + idsToLoad, + results, + persister(), + session + ); + + return getObjectFromList( results, id, session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoaderBuilder.java new file mode 100644 index 0000000000..6baf7deffc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoaderBuilder.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.loader.entity.plan; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.entity.UniqueEntityLoader; +import org.hibernate.persister.entity.OuterJoinLoadable; + +/** + * @author Steve Ebersole + */ +public class DynamicBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder { + /** + * Singleton access + */ + public static final DynamicBatchingEntityLoaderBuilder INSTANCE = new DynamicBatchingEntityLoaderBuilder(); + + @Override + protected UniqueEntityLoader buildBatchingLoader( + OuterJoinLoadable persister, + int batchSize, + LockMode lockMode, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return buildBatchingLoader( persister, batchSize, LockOptions.interpret( lockMode ), factory, influencers ); + } + + @Override + protected UniqueEntityLoader buildBatchingLoader( + OuterJoinLoadable persister, + int batchSize, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return new DynamicBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoader.java new file mode 100644 index 0000000000..2d2591fd25 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoader.java @@ -0,0 +1,120 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.loader.entity.plan; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.OuterJoinLoadable; + +/** + * @author Steve Ebersole + */ +public class PaddedBatchingEntityLoader extends BatchingEntityLoader { + private final int[] batchSizes; + private final EntityLoader[] loaders; + + public PaddedBatchingEntityLoader( + OuterJoinLoadable persister, + int maxBatchSize, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers) { + super( persister ); + + this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); + this.loaders = new EntityLoader[ batchSizes.length ]; + final EntityLoader.Builder entityLoaderBuilder = EntityLoader.forEntity( persister ) + .withInfluencers( loadQueryInfluencers ) + .withLockOptions( lockOptions ); + + // we create a first entity loader to use it as a template for the others + this.loaders[0] = entityLoaderBuilder.withBatchSize( batchSizes[0] ).byPrimaryKey(); + + for ( int i = 1; i < batchSizes.length; i++ ) { + this.loaders[i] = entityLoaderBuilder.withEntityLoaderTemplate( this.loaders[0] ).withBatchSize( batchSizes[i] ).byPrimaryKey(); + } + + validate( maxBatchSize ); + } + + private void validate(int max) { + // these are more indicative of internal problems then user error... + if ( batchSizes[0] != max ) { + throw new HibernateException( "Unexpected batch size spread" ); + } + if ( batchSizes[batchSizes.length-1] != 1 ) { + throw new HibernateException( "Unexpected batch size spread" ); + } + } + + @Override + public Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session, + LockOptions lockOptions) { + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { + final Serializable[] batch = session.getPersistenceContextInternal() + .getBatchFetchQueue() + .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); + + final int numberOfIds = ArrayHelper.countNonNull( batch ); + + // Uses the first batch-size bigger than the number of actual ids in the batch + int indexToUse = batchSizes.length-1; + for ( int i = 0; i < batchSizes.length-1; i++ ) { + if ( batchSizes[i] >= numberOfIds ) { + indexToUse = i; + } + else { + break; + } + } + + final Serializable[] idsToLoad = new Serializable[ batchSizes[indexToUse] ]; + System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds ); + for ( int i = numberOfIds; i < batchSizes[indexToUse]; i++ ) { + idsToLoad[i] = id; + } + + final List results = loaders[indexToUse].loadEntityBatch( + session, + idsToLoad, + persister().getIdentifierType(), + optionalObject, + persister().getEntityName(), + id, + persister(), + lockOptions, + readOnly + ); + + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + idsToLoad, + results, + persister(), + session + ); + + return getObjectFromList( results, id, session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoaderBuilder.java new file mode 100644 index 0000000000..f65fc161fe --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/PaddedBatchingEntityLoaderBuilder.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.loader.entity.plan; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.entity.UniqueEntityLoader; +import org.hibernate.persister.entity.OuterJoinLoadable; + +/** + * @author Steve Ebersole + */ +public class PaddedBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder { + /** + * Singleton access + */ + public static final PaddedBatchingEntityLoaderBuilder INSTANCE = new PaddedBatchingEntityLoaderBuilder(); + + @Override + protected UniqueEntityLoader buildBatchingLoader( + OuterJoinLoadable persister, + int batchSize, + LockMode lockMode, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return buildBatchingLoader( persister, batchSize, LockOptions.interpret( lockMode ), factory, influencers ); + } + + @Override + protected UniqueEntityLoader buildBatchingLoader( + OuterJoinLoadable persister, + int batchSize, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return new PaddedBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/package-info.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/package-info.java new file mode 100644 index 0000000000..97466f1037 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/package-info.java @@ -0,0 +1,12 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/** + * Support for entity loaders built on top of the {@link org.hibernate.loader.plan} + * API to apply entity-graphs + */ +package org.hibernate.loader.entity.plan; diff --git a/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphDynamicBatchStyleTest.java b/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphDynamicBatchStyleTest.java new file mode 100644 index 0000000000..976a4da915 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphDynamicBatchStyleTest.java @@ -0,0 +1,143 @@ +package org.hibernate.graph; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.loader.BatchFetchStyle; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import org.hamcrest.CoreMatchers; + +import static javax.persistence.CascadeType.MERGE; +import static javax.persistence.CascadeType.PERSIST; +import static javax.persistence.CascadeType.REMOVE; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; +import static org.junit.Assert.assertTrue; + +/** + * @author David Hoffer + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-14312" ) +public class EntityGraphDynamicBatchStyleTest extends BaseEntityManagerFunctionalTestCase { + private static final int BATCH_SIZE = 5; + private static final int NUM_OF_LOCATIONS = (BATCH_SIZE * 2) + 1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Fruit.class, + FruitLocation.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.DYNAMIC ); + options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, BATCH_SIZE); + } + + @Before + public void setUp() { + inTransaction( + entityManagerFactory(), + entityManager -> { + final Fruit fruit = new Fruit( 1, "Goji" ); + + for ( int i = 1; i <= NUM_OF_LOCATIONS; i++ ) { + fruit.locations.add( + new FruitLocation( i, "Goji location #" + i, fruit ) + ); + } + + entityManager.persist( fruit ); + } + ); + } + + @Test + public void testEntityGraphSemantic() { + inTransaction( + entityManagerFactory(), + entityManager -> { + final Map hints = Collections.singletonMap( + GraphSemantic.FETCH.getJpaHintName(), + GraphParser.parse( Fruit.class, "locations", entityManager ) + ); + + final Fruit fruit = entityManager.find( Fruit.class, 1, hints ); + assertTrue( Hibernate.isInitialized( fruit.locations ) ); + assertThat( fruit.locations.size(), is( NUM_OF_LOCATIONS ) ); + + fruit.locations.forEach( + fruitLocation -> { + assertTrue( Hibernate.isInitialized( fruitLocation ) ); + } + ); + } + ); + } + + @Entity(name = "Fruit") + @Table(name = "Fruit") + static class Fruit { + + @Id + Integer id; + + @Column + String name; + + @OneToMany( mappedBy = "fruit", cascade = { PERSIST, MERGE, REMOVE } ) + Set locations = new HashSet<>(); + + public Fruit() { + } + + public Fruit(Integer id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "FruitLocation") + @Table(name = "FruitLocation") + static class FruitLocation { + + @Id + Integer id; + + @Column + String location; + + @ManyToOne + Fruit fruit; + + public FruitLocation() { + } + + public FruitLocation(Integer id, String location, Fruit fruit) { + this.id = id; + this.location = location; + this.fruit = fruit; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphPaddedBatchStyleTest.java b/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphPaddedBatchStyleTest.java index c13fc85125..65d12a2837 100644 --- a/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphPaddedBatchStyleTest.java +++ b/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphPaddedBatchStyleTest.java @@ -14,7 +14,6 @@ import javax.persistence.Table; import org.hibernate.Hibernate; import org.hibernate.cfg.AvailableSettings; -import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.loader.BatchFetchStyle; @@ -22,6 +21,12 @@ import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; +import static javax.persistence.CascadeType.MERGE; +import static javax.persistence.CascadeType.PERSIST; +import static javax.persistence.CascadeType.REMOVE; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.loader.BatchFetchStyle.PADDED; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertTrue; @@ -31,6 +36,8 @@ import static org.junit.Assert.assertTrue; */ @TestForIssue( jiraKey = "HHH-14312" ) public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctionalTestCase { + private static final int BATCH_SIZE = 5; + private static final int NUM_OF_LOCATIONS = (BATCH_SIZE * 2) + 1; @Override protected Class[] getAnnotatedClasses() { @@ -42,56 +49,77 @@ public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctional @Override protected void addConfigOptions(Map options) { - options.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED ); - options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "50"); + options.put( AvailableSettings.BATCH_FETCH_STYLE, PADDED ); + options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, BATCH_SIZE ); } @Before public void setUp() { - doInJPA( this::entityManagerFactory, entityManager -> { - Fruit fruit = new Fruit(); - fruit.id = 1; - FruitLocation location1 = new FruitLocation(); - location1.fruit = fruit; - FruitLocation location2 = new FruitLocation(); - location2.fruit = fruit; - fruit.locations.add( location1 ); - fruit.locations.add( location2 ); - entityManager.persist( fruit ); - entityManager.persist( location1 ); - entityManager.persist( location2 ); - } ); + doInJPA( + this::entityManagerFactory, + entityManager -> { + final Fruit fruit = new Fruit( 1, "Goji" ); + + for ( int i = 1; i <= NUM_OF_LOCATIONS; i++ ) { + fruit.locations.add( + new FruitLocation( i, "Goji location #" + i, fruit ) + ); + } + + entityManager.persist( fruit ); + } + ); } @Test public void testEntityGraphSemantic() { - doInJPA( this::entityManagerFactory, entityManager -> { - final RootGraphImplementor graph = (RootGraphImplementor) GraphParser.parse( Fruit.class, "locations", entityManager ); - final Map hints = Collections.singletonMap( GraphSemantic.FETCH.getJpaHintName(), graph ); - final Fruit fruit = entityManager.find( Fruit.class, 1, hints ); - assertTrue( Hibernate.isInitialized( fruit.locations ) ); - } ); + doInJPA( + this::entityManagerFactory, + entityManager -> { + final Map hints = Collections.singletonMap( + GraphSemantic.FETCH.getJpaHintName(), + GraphParser.parse( Fruit.class, "locations", entityManager ) + ); + + final Fruit fruit = entityManager.find( Fruit.class, 1, hints ); + assertTrue( Hibernate.isInitialized( fruit.locations ) ); + assertThat( fruit.locations.size(), is( NUM_OF_LOCATIONS ) ); + + fruit.locations.forEach( + fruitLocation -> { + assertTrue( Hibernate.isInitialized( fruitLocation ) ); + } + ); + } + ); } @Entity(name = "Fruit") @Table(name = "Fruit") static class Fruit { - @Id Integer id; @Column String name; - @OneToMany(mappedBy = "fruit") + @OneToMany( mappedBy = "fruit", cascade = { PERSIST, MERGE, REMOVE } ) Set locations = new HashSet<>(); + + public Fruit() { + } + + public Fruit(Integer id, String name) { + this.id = id; + this.name = name; + } } @Entity(name = "FruitLocation") @Table(name = "FruitLocation") static class FruitLocation { - @Id @GeneratedValue + @Id Integer id; @Column @@ -99,6 +127,15 @@ public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctional @ManyToOne Fruit fruit; + + public FruitLocation() { + } + + public FruitLocation(Integer id, String location, Fruit fruit) { + this.id = id; + this.location = location; + this.fruit = fruit; + } } }