diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java index 8f7f021ea0..2a45061553 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java @@ -76,7 +76,7 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup if ( numberOfIds <= 1 ) { initializeSingleIdLoaderIfNeeded( session ); - final T result = singleIdLoader.load( pkValue, lockOptions, readOnly, session ); + final T result = singleIdLoader.load( pkValue, entityInstance, lockOptions, readOnly, 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. @@ -144,7 +144,14 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup JdbcSelectExecutorStandardImpl.INSTANCE.list( jdbcSelect, jdbcParameterBindings, - getExecutionContext( entityInstance, readOnly, session, subSelectFetchableKeysHandler ), + getExecutionContext( + pkValue, + entityInstance, + readOnly, + lockOptions, + session, + subSelectFetchableKeysHandler + ), RowTransformerPassThruImpl.instance(), ListResultsConsumer.UniqueSemantic.FILTER ); @@ -163,8 +170,10 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup } private ExecutionContext getExecutionContext( + Object entityId, Object entityInstance, Boolean readOnly, + LockOptions lockOptions, SharedSessionContractImplementor session, SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler) { return new ExecutionContext() { @@ -178,6 +187,11 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup return entityInstance; } + @Override + public Object getEntityId() { + return entityId; + } + @Override public QueryOptions getQueryOptions() { return new QueryOptionsAdapter() { @@ -185,6 +199,11 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup public Boolean isReadOnly() { return readOnly; } + + @Override + public LockOptions getLockOptions() { + return lockOptions; + } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java index e4a0a9313b..6fc34545cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java @@ -15,6 +15,7 @@ import java.util.function.Consumer; import org.hibernate.HibernateException; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -604,6 +605,11 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement return navigablePath; } + @Override + public EntityKey getEntityKey() { + return null; + } + @Override public ModelPart getInitializedPart() { return naturalIdMapping; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java index 87ff9594a1..6745d638c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph; import java.util.function.Consumer; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.query.NavigablePath; /** @@ -26,6 +27,8 @@ public interface FetchParentAccess extends Initializer { NavigablePath getNavigablePath(); + EntityKey getEntityKey(); + /** * Register a listener to be notified when the parent is "resolved" * diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java index 716aaf4162..422779d5b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java @@ -26,6 +26,7 @@ import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.collection.CollectionInitializer; +import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.NullValueAssembler; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -165,8 +166,8 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA final PropertyAccess parentInjectionPropertyAccess = embeddedModelPartDescriptor.getParentInjectionAttributePropertyAccess(); + Initializer initializer = rowProcessingState.resolveInitializer( navigablePath.getParent() ); if ( parentInjectionPropertyAccess != null ) { - Initializer initializer = rowProcessingState.resolveInitializer( navigablePath.getParent() ); final Object owner; if ( initializer instanceof CollectionInitializer ) { owner = ( (CollectionInitializer) initializer ).getCollectionInstance().getOwner(); @@ -214,15 +215,27 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA else { notifyParentResolutionListeners( compositeInstance ); if ( compositeInstance instanceof HibernateProxy ) { - Object target = embeddedModelPartDescriptor.getEmbeddableTypeDescriptor() - .getRepresentationStrategy() - .getInstantiator() - .instantiate( VALUE_ACCESS, rowProcessingState.getSession().getFactory() ); - embeddedModelPartDescriptor.getEmbeddableTypeDescriptor().setPropertyValues( - target, - resolvedValues - ); - ( (HibernateProxy) compositeInstance ).getHibernateLazyInitializer().setImplementation( target ); + if ( initializer != this ) { + ( (AbstractEntityInitializer) initializer ).registerResolutionListener( + entityInstance -> { + embeddedModelPartDescriptor.getEmbeddableTypeDescriptor().setPropertyValues( + entityInstance, + resolvedValues + ); + } + ); + } + else { + Object target = embeddedModelPartDescriptor.getEmbeddableTypeDescriptor() + .getRepresentationStrategy() + .getInstantiator() + .instantiate( VALUE_ACCESS, rowProcessingState.getSession().getFactory() ); + embeddedModelPartDescriptor.getEmbeddableTypeDescriptor().setPropertyValues( + target, + resolvedValues + ); + ( (HibernateProxy) compositeInstance ).getHibernateLazyInitializer().setImplementation( target ); + } } // At this point, createEmptyCompositesEnabled is always true. // We can only set the property values on the compositeInstance though if there is at least one non null value. diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java index 0ae5bca5fc..17ecb52631 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.graph.embeddable.internal; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.embeddable.AbstractEmbeddableInitializer; @@ -25,6 +26,11 @@ public class EmbeddableFetchInitializer @Override public Object getParentKey() { - return getFetchParentAccess().getParentKey(); + return findFirstEntityDescriptorAccess().getParentKey(); + } + + @Override + public EntityKey getEntityKey() { + return findFirstEntityDescriptorAccess().getEntityKey(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java index a4336bafb5..838ff1715d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.graph.embeddable.internal; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.embeddable.AbstractEmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; @@ -22,7 +23,12 @@ public class EmbeddableResultInitializer extends AbstractEmbeddableInitializer { @Override public Object getParentKey() { - return null; + return findFirstEntityDescriptorAccess().getParentKey(); + } + + @Override + public EntityKey getEntityKey() { + return findFirstEntityDescriptorAccess().getEntityKey(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index b78bc31d57..92c2847a04 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -581,6 +581,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces return existingLoadingEntry.getEntityInstance(); } + assert existingLoadingEntry == null || existingLoadingEntry.getEntityInstance() == null; + Object instance = null; // this isEntityReturn bit is just for entity loaders, not hql/criteria diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java new file mode 100644 index 0000000000..0d8230851a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java @@ -0,0 +1,222 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.graph.entity.internal; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.UniqueKeyLoadable; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.results.graph.AbstractFetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; +import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.EntityLoadingLogger; +import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.internal.log.LoggingHelper.toLoggableString; + +public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess implements EntityInitializer { + private static final String CONCRETE_NAME = BatchEntitySelectFetchInitializer.class.getSimpleName(); + + private FetchParentAccess parentAccess; + private final NavigablePath navigablePath; + + protected final EntityPersister concreteDescriptor; + protected final DomainResultAssembler identifierAssembler; + private final ToOneAttributeMapping referencedModelPart; + + protected Object entityInstance; + private EntityKey entityKey; + + private Map toBatchLoad = new LinkedHashMap<>(); + + public BatchEntitySelectFetchInitializer( + FetchParentAccess parentAccess, + ToOneAttributeMapping referencedModelPart, + NavigablePath fetchedNavigable, + EntityPersister concreteDescriptor, + DomainResultAssembler identifierAssembler) { + this.parentAccess = parentAccess; + this.referencedModelPart = referencedModelPart; + this.navigablePath = fetchedNavigable; + this.concreteDescriptor = concreteDescriptor; + this.identifierAssembler = identifierAssembler; + } + + public ModelPart getInitializedPart() { + return referencedModelPart; + } + + @Override + public NavigablePath getNavigablePath() { + return navigablePath; + } + + @Override + public void resolveKey(RowProcessingState rowProcessingState) { + + + } + + @Override + public void resolveInstance(RowProcessingState rowProcessingState) { + + } + + @Override + public void initializeInstance(RowProcessingState rowProcessingState) { + final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); + if ( entityIdentifier == null ) { + return; + } + entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); + + final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContextInternal(); + entityInstance = persistenceContext.getEntity( entityKey ); + if ( entityInstance != null ) { + return; + } + Initializer initializer = rowProcessingState.getJdbcValuesSourceProcessingState() + .findInitializer( entityKey ); + + if ( initializer != null ) { + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Found an initializer for entity (%s) : %s", + CONCRETE_NAME, + toLoggableString( getNavigablePath(), entityIdentifier ), + entityIdentifier + ); + } + initializer.resolveInstance( rowProcessingState ); + entityInstance = initializer.getInitializedInstance(); + // EARLY EXIT!!! + return; + } + + final LoadingEntityEntry existingLoadingEntry = rowProcessingState.getSession() + .getPersistenceContext() + .getLoadContexts() + .findLoadingEntityEntry( entityKey ); + + if ( existingLoadingEntry != null ) { + if ( existingLoadingEntry.getEntityInitializer() != this ) { + // the entity is already being loaded elsewhere + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", + CONCRETE_NAME, + toLoggableString( getNavigablePath(), entityIdentifier ), + existingLoadingEntry.getEntityInitializer() + ); + } + this.entityInstance = existingLoadingEntry.getEntityInstance(); + + // EARLY EXIT!!! + return; + } + } + + persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( entityKey ); + toBatchLoad.put( entityKey, parentAccess.getInitializedInstance() ); + } + + @Override + public void finishUpRow(RowProcessingState rowProcessingState) { + entityInstance = null; + clearParentResolutionListeners(); + } + + @Override + public EntityPersister getEntityDescriptor() { + return concreteDescriptor; + } + + @Override + public Object getEntityInstance() { + return entityInstance; + } + + @Override + public EntityKey getEntityKey() { + return entityKey; + } + + @Override + public Object getParentKey() { + throw new NotYetImplementedFor6Exception( getClass() ); + } + + @Override + public void registerResolutionListener(Consumer listener) { + if ( entityInstance != null ) { + listener.accept( entityInstance ); + } + else { + super.registerResolutionListener( listener ); + } + } + + @Override + public EntityPersister getConcreteDescriptor() { + return concreteDescriptor; + } + + @Override + public String toString() { + return "EntitySelectFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } + + @Override + public void endLoading(ExecutionContext context) { + toBatchLoad.forEach( + (entityKey, parentInstance) -> { + final Object instance = context.getSession().internalLoad( + entityKey.getEntityName(), + entityKey.getIdentifier(), + true, + referencedModelPart.isNullable() + ); + if ( instance != null ) { + ( (AbstractEntityPersister) referencedModelPart.getDeclaringType() ).setPropertyValue( + parentInstance, + referencedModelPart.getPartName(), + instance + ); + final EntityEntry entry = context.getSession() + .getPersistenceContext() + .getEntry( parentInstance ); + final int propertyIndex = ( (UniqueKeyLoadable) ( (AbstractEntityInitializer) parentAccess ).getEntityDescriptor() ).getPropertyIndex( + referencedModelPart.getPartName() ); + if ( entry != null ) { + final Object[] loadedState = entry.getLoadedState(); + if ( loadedState != null ) { + loadedState[propertyIndex] = instance; + } + } + } + } + ); + toBatchLoad = null; + parentAccess = null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java index e7534c3a92..5da1a41051 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java @@ -68,13 +68,24 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { result.createResultAssembler( creationState ) ); } - return new EntitySelectFetchInitializer( - parentAccess, - fetchedAttribute, - getNavigablePath(), - entityPersister, - result.createResultAssembler( creationState ) - ); + if ( entityPersister.isBatchLoadable() ) { + return new BatchEntitySelectFetchInitializer( + parentAccess, + fetchedAttribute, + getNavigablePath(), + entityPersister, + result.createResultAssembler( creationState ) + ); + } + else { + return new EntitySelectFetchInitializer( + parentAccess, + fetchedAttribute, + getNavigablePath(), + entityPersister, + result.createResultAssembler( creationState ) + ); + } } ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java index 971ed8023f..28c131b42e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java @@ -126,8 +126,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl return; } - Initializer initializer = rowProcessingState.getJdbcValuesSourceProcessingState().findInitializer( - entityKey ); + Initializer initializer = rowProcessingState.getJdbcValuesSourceProcessingState() + .findInitializer( entityKey ); if ( initializer != null ) { if ( EntityLoadingLogger.DEBUG_ENABLED ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java index e59e5517c0..a66e34719d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java @@ -15,6 +15,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.BiDirectionalFetch; @@ -27,6 +28,7 @@ import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.BatchEntitySelectFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchByUniqueKeyInitializer; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; @@ -116,13 +118,25 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association { ); } if ( timing == FetchTiming.IMMEDIATE ) { - return new EntitySelectFetchInitializer( - parentAccess, - (ToOneAttributeMapping) referencedModelPart, - getReferencedPath(), - entityMappingType.getEntityPersister(), - resultAssembler - ); + final EntityPersister entityPersister = entityMappingType.getEntityPersister(); + if ( entityPersister.isBatchLoadable() ) { + return new BatchEntitySelectFetchInitializer( + parentAccess, + (ToOneAttributeMapping) referencedModelPart, + getReferencedPath(), + entityPersister, + resultAssembler + ); + } + else { + return new EntitySelectFetchInitializer( + parentAccess, + (ToOneAttributeMapping) referencedModelPart, + getReferencedPath(), + entityPersister, + resultAssembler + ); + } } else { return new EntityDelayedFetchInitializer( diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/A.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/A.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/A.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/A.java index 5e36727fc9..4c483dc78f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/A.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/A.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/B.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/B.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/B.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/B.java index 0b9dff4aed..b3c0a0ee1f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/B.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/B.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import org.hibernate.annotations.BatchSize; diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BId.java similarity index 83% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/BId.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BId.java index c2c3e888c4..5a858c96b0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BId.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BId.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import java.io.Serializable; @@ -31,4 +31,8 @@ public class BId this.idPart2 = idPart2; } + @Override + public String toString() { + return "BId (" + idPart1 + ", " + idPart2 + ")"; + } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchBootstrapTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchBootstrapTest.java similarity index 67% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchBootstrapTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchBootstrapTest.java index 54af121c3b..11e014c31d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchBootstrapTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchBootstrapTest.java @@ -1,7 +1,16 @@ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import java.util.LinkedHashSet; import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -9,33 +18,20 @@ import jakarta.persistence.Id; import jakarta.persistence.ManyToMany; import jakarta.persistence.MappedSuperclass; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; - -public class BatchFetchBootstrapTest extends BaseCoreFunctionalTestCase { - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - JafSid.class, UserGroup.class - }; - } - - @Override - protected void configure(Configuration configuration) { - configuration.setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "30"); - } - - @Override - protected void buildSessionFactory() { - } +@DomainModel( + annotatedClasses = { + BatchFetchBootstrapTest.JafSid.class, BatchFetchBootstrapTest.UserGroup.class + } +) +@SessionFactory +@ServiceRegistry( + settings = @Setting( name = AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, value = "30") +) +public class BatchFetchBootstrapTest { @Test public void test() { - super.buildSessionFactory(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java similarity index 75% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java index a5ae54a2c2..f8d630332c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java @@ -4,10 +4,28 @@ * 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.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import java.util.ArrayList; import java.util.List; + +import org.hibernate.Session; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.engine.spi.BatchFetchQueue; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import jakarta.persistence.ConstraintMode; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; @@ -15,74 +33,54 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; -import org.hibernate.Session; -import org.hibernate.annotations.BatchSize; -import org.hibernate.annotations.NotFound; -import org.hibernate.annotations.NotFoundAction; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.engine.spi.BatchFetchQueue; -import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.resource.jdbc.spi.StatementInspector; - -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * @author Gail Badner * @author Stephen Fikes */ -public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctionalTestCase { - private static final AStatementInspector statementInspector = new AStatementInspector(); +@DomainModel( + annotatedClasses = { + BatchFetchNotFoundIgnoreDynamicStyleTest.Employee.class, + BatchFetchNotFoundIgnoreDynamicStyleTest.Task.class + } +) +@SessionFactory( + statementInspectorClass = BatchFetchNotFoundIgnoreDynamicStyleTest.AStatementInspector.class +) +public class BatchFetchNotFoundIgnoreDynamicStyleTest { private static final int NUMBER_OF_EMPLOYEES = 8; private List tasks = new ArrayList<>(); - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Employee.class, Task.class }; - } - @Override - protected void configure(Configuration configuration) { - super.configure( configuration ); - configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, statementInspector ); - } - - @Before - public void createData() { + @BeforeEach + public void createData(SessionFactoryScope scope) { tasks.clear(); - tasks = doInHibernate( - this::sessionFactory, session -> { - for (int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++) { + tasks = scope.fromTransaction( + session -> { + for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) { Task task = new Task(); task.id = i; tasks.add( task ); session.persist( task ); - Employee e = new Employee("employee0" + i); + Employee e = new Employee( "employee0" + i ); e.task = task; - session.persist(e); + session.persist( e ); } return tasks; } ); } - @After - public void deleteData() { - doInHibernate( - this::sessionFactory, session -> { + @AfterEach + public void deleteData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { session.createQuery( "delete from Task" ).executeUpdate(); session.createQuery( "delete from Employee" ).executeUpdate(); } @@ -90,10 +88,10 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional } @Test - public void testSeveralNotFoundFromQuery() { + public void testSeveralNotFoundFromQuery(SessionFactoryScope scope) { - doInHibernate( - this::sessionFactory, session -> { + scope.inTransaction( + session -> { // delete 2nd and 8th Task so that the non-found Task entities will be queried // in 2 different batches. session.delete( tasks.get( 1 ) ); @@ -101,14 +99,15 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional } ); + final AStatementInspector statementInspector = (AStatementInspector) scope.getStatementInspector(); statementInspector.clear(); - final List employees = doInHibernate( - this::sessionFactory, session -> { + final List employees = scope.fromTransaction( + session -> { List results = session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); - for ( int i = 0 ; i < tasks.size() ; i++ ) { - checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + for ( Task task : tasks ) { + checkInBatchFetchQueue( task.id, session, false ); } return results; } @@ -145,7 +144,7 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional assertEquals( 1, paramterCounts.get( 3 ).intValue() ); assertEquals( NUMBER_OF_EMPLOYEES, employees.size() ); - for ( int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++ ) { + for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) { if ( i == 1 || i == 7 ) { assertNull( employees.get( i ).task ); } @@ -156,10 +155,10 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional } @Test - public void testMostNotFoundFromQuery() { + public void testMostNotFoundFromQuery(SessionFactoryScope scope) { - doInHibernate( - this::sessionFactory, session -> { + scope.inTransaction( + session -> { // delete all but last Task entity for ( int i = 0; i < 7; i++ ) { session.delete( tasks.get( i ) ); @@ -167,14 +166,15 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional } ); + final AStatementInspector statementInspector = (AStatementInspector) scope.getStatementInspector(); statementInspector.clear(); - final List employees = doInHibernate( - this::sessionFactory, session -> { + final List employees = scope.fromTransaction( + session -> { List results = session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); - for ( int i = 0 ; i < tasks.size() ; i++ ) { - checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + for ( Task task : tasks ) { + checkInBatchFetchQueue( task.id, session, false ); } return results; } @@ -233,7 +233,7 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional assertEquals( NUMBER_OF_EMPLOYEES, employees.size() ); - for ( int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++ ) { + for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) { if ( i == 7 ) { assertEquals( tasks.get( i ).id, employees.get( i ).task.id ); } @@ -244,19 +244,20 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional } @Test - public void testNotFoundFromGet() { + public void testNotFoundFromGet(SessionFactoryScope scope) { - doInHibernate( - this::sessionFactory, session -> { + scope.inTransaction( + session -> { // delete task so it is not found later when getting the Employee. session.delete( tasks.get( 0 ) ); } ); + final AStatementInspector statementInspector = (AStatementInspector) scope.getStatementInspector(); statementInspector.clear(); - doInHibernate( - this::sessionFactory, session -> { + scope.inTransaction( + session -> { Employee employee = session.get( Employee.class, "employee00" ); checkInBatchFetchQueue( tasks.get( 0 ).id, session, false ); assertNotNull( employee ); @@ -266,18 +267,17 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional final List paramterCounts = statementInspector.parameterCounts; - // there should be 2 SQL statements executed - // 1) query to load Employee entity by ID (associated Tasks is registered for batch loading) - // 2) batch will only contain the ID for the associated Task (which will not be found) - assertEquals( 2, paramterCounts.size() ); + // there should be 1 SQL statements executed, we select the tasks with a join + // 1) query to load Employee entity by ID and fetch the Tasks + assertEquals( 1, paramterCounts.size() ); // query loading Employee entities shouldn't have any parameters assertEquals( 1, paramterCounts.get( 0 ).intValue() ); - // Will result in just querying a single Task (because the batch is empty). - // query should have 1 parameter; - // Task won't be found. - assertEquals( 1, paramterCounts.get( 1 ).intValue() ); +// // Will result in just querying a single Task (because the batch is empty). +// // query should have 1 parameter; +// // Task won't be found. +// assertEquals( 1, paramterCounts.get( 1 ).intValue() ); } private static void checkInBatchFetchQueue(long id, Session session, boolean expected) { @@ -298,7 +298,7 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional @Id private String name; - @OneToOne(optional = true) + @OneToOne @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) @NotFound(action = NotFoundAction.IGNORE) private Task task; @@ -324,13 +324,18 @@ public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctional public static class AStatementInspector implements StatementInspector { private List parameterCounts = new ArrayList<>(); + public AStatementInspector() { + } + public String inspect(String sql) { parameterCounts.add( countParameters( sql ) ); return sql; } + private void clear() { parameterCounts.clear(); } + private int countParameters(String sql) { int count = 0; int parameterIndex = sql.indexOf( '?' ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchReferencedColumnNameTest.java similarity index 67% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchReferencedColumnNameTest.java index 8ca5c226cb..31a497233a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchReferencedColumnNameTest.java @@ -4,13 +4,21 @@ * 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.test.batchfetch; - -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +package org.hibernate.orm.test.batchfetch; import java.time.ZonedDateTime; import java.util.List; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -21,34 +29,30 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import jakarta.persistence.Table; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Assert; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -public class BatchFetchReferencedColumnNameTest extends BaseCoreFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[]{ Child.class, Parent.class }; - } - - @Override - protected void configure(Configuration configuration) { - super.configure( configuration ); - - configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); - configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); - - configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "64" ); - } +@DomainModel( + annotatedClasses = { + BatchFetchReferencedColumnNameTest.Child.class, + BatchFetchReferencedColumnNameTest.Parent.class + } +) +@SessionFactory +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.SHOW_SQL, value = "true"), + @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"), + @Setting(name = AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, value = "64") + } +) +public class BatchFetchReferencedColumnNameTest { @Test @TestForIssue(jiraKey = "HHH-13059") - public void test() throws Exception { - doInHibernate( this::sessionFactory, session -> { + public void test(SessionFactoryScope scope) throws Exception { + scope.inTransaction( session -> { Parent p = new Parent(); p.setId( 1L ); session.save( p ); @@ -66,11 +70,11 @@ public class BatchFetchReferencedColumnNameTest extends BaseCoreFunctionalTestCa session.save( c2 ); } ); - doInHibernate( this::sessionFactory, session -> { + scope.inTransaction( session -> { Parent p = session.get( Parent.class, 1L ); - Assert.assertNotNull( p ); + assertNotNull( p ); - Assert.assertEquals( 2, p.getChildren().size() ); + assertEquals( 2, p.getChildren().size() ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchRefreshTest.java similarity index 71% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchRefreshTest.java index 3ae3d8773c..6c92c73741 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchRefreshTest.java @@ -4,11 +4,21 @@ * 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.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import java.util.LinkedHashSet; -import java.util.Map; import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -18,26 +28,29 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import org.hibernate.cfg.AvailableSettings; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.*; - -public class BatchFetchRefreshTest extends BaseNonConfigCoreFunctionalTestCase { +@DomainModel( + annotatedClasses = { + BatchFetchRefreshTest.Parent.class, + BatchFetchRefreshTest.Child.class + } +) +@SessionFactory +@ServiceRegistry( + settings = @Setting(name = AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, value = "8") +) +public class BatchFetchRefreshTest { @Test - - public void testRefreshWithBatch() { - - doInHibernate( this::sessionFactory, session -> { + public void testRefreshWithBatch(SessionFactoryScope scope) { + scope.inTransaction( session -> { // Retrieve one of the parents into the session. - Parent parent = session.find(Parent.class, 1); - Assert.assertNotNull(parent); + Parent parent = session.find( Parent.class, 1 ); + assertNotNull( parent ); // Retrieve children but keep their parents lazy! // This allows batch fetching to do its thing when we refresh below. @@ -50,16 +63,16 @@ public class BatchFetchRefreshTest extends BaseNonConfigCoreFunctionalTestCase { parent.getChildren().size(); // Another interesting thing to note - em.getLockMode returns an incorrect value after the above refresh - Assert.assertEquals( LockModeType.PESSIMISTIC_WRITE, session.getLockMode( parent ) ); - }); + assertEquals( LockModeType.PESSIMISTIC_WRITE, session.getLockMode( parent ) ); + } ); } - @Before - public void setupData() { + @BeforeEach + public void setupData(SessionFactoryScope scope) { final int numParents = 5; final int childrenPerParent = 2; - doInHibernate( this::sessionFactory, session -> { + scope.inTransaction( session -> { int k = 1; for ( int i = 1; i <= numParents; i++ ) { Parent parent = new Parent(); @@ -69,7 +82,7 @@ public class BatchFetchRefreshTest extends BaseNonConfigCoreFunctionalTestCase { session.persist( parent ); // Create some children for each parent... - for ( int j = 0; j < childrenPerParent; j++,k++ ) { + for ( int j = 0; j < childrenPerParent; j++, k++ ) { Child child = new Child(); child.childId = k; child.name = "Child_" + i + "_" + j; @@ -79,24 +92,10 @@ public class BatchFetchRefreshTest extends BaseNonConfigCoreFunctionalTestCase { session.persist( child ); } } - }); + } ); } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Parent.class, - Child.class - }; - } - - @Override - protected void addSettings(Map settings) { - super.addSettings( settings ); - settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "8" ); - } - @Entity(name = "Parent") public static class Parent { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchTest.java new file mode 100644 index 0000000000..370bdd3084 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchTest.java @@ -0,0 +1,195 @@ +/* + * 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.orm.test.batchfetch; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * @author Gavin King + */ +@DomainModel( + xmlMappings = "org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml", + annotatedClasses = BatchLoadableEntity.class +) +@SessionFactory( + generateStatistics = true +) +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "false") + } +) +public class BatchFetchTest { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testBatchFetch(SessionFactoryScope scope) { + ProductLine ossProductLine = new ProductLine(); + Model hibernateModel = new Model( ossProductLine ); + scope.inTransaction( + session -> { + ProductLine cars = new ProductLine(); + cars.setDescription( "Cars" ); + Model monaro = new Model( cars ); + monaro.setName( "monaro" ); + monaro.setDescription( "Holden Monaro" ); + Model hsv = new Model( cars ); + hsv.setName( "hsv" ); + hsv.setDescription( "Holden Commodore HSV" ); + session.save( cars ); + + ossProductLine.setDescription( "OSS" ); + Model jboss = new Model( ossProductLine ); + jboss.setName( "JBoss" ); + jboss.setDescription( "JBoss Application Server" ); + + hibernateModel.setName( "Hibernate" ); + hibernateModel.setDescription( "Hibernate" ); + Model cache = new Model( ossProductLine ); + cache.setName( "JBossCache" ); + cache.setDescription( "JBoss TreeCache" ); + session.save( ossProductLine ); + } + ); + + scope.getSessionFactory().getCache().evictEntityRegion( Model.class ); + scope.getSessionFactory().getCache().evictEntityRegion( ProductLine.class ); + + scope.inTransaction( + session -> { + List list = session.createQuery( "from ProductLine pl order by pl.description" ) + .list(); + ProductLine cars = list.get( 0 ); + ProductLine oss = list.get( 1 ); + assertFalse( Hibernate.isInitialized( cars.getModels() ) ); + assertFalse( Hibernate.isInitialized( oss.getModels() ) ); + assertEquals( 2, cars.getModels().size() ); //fetch both collections + assertTrue( Hibernate.isInitialized( cars.getModels() ) ); + assertTrue( Hibernate.isInitialized( oss.getModels() ) ); + + session.clear(); + + List models = session.createQuery( "from Model m" ).list(); + Model hibernate = session.get( Model.class, hibernateModel.getId() ); + hibernate.getProductLine().getId(); + for ( Model aList : models ) { + assertFalse( Hibernate.isInitialized( aList.getProductLine() ) ); + } + assertEquals( hibernate.getProductLine().getDescription(), "OSS" ); //fetch both productlines + + session.clear(); + + Iterator iter = session.createQuery( "from Model" ).list().iterator(); + models = new ArrayList(); + while ( iter.hasNext() ) { + models.add( iter.next() ); + } + Model m = models.get( 0 ); + m.getDescription(); //fetch a batch of 4 + + session.clear(); + + list = session.createQuery( "from ProductLine" ).list(); + ProductLine pl = list.get( 0 ); + ProductLine pl2 = list.get( 1 ); + session.evict( pl2 ); + pl.getModels().size(); //fetch just one collection! (how can we write an assertion for that??) + } + ); + + scope.inTransaction( + session -> { + List list = session.createQuery( "from ProductLine pl order by pl.description" ) + .list(); + ProductLine cars = list.get( 0 ); + ProductLine oss = list.get( 1 ); + assertEquals( cars.getModels().size(), 2 ); + assertEquals( oss.getModels().size(), 3 ); + session.delete( cars ); + session.delete( oss ); + } + ); + } + + @Test + @SuppressWarnings({ "unchecked" }) + public void testBatchFetch2(SessionFactoryScope scope) { + int size = 32 + 14; + scope.inTransaction( + session -> { + for ( int i = 0; i < size; i++ ) { + session.save( new BatchLoadableEntity( i ) ); + } + } + ); + + scope.inTransaction( + session -> { + // load them all as proxies + for ( int i = 0; i < size; i++ ) { + BatchLoadableEntity entity = session.load( BatchLoadableEntity.class, i ); + assertFalse( Hibernate.isInitialized( entity ) ); + } + scope.getSessionFactory().getStatistics().clear(); + // now start initializing them... + for ( int i = 0; i < size; i++ ) { + BatchLoadableEntity entity = session.load( BatchLoadableEntity.class, i ); + Hibernate.initialize( entity ); + assertTrue( Hibernate.isInitialized( entity ) ); + } + // so at this point, all entities are initialized. see how many fetches were performed. + final int expectedFetchCount; +// if ( sessionFactory().getSettings().getBatchFetchStyle() == BatchFetchStyle.LEGACY ) { +// expectedFetchCount = 3; // (32 + 10 + 4) +// } +// else if ( sessionFactory().getSettings().getBatchFetchStyle() == BatchFetchStyle.DYNAMIC ) { +// expectedFetchCount = 2; // (32 + 14) : because we limited batch-size to 32 +// } +// else { + // PADDED + expectedFetchCount = 2; // (32 + 16*) with the 16 being padded +// } + assertEquals( + expectedFetchCount, + scope.getSessionFactory().getStatistics() + .getEntityStatistics( BatchLoadableEntity.class.getName() ) + .getFetchCount() + ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete BatchLoadableEntity" ).executeUpdate(); + session.createQuery( "delete Model" ).executeUpdate(); + session.createQuery( "delete ProductLine" ).executeUpdate(); + } + ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchLoadableEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchLoadableEntity.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchLoadableEntity.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchLoadableEntity.java index 77d9422c53..e9db07c668 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchLoadableEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchLoadableEntity.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import jakarta.persistence.Entity; import jakarta.persistence.Id; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java index 362723fbb6..b94fc83747 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java @@ -1,9 +1,17 @@ package org.hibernate.orm.test.batchfetch; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertNotNull; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.annotations.Fetch; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; -import java.util.Map; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -11,38 +19,23 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.annotations.Fetch; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; -import org.hibernate.loader.BatchFetchStyle; -import org.hibernate.metamodel.spi.MetamodelImplementor; -import org.hibernate.persister.entity.EntityPersister; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; -public class BatchingEntityLoaderInitializationWithNoLockModeTest extends BaseEntityManagerFunctionalTestCase { +@Jpa( + annotatedClasses = { + BatchingEntityLoaderInitializationWithNoLockModeTest.MainEntity.class, + BatchingEntityLoaderInitializationWithNoLockModeTest.SubEntity.class + }, + properties = { @Setting(name = AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, value = "5") } +) +public class BatchingEntityLoaderInitializationWithNoLockModeTest { private Long mainId; - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { MainEntity.class, SubEntity.class }; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - protected Map buildSettings() { - Map settings = super.buildSettings(); - settings.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.LEGACY ); - settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, 5 ); - return settings; - } @Test - public void testJoin() { - doInJPA( this::entityManagerFactory, em -> { + public void testJoin(EntityManagerFactoryScope scope) { + scope.inTransaction( em -> { SubEntity sub = new SubEntity(); em.persist( sub ); @@ -51,9 +44,9 @@ public class BatchingEntityLoaderInitializationWithNoLockModeTest extends BaseEn em.persist( main ); this.mainId = main.getId(); - }); + } ); - doInJPA( this::entityManagerFactory, em -> { + scope.inTransaction( em -> { EntityPersister entityPersister = ( (MetamodelImplementor) em.getMetamodel() ) .entityPersister( MainEntity.class ); @@ -61,8 +54,9 @@ public class BatchingEntityLoaderInitializationWithNoLockModeTest extends BaseEn LockOptions lockOptions = new LockOptions( LockMode.NONE ); lockOptions.setTimeOut( 10 ); - MainEntity main = (MainEntity) entityPersister.load( this.mainId, null, lockOptions, - (SharedSessionContractImplementor) em ); + MainEntity main = (MainEntity) entityPersister. + load( this.mainId, null, lockOptions, (SharedSessionContractImplementor) em ); + assertNotNull( main.getSub() ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/City.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/City.java index 96d86db58e..b794e02655 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/City.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Country.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Country.java index 2c498df1a2..c18ae1fde5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Country.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; import java.util.List; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/DynamicBatchFetchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/DynamicBatchFetchTest.java new file mode 100644 index 0000000000..376dc9f309 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/DynamicBatchFetchTest.java @@ -0,0 +1,81 @@ +/* + * 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.orm.test.batchfetch; + +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DomainModel( + annotatedClasses = { A.class, B.class } +) +@SessionFactory +@ServiceRegistry( + settings = @Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "false") +) +public class DynamicBatchFetchTest { + private static int currentId = 1; + + @Test + public void testDynamicBatchFetch(SessionFactoryScope scope) { + Integer aId1 = createAAndB( "foo_1", scope ); + Integer aId2 = createAAndB( "foo_2", scope ); + + scope.inTransaction( + session -> { + List resultList = session.createQuery( "from A where id in (" + aId1 + "," + aId2 + ") order by id" ) + .list(); + A a1 = (A) resultList.get( 0 ); + A a2 = (A) resultList.get( 1 ); + assertEquals( aId1, a1.getId() ); + assertEquals( aId2, a2.getId() ); + assertFalse( Hibernate.isInitialized( a1.getB() ) ); + assertFalse( Hibernate.isInitialized( a2.getB() ) ); + B b = a1.getB(); + assertFalse( Hibernate.isInitialized( b ) ); + assertEquals( "foo_1", b.getOtherProperty() ); + assertTrue( Hibernate.isInitialized( a1.getB() ) ); + assertTrue( Hibernate.isInitialized( a2.getB() ) ); + assertEquals( "foo_2", a2.getB().getOtherProperty() ); + } + ); + } + + private int createAAndB(String otherProperty, SessionFactoryScope scope) { + scope.inTransaction( + session -> { + B b = new B(); + b.setIdPart1( currentId ); + b.setIdPart2( currentId ); + b.setOtherProperty( otherProperty ); + session.save( b ); + + A a = new A(); + a.setId( currentId ); + a.setB( b ); + + session.save( a ); + } + ); + + currentId++; + + return currentId - 1; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/DynamicBatchFetchTestCase.java similarity index 51% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/DynamicBatchFetchTestCase.java index 08c5f471b0..c31092b37c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/DynamicBatchFetchTestCase.java @@ -4,43 +4,40 @@ * 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.test.batchfetch; - -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.assertNotNull; +package org.hibernate.orm.test.batchfetch; import java.util.List; import java.util.stream.IntStream; import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.loader.BatchFetchStyle; + import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; -public class PaddedBatchFetchTestCase extends BaseCoreFunctionalTestCase { +import static org.junit.Assert.assertNotNull; - @Override - protected Class[] getAnnotatedClasses() { - return new Class[]{ Country.class, City.class }; - } - - @Override - protected void configure(Configuration configuration) { - super.configure( configuration ); - - configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); - configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); - - configuration.setProperty( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.name() ); - configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "15" ); - } +@DomainModel( + annotatedClasses = { Country.class, City.class } +) +@SessionFactory +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.SHOW_SQL, value = "true"), + @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"), + @Setting(name = AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, value = "15") + } +) +public class DynamicBatchFetchTestCase { @Test @TestForIssue(jiraKey = "HHH-12835") - public void paddedBatchFetchTest() throws Exception { - doInHibernate( this::sessionFactory, session -> { + public void batchFetchTest(SessionFactoryScope scope) { + scope.inTransaction( session -> { // Having DEFAULT_BATCH_FETCH_SIZE=15 // results in batchSizes = [15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] // Let's create 11 countries so batch size 15 will be used with padded values, @@ -54,7 +51,7 @@ public class PaddedBatchFetchTestCase extends BaseCoreFunctionalTestCase { } ); } ); - doInHibernate( this::sessionFactory, session -> { + scope.inTransaction( session -> { List allCities = session.createQuery( "from City", City.class ).list(); // this triggers countries to be fetched in batch diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Model.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Model.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/Model.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Model.java index cb66e056db..22e860c3dc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Model.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Model.java @@ -6,7 +6,7 @@ */ //$Id: Model.java 4460 2004-08-29 12:04:14Z oneovthafew $ -package org.hibernate.test.batchfetch; +package org.hibernate.orm.test.batchfetch; /** diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/ProductLine.hbm.xml b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/batchfetch/ProductLine.hbm.xml rename to hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml index acbbe00d16..a69a50aa1c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/ProductLine.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml @@ -9,7 +9,7 @@ "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - +