HHH-12558 - Lazy load EntityLoaders to improve memory usage

This commit is contained in:
Steve Ebersole 2018-05-14 16:01:02 -05:00
parent 7943fe3fc2
commit 60f4645036
8 changed files with 96 additions and 26 deletions

View File

@ -6,6 +6,9 @@
*/ */
package org.hibernate.boot; package org.hibernate.boot;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.ConnectionReleaseMode; import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
@ -27,9 +30,6 @@
import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.tuple.entity.EntityTuplizerFactory; import org.hibernate.tuple.entity.EntityTuplizerFactory;
import java.util.Map;
import java.util.function.Supplier;
/** /**
* The contract for building a {@link org.hibernate.SessionFactory} given a number of options. * The contract for building a {@link org.hibernate.SessionFactory} given a number of options.
* *
@ -303,6 +303,14 @@ SessionFactoryBuilder applyEntityTuplizer(
*/ */
SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style); SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style);
/**
* Should entity Loaders be generated immediately? Or should the creation
* be delayed until first need?
*
* @see org.hibernate.cfg.AvailableSettings#DELAY_ENTITY_LOADER_CREATIONS
*/
SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay);
/** /**
* Allows specifying a default batch-fetch size for all entities and collections * Allows specifying a default batch-fetch size for all entities and collections
* which do not otherwise specify a batch-fetch size. * which do not otherwise specify a batch-fetch size.

View File

@ -219,6 +219,12 @@ public SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style) {
return this; return this;
} }
@Override
public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) {
this.optionsBuilder.applyDelayedEntityLoaderCreations( delay );
return this;
}
@Override @Override
public SessionFactoryBuilder applyDefaultBatchFetchSize(int size) { public SessionFactoryBuilder applyDefaultBatchFetchSize(int size) {
this.optionsBuilder.applyDefaultBatchFetchSize( size ); this.optionsBuilder.applyDefaultBatchFetchSize( size );

View File

@ -82,6 +82,7 @@
import static org.hibernate.cfg.AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY; import static org.hibernate.cfg.AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY;
import static org.hibernate.cfg.AvailableSettings.DEFAULT_BATCH_FETCH_SIZE; import static org.hibernate.cfg.AvailableSettings.DEFAULT_BATCH_FETCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.DEFAULT_ENTITY_MODE; import static org.hibernate.cfg.AvailableSettings.DEFAULT_ENTITY_MODE;
import static org.hibernate.cfg.AvailableSettings.DELAY_ENTITY_LOADER_CREATIONS;
import static org.hibernate.cfg.AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS; import static org.hibernate.cfg.AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS;
import static org.hibernate.cfg.AvailableSettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH; import static org.hibernate.cfg.AvailableSettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION; import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION;
@ -183,6 +184,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private MultiTableBulkIdStrategy multiTableBulkIdStrategy; private MultiTableBulkIdStrategy multiTableBulkIdStrategy;
private TempTableDdlTransactionHandling tempTableDdlTransactionHandling; private TempTableDdlTransactionHandling tempTableDdlTransactionHandling;
private BatchFetchStyle batchFetchStyle; private BatchFetchStyle batchFetchStyle;
private boolean delayBatchFetchLoaderCreations;
private int defaultBatchFetchSize; private int defaultBatchFetchSize;
private Integer maximumFetchDepth; private Integer maximumFetchDepth;
private NullPrecedence defaultNullPrecedence; private NullPrecedence defaultNullPrecedence;
@ -324,6 +326,7 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo
); );
this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) ); this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) );
this.delayBatchFetchLoaderCreations = cfgService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true );
this.defaultBatchFetchSize = ConfigurationHelper.getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 ); this.defaultBatchFetchSize = ConfigurationHelper.getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 );
this.maximumFetchDepth = ConfigurationHelper.getInteger( MAX_FETCH_DEPTH, configurationSettings ); this.maximumFetchDepth = ConfigurationHelper.getInteger( MAX_FETCH_DEPTH, configurationSettings );
final String defaultNullPrecedence = ConfigurationHelper.getString( final String defaultNullPrecedence = ConfigurationHelper.getString(
@ -773,6 +776,11 @@ public BatchFetchStyle getBatchFetchStyle() {
return batchFetchStyle; return batchFetchStyle;
} }
@Override
public boolean isDelayBatchFetchLoaderCreationsEnabled() {
return delayBatchFetchLoaderCreations;
}
@Override @Override
public int getDefaultBatchFetchSize() { public int getDefaultBatchFetchSize() {
return defaultBatchFetchSize; return defaultBatchFetchSize;
@ -1110,6 +1118,10 @@ public void applyBatchFetchStyle(BatchFetchStyle style) {
this.batchFetchStyle = style; this.batchFetchStyle = style;
} }
public void applyDelayedEntityLoaderCreations(boolean delay) {
this.delayBatchFetchLoaderCreations = delay;
}
public void applyDefaultBatchFetchSize(int size) { public void applyDefaultBatchFetchSize(int size) {
this.defaultBatchFetchSize = size; this.defaultBatchFetchSize = size;
} }
@ -1300,5 +1312,4 @@ public SessionFactoryOptions buildOptions() {
return this; return this;
} }
} }

View File

@ -6,6 +6,9 @@
*/ */
package org.hibernate.boot.spi; package org.hibernate.boot.spi;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.ConnectionReleaseMode; import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
@ -28,9 +31,6 @@
import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.tuple.entity.EntityTuplizerFactory; import org.hibernate.tuple.entity.EntityTuplizerFactory;
import java.util.Map;
import java.util.function.Supplier;
/** /**
* Convenience base class for custom implementors of SessionFactoryBuilder, using delegation * Convenience base class for custom implementors of SessionFactoryBuilder, using delegation
* *
@ -193,6 +193,12 @@ public T applyBatchFetchStyle(BatchFetchStyle style) {
return getThis(); return getThis();
} }
@Override
public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) {
delegate.applyDelayedEntityLoaderCreations( delay );
return getThis();
}
@Override @Override
public T applyDefaultBatchFetchSize(int size) { public T applyDefaultBatchFetchSize(int size) {
delegate.applyDefaultBatchFetchSize( size ); delegate.applyDefaultBatchFetchSize( size );

View File

@ -6,6 +6,10 @@
*/ */
package org.hibernate.boot.spi; package org.hibernate.boot.spi;
import java.util.Map;
import java.util.TimeZone;
import java.util.function.Supplier;
import org.hibernate.ConnectionReleaseMode; import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
@ -31,10 +35,6 @@
import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.tuple.entity.EntityTuplizerFactory; import org.hibernate.tuple.entity.EntityTuplizerFactory;
import java.util.Map;
import java.util.TimeZone;
import java.util.function.Supplier;
/** /**
* Convenience base class for custom implementors of SessionFactoryOptions, using delegation * Convenience base class for custom implementors of SessionFactoryOptions, using delegation
* *
@ -172,6 +172,11 @@ public BatchFetchStyle getBatchFetchStyle() {
return delegate.getBatchFetchStyle(); return delegate.getBatchFetchStyle();
} }
@Override
public boolean isDelayBatchFetchLoaderCreationsEnabled() {
return delegate.isDelayBatchFetchLoaderCreationsEnabled();
}
@Override @Override
public int getDefaultBatchFetchSize() { public int getDefaultBatchFetchSize() {
return delegate.getDefaultBatchFetchSize(); return delegate.getDefaultBatchFetchSize();

View File

@ -161,6 +161,8 @@ default Supplier<? extends Interceptor> getStatelessInterceptorImplementorSuppli
BatchFetchStyle getBatchFetchStyle(); BatchFetchStyle getBatchFetchStyle();
boolean isDelayBatchFetchLoaderCreationsEnabled();
int getDefaultBatchFetchSize(); int getDefaultBatchFetchSize();
Integer getMaximumFetchDepth(); Integer getMaximumFetchDepth();

View File

@ -7,7 +7,6 @@
package org.hibernate.cfg; package org.hibernate.cfg;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -1560,6 +1559,20 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/ */
String BATCH_FETCH_STYLE = "hibernate.batch_fetch_style"; String BATCH_FETCH_STYLE = "hibernate.batch_fetch_style";
/**
* Controls how the individual Loaders for an entity are created.
*
* When `true` (the default), only the minimal set of Loaders are
* created. These include the handling for {@link org.hibernate.LockMode#READ}
* and {@link org.hibernate.LockMode#NONE} as well as specialized Loaders for
* merge and refresh handling.
*
* `false` indicates that all loaders should be created up front
*
* @since 5.3
*/
String DELAY_ENTITY_LOADER_CREATIONS = "hibernate.loader.delay_entity_loader_creations";
/** /**
* A transaction can be rolled back by another thread ("tracking by thread") * A transaction can be rolled back by another thread ("tracking by thread")
* -- not the original application. Examples of this include a JTA * -- not the original application. Examples of this include a JTA
@ -1922,4 +1935,5 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
* @since 5.2.17 * @since 5.2.17
*/ */
String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding"; String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding";
} }

