diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index 66e582ae1e..92e7670de4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -35,13 +35,13 @@ import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.graph.spi.AttributeNodeImplementor; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.FastSessionServices; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; import org.hibernate.proxy.HibernateProxy; import org.hibernate.stat.internal.StatsHelper; import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.type.EntityType; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; @@ -121,18 +121,13 @@ public final class TwoPhaseLoad { final boolean readOnly, final SharedSessionContractImplementor session, final PreLoadEvent preLoadEvent) { - final PersistenceContext persistenceContext = session.getPersistenceContext(); - final EntityEntry entityEntry = persistenceContext.getEntry( entity ); - if ( entityEntry == null ) { - throw new AssertionFailure( "possible non-threadsafe access to the session" ); - } final EventListenerGroup listenerGroup = session .getFactory() .getServiceRegistry() .getService( EventListenerRegistry.class ) .getEventListenerGroup( EventType.PRE_LOAD ); final Iterable listeners = listenerGroup.listeners(); - doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, listeners ); + initializeEntity( entity, readOnly, session, preLoadEvent, listeners, EntityResolver.DEFAULT ); } /** @@ -155,22 +150,46 @@ public final class TwoPhaseLoad { final SharedSessionContractImplementor session, final PreLoadEvent preLoadEvent, final Iterable preLoadEventListeners) { + initializeEntity( entity, readOnly, session, preLoadEvent, preLoadEventListeners, EntityResolver.DEFAULT ); + } + + /** + * Perform the second step of 2-phase load. Fully initialize the entity + * instance. + *

+ * After processing a JDBC result set, we "resolve" all the associations + * between the entities which were instantiated and had their state + * "hydrated" into an array + * + * @param entity The entity being loaded + * @param readOnly Is the entity being loaded as read-only + * @param session The Session + * @param preLoadEvent The (re-used) pre-load event + * @param preLoadEventListeners the pre-load event listeners + * @param entityResolver the resolver used for to-one entity associations + * (not used when an entity is a bytecode-enhanced lazy entity) + */ + public static void initializeEntity( + final Object entity, + final boolean readOnly, + final SharedSessionContractImplementor session, + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners, + final EntityResolver entityResolver) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final EntityEntry entityEntry = persistenceContext.getEntry( entity ); if ( entityEntry == null ) { throw new AssertionFailure( "possible non-threadsafe access to the session" ); } - doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, preLoadEventListeners ); + initializeEntityEntryLoadedState( entity, entityEntry, session, entityResolver ); + initializeEntityFromEntityEntryLoadedState( entity, entityEntry, readOnly, session, preLoadEvent, preLoadEventListeners ); } - private static void doInitializeEntity( + public static void initializeEntityEntryLoadedState( final Object entity, final EntityEntry entityEntry, - final boolean readOnly, final SharedSessionContractImplementor session, - final PreLoadEvent preLoadEvent, - final Iterable preLoadEventListeners) throws HibernateException { - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityResolver entityResolver) throws HibernateException { final EntityPersister persister = entityEntry.getPersister(); final Serializable id = entityEntry.getId(); final Object[] hydratedState = entityEntry.getLoadedState(); @@ -229,18 +248,36 @@ public final class TwoPhaseLoad { // we know value != LazyPropertyInitializer.UNFETCHED_PROPERTY Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled ); - hydratedState[i] = types[i].resolve( value, session, entity, overridingEager ); + hydratedState[i] = types[i].isEntityType() + ? entityResolver.resolve( (EntityType) types[i], value, session, entity, overridingEager ) + : types[i].resolve( value, session, entity, overridingEager ); } else { if ( debugEnabled ) { LOG.debugf( "Skipping attribute : `%s`", propertyNames[i] ); } } - + if ( session.getFetchGraphLoadContext() != fetchGraphContext ) { session.setFetchGraphLoadContext( fetchGraphContext ); } } + } + + public static void initializeEntityFromEntityEntryLoadedState( + final Object entity, + final EntityEntry entityEntry, + final boolean readOnly, + final SharedSessionContractImplementor session, + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners) throws HibernateException { + + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityPersister persister = entityEntry.getPersister(); + final Serializable id = entityEntry.getId(); + final Object[] hydratedState = entityEntry.getLoadedState(); + + final boolean debugEnabled = LOG.isDebugEnabled(); //Must occur after resolving identifiers! if ( session.isEventSource() ) { @@ -600,4 +637,23 @@ public final class TwoPhaseLoad { false ); } + + /** + * Implementations determine how a to-one associations is resolved. + * + * @see #initializeEntity(Object, boolean, SharedSessionContractImplementor, PreLoadEvent, Iterable, EntityResolver) + */ + public interface EntityResolver { + + Object resolve( + EntityType entityType, + Object value, + SharedSessionContractImplementor session, + Object owner, + Boolean overridingEager + ); + + EntityResolver DEFAULT = (entityType, value, session, owner, overridingEager) -> + entityType.resolve( value, session, owner, overridingEager ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index 92f71795e4..d3070fe0a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -986,12 +986,48 @@ public abstract class Loader { int maxRows, List afterLoadActions) throws SQLException { final int entitySpan = getEntityPersisters().length; + final boolean createSubselects = isSubselectLoadingEnabled(); + final List subselectResultKeys = createSubselects ? new ArrayList<>() : null; + final List hydratedObjects = entitySpan == 0 ? null : new ArrayList<>( entitySpan * 10 ); + + final List results = getRowsFromResultSet( + rs, + queryParameters, + session, + returnProxies, + forcedResultTransformer, + maxRows, + hydratedObjects, + subselectResultKeys + ); + + initializeEntitiesAndCollections( + hydratedObjects, + rs, + session, + queryParameters.isReadOnly( session ), + afterLoadActions + ); + if ( createSubselects ) { + createSubselects( subselectResultKeys, queryParameters, session ); + } + return results; + } + + protected List getRowsFromResultSet( + ResultSet rs, + QueryParameters queryParameters, + SharedSessionContractImplementor session, + boolean returnProxies, + ResultTransformer forcedResultTransformer, + int maxRows, + List hydratedObjects, + List subselectResultKeys) throws SQLException { + final int entitySpan = getEntityPersisters().length; + final boolean createSubselects = isSubselectLoadingEnabled(); final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session ); final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() ); - final boolean createSubselects = isSubselectLoadingEnabled(); - final List subselectResultKeys = createSubselects ? new ArrayList() : null; - final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 ); - final List results = new ArrayList(); + final List results = new ArrayList<>(); handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session ); EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row @@ -1023,16 +1059,6 @@ public abstract class Loader { LOG.tracev( "Done processing result set ({0} rows)", count ); - initializeEntitiesAndCollections( - hydratedObjects, - rs, - session, - queryParameters.isReadOnly( session ), - afterLoadActions - ); - if ( createSubselects ) { - createSubselects( subselectResultKeys, queryParameters, session ); - } return results; } @@ -1061,7 +1087,7 @@ public abstract class Loader { return result; } - private void createSubselects(List keys, QueryParameters queryParameters, SharedSessionContractImplementor session) { + protected void createSubselects(List keys, QueryParameters queryParameters, SharedSessionContractImplementor session) { if ( keys.size() > 1 ) { //if we only returned one entity, query by key is more efficient Set[] keySets = transpose( keys ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinLoader.java index e10270b8ff..b1e3e54537 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinLoader.java @@ -62,7 +62,7 @@ public abstract class OuterJoinLoader extends BasicLoader { return sql; } - protected final Loadable[] getEntityPersisters() { + public final Loadable[] getEntityPersisters() { return persisters; } @@ -90,7 +90,7 @@ public abstract class OuterJoinLoader extends BasicLoader { return aliases; } - protected final CollectionPersister[] getCollectionPersisters() { + public final CollectionPersister[] getCollectionPersisters() { return collectionPersisters; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 099e3b348d..7831ecca2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -31,6 +31,7 @@ import org.hibernate.loader.plan.build.spi.MetamodelDrivenLoadPlanBuilder; import org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader; import org.hibernate.loader.plan.exec.internal.BatchingLoadQueryDetailsFactory; import org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessorResolver; import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; import org.hibernate.loader.plan.spi.LoadPlan; @@ -58,7 +59,8 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan SessionFactoryImplementor factory, String[] uniqueKeyColumnNames, Type uniqueKeyType, - QueryBuildingParameters buildingParameters) { + QueryBuildingParameters buildingParameters, + ResultSetProcessorResolver resultSetProcessorResolver) { super( factory ); this.entityPersister = entityPersister; this.uniqueKeyType = uniqueKeyType; @@ -96,7 +98,42 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan plan, uniqueKeyColumnNames, buildingParameters, - factory + factory, + resultSetProcessorResolver + ); + } + + public AbstractLoadPlanBasedEntityLoader( + OuterJoinLoadable entityPersister, + SessionFactoryImplementor factory, + String[] uniqueKeyColumnNames, + Type uniqueKeyType, + QueryBuildingParameters buildingParameters) { + this( + entityPersister, + factory, + uniqueKeyColumnNames, + uniqueKeyType, + buildingParameters,ResultSetProcessorResolver.DEFAULT + ); + } + + protected AbstractLoadPlanBasedEntityLoader( + OuterJoinLoadable entityPersister, + SessionFactoryImplementor factory, + EntityLoadQueryDetails entityLoaderQueryDetailsTemplate, + Type uniqueKeyType, + QueryBuildingParameters buildingParameters, + ResultSetProcessorResolver resultSetProcessorResolver) { + super( factory ); + this.entityPersister = entityPersister; + this.uniqueKeyType = uniqueKeyType; + this.entityName = entityPersister.getEntityName(); + + this.staticLoadQuery = BatchingLoadQueryDetailsFactory.INSTANCE.makeEntityLoadQueryDetails( + entityLoaderQueryDetailsTemplate, + buildingParameters, + resultSetProcessorResolver ); } @@ -106,14 +143,13 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan EntityLoadQueryDetails entityLoaderQueryDetailsTemplate, Type uniqueKeyType, QueryBuildingParameters buildingParameters) { - super( factory ); - this.entityPersister = entityPersister; - this.uniqueKeyType = uniqueKeyType; - this.entityName = entityPersister.getEntityName(); - - this.staticLoadQuery = BatchingLoadQueryDetailsFactory.INSTANCE.makeEntityLoadQueryDetails( + this( + entityPersister, + factory, entityLoaderQueryDetailsTemplate, - buildingParameters + uniqueKeyType, + buildingParameters, + ResultSetProcessorResolver.DEFAULT ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java index ee0e648208..2fef82a0f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java @@ -17,6 +17,7 @@ import org.hibernate.loader.plan.exec.process.spi.CollectionReferenceInitializer import org.hibernate.loader.plan.exec.process.spi.EntityReferenceInitializer; import org.hibernate.loader.plan.exec.process.spi.ReaderCollector; import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessor; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessorResolver; import org.hibernate.loader.plan.exec.query.internal.SelectStatementBuilder; import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; @@ -104,6 +105,10 @@ public abstract class AbstractLoadQueryDetails implements LoadQueryDetails { * */ protected void generate() { + generate( ResultSetProcessorResolver.DEFAULT ); + } + + protected void generate(ResultSetProcessorResolver resultSetProcessorResolver) { // There are 2 high-level requirements to perform here: // 1) Determine the SQL required to carry out the given LoadPlan (and fulfill // {@code LoadQueryDetails#getSqlStatement()}). SelectStatementBuilder collects the ongoing efforts to @@ -190,10 +195,10 @@ public abstract class AbstractLoadQueryDetails implements LoadQueryDetails { LoadPlanTreePrinter.INSTANCE.logTree( loadPlan, queryProcessor.getAliasResolutionContext() ); this.sqlStatement = select.toStatementString(); - this.resultSetProcessor = new ResultSetProcessorImpl( + this.resultSetProcessor = resultSetProcessorResolver.resolveResultSetProcessor( loadPlan, queryProcessor.getAliasResolutionContext(), - getReaderCollector().buildRowReader(), + getReaderCollector(), shouldUseOptionalEntityInstance(), isSubselectLoadingEnabled( fetchStats ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java index a03cf24e92..d500a816fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java @@ -7,6 +7,7 @@ package org.hibernate.loader.plan.exec.internal; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessorResolver; import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; import org.hibernate.loader.plan.spi.CollectionReturn; @@ -38,6 +39,7 @@ public class BatchingLoadQueryDetailsFactory { * @param buildingParameters Any influencers that would affect the generated SQL (mostly we are concerned with those * that add additional joins here) * @param factory The SessionFactory + * @oaram resultSetProcessorResolver The ResultSet processor resolver. * * @return The EntityLoadQueryDetails */ @@ -45,7 +47,8 @@ public class BatchingLoadQueryDetailsFactory { LoadPlan loadPlan, String[] keyColumnNames, QueryBuildingParameters buildingParameters, - SessionFactoryImplementor factory) { + SessionFactoryImplementor factory, + ResultSetProcessorResolver resultSetProcessorResolver) { // TODO: how should shouldUseOptionalEntityInformation be used? // final int batchSize = buildingParameters.getBatchSize(); @@ -64,7 +67,54 @@ public class BatchingLoadQueryDetailsFactory { aliasResolutionContext, rootReturn, buildingParameters, - factory + factory, + resultSetProcessorResolver + ); + } + /** + * Returns an EntityLoadQueryDetails object from the given inputs. + * + * @param loadPlan The load plan + * @param keyColumnNames The columns to load the entity by (the PK columns or some other unique set of columns) + * @param buildingParameters Any influencers that would affect the generated SQL (mostly we are concerned with those + * that add additional joins here) + * @param factory The SessionFactory + * + * @return The EntityLoadQueryDetails + */ + public EntityLoadQueryDetails makeEntityLoadQueryDetails( + LoadPlan loadPlan, + String[] keyColumnNames, + QueryBuildingParameters buildingParameters, + SessionFactoryImplementor factory) { + + return makeEntityLoadQueryDetails( + loadPlan, + keyColumnNames, + buildingParameters, + factory, + ResultSetProcessorResolver.DEFAULT + ); + } + + /** + * Returns a EntityLoadQueryDetails object based on an existing one and additional elements specific to this one. + * + * @param entityLoadQueryDetailsTemplate the template + * @param buildingParameters Any influencers that would affect the generated SQL (mostly we are concerned with those + * that add additional joins here) + * @oaram resultSetProcessorResolver The ResultSet processor resolver. + * + * @return The EntityLoadQueryDetails + */ + public EntityLoadQueryDetails makeEntityLoadQueryDetails( + EntityLoadQueryDetails entityLoadQueryDetailsTemplate, + QueryBuildingParameters buildingParameters, + ResultSetProcessorResolver resultSetProcessorResolver) { + return new EntityLoadQueryDetails( + entityLoadQueryDetailsTemplate, + buildingParameters, + resultSetProcessorResolver ); } @@ -79,9 +129,10 @@ public class BatchingLoadQueryDetailsFactory { public EntityLoadQueryDetails makeEntityLoadQueryDetails( EntityLoadQueryDetails entityLoadQueryDetailsTemplate, QueryBuildingParameters buildingParameters) { - return new EntityLoadQueryDetails( + return makeEntityLoadQueryDetails( entityLoadQueryDetailsTemplate, - buildingParameters + buildingParameters, + ResultSetProcessorResolver.DEFAULT ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java index 4b922df9cf..064a33fbc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java @@ -22,6 +22,7 @@ import org.hibernate.loader.plan.exec.process.internal.ResultSetProcessingContex import org.hibernate.loader.plan.exec.process.spi.EntityReferenceInitializer; import org.hibernate.loader.plan.exec.process.spi.ReaderCollector; import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessorResolver; import org.hibernate.loader.plan.exec.process.spi.RowReader; import org.hibernate.loader.plan.exec.query.internal.SelectStatementBuilder; import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; @@ -58,6 +59,7 @@ public class EntityLoadQueryDetails extends AbstractLoadQueryDetails { * @param buildingParameters Any influencers that would affect the generated SQL (mostly we are concerned with those * that add additional joins here) * @param factory The SessionFactory + * @param resultSetProcessorResolver The ResultSet resolver. */ protected EntityLoadQueryDetails( LoadPlan loadPlan, @@ -65,7 +67,9 @@ public class EntityLoadQueryDetails extends AbstractLoadQueryDetails { AliasResolutionContextImpl aliasResolutionContext, EntityReturn rootReturn, QueryBuildingParameters buildingParameters, - SessionFactoryImplementor factory) { + SessionFactoryImplementor factory, + ResultSetProcessorResolver resultSetProcessorResolver) { + super( loadPlan, aliasResolutionContext, @@ -82,21 +86,55 @@ public class EntityLoadQueryDetails extends AbstractLoadQueryDetails { new EntityReturnReader( rootReturn ), new EntityReferenceInitializerImpl( rootReturn, entityReferenceAliases, true ) ); - generate(); + generate( resultSetProcessorResolver ); + } + + /** + * Constructs a EntityLoadQueryDetails object from the given inputs. + * + * @param loadPlan The load plan + * @param keyColumnNames The columns to load the entity by (the PK columns or some other unique set of columns) + * @param buildingParameters Any influencers that would affect the generated SQL (mostly we are concerned with those + * that add additional joins here) + * @param factory The SessionFactory + */ + protected EntityLoadQueryDetails( + LoadPlan loadPlan, + String[] keyColumnNames, + AliasResolutionContextImpl aliasResolutionContext, + EntityReturn rootReturn, + QueryBuildingParameters buildingParameters, + SessionFactoryImplementor factory) { + this( + loadPlan, + keyColumnNames, + aliasResolutionContext, + rootReturn, + buildingParameters, + factory, + ResultSetProcessorResolver.DEFAULT + ); } protected EntityLoadQueryDetails( EntityLoadQueryDetails initialEntityLoadQueryDetails, - QueryBuildingParameters buildingParameters) { + QueryBuildingParameters buildingParameters, + ResultSetProcessorResolver resultSetProcessorResolver) { this( initialEntityLoadQueryDetails.getLoadPlan(), initialEntityLoadQueryDetails.getKeyColumnNames(), new AliasResolutionContextImpl( initialEntityLoadQueryDetails.getSessionFactory() ), (EntityReturn) initialEntityLoadQueryDetails.getRootReturn(), buildingParameters, - initialEntityLoadQueryDetails.getSessionFactory() + initialEntityLoadQueryDetails.getSessionFactory(), + resultSetProcessorResolver ); } + protected EntityLoadQueryDetails( + EntityLoadQueryDetails initialEntityLoadQueryDetails, + QueryBuildingParameters buildingParameters) { + this( initialEntityLoadQueryDetails, buildingParameters, ResultSetProcessorResolver.DEFAULT ); + } public boolean hasCollectionInitializers() { return CollectionHelper.isNotEmpty( readerCollector.getArrayReferenceInitializers() ) || diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java index 12cedc2ed5..9d99927202 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java @@ -224,7 +224,7 @@ public abstract class AbstractRowReader implements RowReader { postLoad( postLoadEvent, context, hydratedEntityRegistrations, afterLoadActionList ); } - private void finishLoadingArrays(ResultSetProcessingContextImpl context) { + protected void finishLoadingArrays(ResultSetProcessingContextImpl context) { for ( CollectionReferenceInitializer arrayReferenceInitializer : arrayReferenceInitializers ) { arrayReferenceInitializer.endLoading( context ); } @@ -262,13 +262,13 @@ public abstract class AbstractRowReader implements RowReader { } } - private void finishLoadingCollections(ResultSetProcessingContextImpl context) { + protected void finishLoadingCollections(ResultSetProcessingContextImpl context) { for ( CollectionReferenceInitializer collectionReferenceInitializer : collectionReferenceInitializers ) { collectionReferenceInitializer.endLoading( context ); } } - private void afterInitialize(ResultSetProcessingContextImpl context, + protected void afterInitialize(ResultSetProcessingContextImpl context, List hydratedEntityRegistrations) { if ( hydratedEntityRegistrations == null ) { return; @@ -279,7 +279,7 @@ public abstract class AbstractRowReader implements RowReader { } } - private void postLoad( + protected void postLoad( PostLoadEvent postLoadEvent, ResultSetProcessingContextImpl context, List hydratedEntityRegistrations, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java index 57bc45bc69..362e187406 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java @@ -315,10 +315,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex return hydratedEntityRegistrationList; } - /** - * Package-protected - */ - void wrapUp() { + public void wrapUp() { createSubselects(); if ( hydratedEntityRegistrationList != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java index 7420d0b4d7..c959d17b38 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java @@ -80,11 +80,57 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { ResultTransformer forcedResultTransformer, List afterLoadActionList) throws SQLException { - handlePotentiallyEmptyCollectionRootReturns( loadPlan, queryParameters.getCollectionKeys(), resultSet, session ); + handlePotentiallyEmptyCollectionRootReturns( queryParameters.getCollectionKeys(), resultSet, session ); + + final ResultSetProcessingContextImpl context = createResultSetProcessingContext( + resultSet, + session, + queryParameters, + namedParameterContext, + returnProxies, + readOnly + ); + + final List loadResults = extractRows( resultSet, queryParameters, context ); + + rowReader.finishUp( context, afterLoadActionList ); + context.wrapUp(); + + session.getPersistenceContextInternal().initializeNonLazyCollections(); + + return loadResults; + } + + protected ResultSetProcessingContextImpl createResultSetProcessingContext( + ResultSet resultSet, + final SharedSessionContractImplementor session, + QueryParameters queryParameters, + NamedParameterContext namedParameterContext, + boolean returnProxies, + boolean readOnly) { + return new ResultSetProcessingContextImpl( + resultSet, + session, + loadPlan, + aliasResolutionContext, + readOnly, + shouldUseOptionalEntityInstance, + returnProxies, + queryParameters, + namedParameterContext, + hadSubselectFetches + ); + } + + protected List extractRows( + ResultSet resultSet, + QueryParameters queryParameters, + final ResultSetProcessingContextImpl context) throws SQLException { final boolean traceEnabled = LOG.isTraceEnabled(); final int maxRows; - final List loadResults; + final List loadResults; + final RowSelection selection = queryParameters.getRowSelection(); if ( LimitHelper.hasMaxRows( selection ) ) { maxRows = selection.getMaxRows(); @@ -99,19 +145,6 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { maxRows = Integer.MAX_VALUE; } - final ResultSetProcessingContextImpl context = new ResultSetProcessingContextImpl( - resultSet, - session, - loadPlan, - aliasResolutionContext, - readOnly, - shouldUseOptionalEntityInstance, - returnProxies, - queryParameters, - namedParameterContext, - hadSubselectFetches - ); - if ( traceEnabled ) { LOG.trace( "Processing result set" ); } @@ -134,17 +167,10 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { LOG.tracev( "Done processing result set ({0} rows)", count ); } - rowReader.finishUp( context, afterLoadActionList ); - context.wrapUp(); - - session.getPersistenceContextInternal().initializeNonLazyCollections(); - return loadResults; } - - private void handlePotentiallyEmptyCollectionRootReturns( - LoadPlan loadPlan, + protected void handlePotentiallyEmptyCollectionRootReturns( Serializable[] collectionKeys, ResultSet resultSet, SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessorResolver.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessorResolver.java new file mode 100644 index 0000000000..a908d916ac --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessorResolver.java @@ -0,0 +1,32 @@ +/* + * 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.plan.exec.process.spi; + +import org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.spi.LoadPlan; + +public interface ResultSetProcessorResolver { + + ResultSetProcessor resolveResultSetProcessor( + LoadPlan loadPlan, + AliasResolutionContext aliasResolutionContext, + ReaderCollector readerCollector, + boolean shouldUseOptionalEntityInstance, + boolean hadSubselectFetches + ); + + ResultSetProcessorResolver DEFAULT = + (loadPlan, aliasResolutionContext, readerCollector, shouldUseOptionalEntityInstance, hadSubselectFetches) -> + new ResultSetProcessorImpl( + loadPlan, + aliasResolutionContext, + readerCollector.buildRowReader(), + shouldUseOptionalEntityInstance, + hadSubselectFetches + ); +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index b553962778..11ca22ff7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -471,6 +471,18 @@ public abstract class EntityType extends AbstractType implements AssociationType return null; } + /** + * Would an entity be eagerly loaded given the value provided for {@code overridingEager}? + * + * @param overridingEager can override eager from the mapping. + * + * @return If {@code overridingEager} is null, then it does not override. + * If true or false then it overrides the mapping value. + */ + public boolean isEager(Boolean overridingEager) { + return overridingEager != null ? overridingEager : this.eager; + } + @Override public Type getSemiResolvedType(SessionFactoryImplementor factory) { return getAssociatedEntityPersister( factory ).getIdentifierType(); @@ -682,12 +694,10 @@ public abstract class EntityType extends AbstractType implements AssociationType getAssociatedEntityPersister( session.getFactory() ) .isInstrumented(); - boolean eager = overridingEager != null ? overridingEager : this.eager; - Object proxyOrEntity = session.internalLoad( getAssociatedEntityName(), id, - eager, + isEager( overridingEager ), isNullable() );