View File

@ -14,11 +14,13 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -4113,7 +4115,12 @@ public final void postInstantiate() throws MappingException {
protected void doPostInstantiate() { protected void doPostInstantiate() {
} }
//needed by subclasses to override the createLoader strategy /**
* "Needed" by subclasses to override the createLoader strategy
*
* @deprecated Because there are better patterns for this
*/
@Deprecated
protected Map getLoaders() { protected Map getLoaders() {
return loaders; return loaders;
} }
@ -4125,9 +4132,17 @@ protected void createLoaders() {
noneLockLoader = createEntityLoader( LockMode.NONE ); noneLockLoader = createEntityLoader( LockMode.NONE );
readLockLoader = createEntityLoader( LockMode.READ ); readLockLoader = createEntityLoader( LockMode.READ );
final Map loaders = getLoaders();
// The loaders for the other lock modes are lazily loaded and will later be stored in this map. // The loaders for the other lock modes are lazily loaded and will later be stored in this map,
// unless this setting is disabled
if ( ! factory.getSessionFactoryOptions().isDelayBatchFetchLoaderCreationsEnabled() ) {
for ( LockMode lockMode : EnumSet.complementOf( EnumSet.of( LockMode.NONE, LockMode.READ ) ) ) {
loaders.put( lockMode, createEntityLoader( lockMode ) );
}
}
// And finally, create the internal merge and refresh load plans
loaders.put( loaders.put(
"merge", "merge",
@ -4147,10 +4162,10 @@ else if ( LockMode.READ == lockMode ) {
return readLockLoader; return readLockLoader;
} }
return loaders.computeIfAbsent( lockMode, this::createLazyLoadedEntityLoader ); return loaders.computeIfAbsent( lockMode, this::generateDelayedEntityLoader );
} }
private UniqueEntityLoader createLazyLoadedEntityLoader(Object lockModeObject) { private UniqueEntityLoader generateDelayedEntityLoader(Object lockModeObject) {
// Unfortunately, the loaders map mixes LockModes and Strings as keys so we need to accept an Object. // Unfortunately, the loaders map mixes LockModes and Strings as keys so we need to accept an Object.
// The cast is safe as we will always call this method with a LockMode. // The cast is safe as we will always call this method with a LockMode.
LockMode lockMode = (LockMode) lockModeObject; LockMode lockMode = (LockMode) lockModeObject;
@ -4159,23 +4174,26 @@ private UniqueEntityLoader createLazyLoadedEntityLoader(Object lockModeObject) {
case NONE: case NONE:
case READ: case READ:
case OPTIMISTIC: case OPTIMISTIC:
case OPTIMISTIC_FORCE_INCREMENT: case OPTIMISTIC_FORCE_INCREMENT: {
return createEntityLoader( lockMode ); return createEntityLoader( lockMode );
}
case UPGRADE: case UPGRADE:
case UPGRADE_NOWAIT: case UPGRADE_NOWAIT:
case UPGRADE_SKIPLOCKED: case UPGRADE_SKIPLOCKED:
case FORCE: case FORCE:
case PESSIMISTIC_READ: case PESSIMISTIC_READ:
case PESSIMISTIC_WRITE: case PESSIMISTIC_WRITE:
case PESSIMISTIC_FORCE_INCREMENT: case PESSIMISTIC_FORCE_INCREMENT: {
//TODO: inexact, what we really need to know is: are any outer joins used? //TODO: inexact, what we really need to know is: are any outer joins used?
boolean disableForUpdate = getSubclassTableSpan() > 1 && boolean disableForUpdate = getSubclassTableSpan() > 1
hasSubclasses() && && hasSubclasses()
!getFactory().getDialect().supportsOuterJoinForUpdate(); && !getFactory().getDialect().supportsOuterJoinForUpdate();
return disableForUpdate ? readLockLoader : createEntityLoader( lockMode ); return disableForUpdate ? readLockLoader : createEntityLoader( lockMode );
default: }
throw new IllegalStateException( String.format( "Lock mode %1$s not supported by entity loaders.", lockMode ) ); default: {
throw new IllegalStateException( String.format( Locale.ROOT, "Lock mode %1$s not supported by entity loaders.", lockMode ) );
}
} }
} }
@ -4258,7 +4276,7 @@ else if ( session.getLoadQueryInfluencers().getInternalFetchProfile() != null &&
// Next, we consider whether an 'internal' fetch profile has been set. // Next, we consider whether an 'internal' fetch profile has been set.
// This indicates a special fetch profile Hibernate needs applied // This indicates a special fetch profile Hibernate needs applied
// (for its merge loading process e.g.). // (for its merge loading process e.g.).
return (UniqueEntityLoader) getLoaders().get( session.getLoadQueryInfluencers().getInternalFetchProfile() ); return loaders.get( session.getLoadQueryInfluencers().getInternalFetchProfile() );
} }
else if ( isAffectedByEnabledFetchProfiles( session ) ) { else if ( isAffectedByEnabledFetchProfiles( session ) ) {
// If the session has associated influencers we need to adjust the // If the session has associated influencers we need to adjust the
@ -4272,7 +4290,7 @@ else if ( lockOptions.getTimeOut() != LockOptions.WAIT_FOREVER ) {
return createEntityLoader( lockOptions, session.getLoadQueryInfluencers() ); return createEntityLoader( lockOptions, session.getLoadQueryInfluencers() );
} }
else { else {
return (UniqueEntityLoader) getLoaderByLockMode( lockOptions.getLockMode() ); return getLoaderByLockMode( lockOptions.getLockMode() );
} }
} }