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;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode;
@ -27,9 +30,6 @@ import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.tuple.entity.EntityTuplizer;
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.
*
@ -303,6 +303,14 @@ public interface SessionFactoryBuilder {
*/
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
* which do not otherwise specify a batch-fetch size.

View File

@ -219,6 +219,12 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return this;
}
@Override
public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) {
this.optionsBuilder.applyDelayedEntityLoaderCreations( delay );
return this;
}
@Override
public SessionFactoryBuilder applyDefaultBatchFetchSize(int size) {
this.optionsBuilder.applyDefaultBatchFetchSize( size );

View File

@ -82,6 +82,7 @@ import static org.hibernate.cfg.AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE
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_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.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION;
@ -183,6 +184,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private MultiTableBulkIdStrategy multiTableBulkIdStrategy;
private TempTableDdlTransactionHandling tempTableDdlTransactionHandling;
private BatchFetchStyle batchFetchStyle;
private boolean delayBatchFetchLoaderCreations;
private int defaultBatchFetchSize;
private Integer maximumFetchDepth;
private NullPrecedence defaultNullPrecedence;
@ -324,6 +326,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
);
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.maximumFetchDepth = ConfigurationHelper.getInteger( MAX_FETCH_DEPTH, configurationSettings );
final String defaultNullPrecedence = ConfigurationHelper.getString(
@ -773,6 +776,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return batchFetchStyle;
}
@Override
public boolean isDelayBatchFetchLoaderCreationsEnabled() {
return delayBatchFetchLoaderCreations;
}
@Override
public int getDefaultBatchFetchSize() {
return defaultBatchFetchSize;
@ -1110,6 +1118,10 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
this.batchFetchStyle = style;
}
public void applyDelayedEntityLoaderCreations(boolean delay) {
this.delayBatchFetchLoaderCreations = delay;
}
public void applyDefaultBatchFetchSize(int size) {
this.defaultBatchFetchSize = size;
}
@ -1300,5 +1312,4 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return this;
}
}

View File

@ -6,6 +6,9 @@
*/
package org.hibernate.boot.spi;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode;
@ -28,9 +31,6 @@ import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.tuple.entity.EntityTuplizer;
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
*
@ -193,6 +193,12 @@ public abstract class AbstractDelegatingSessionFactoryBuilder<T extends SessionF
return getThis();
}
@Override
public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) {
delegate.applyDelayedEntityLoaderCreations( delay );
return getThis();
}
@Override
public T applyDefaultBatchFetchSize(int size) {
delegate.applyDefaultBatchFetchSize( size );

View File

@ -6,6 +6,10 @@
*/
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.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode;
@ -31,10 +35,6 @@ import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
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
*
@ -172,6 +172,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.getBatchFetchStyle();
}
@Override
public boolean isDelayBatchFetchLoaderCreationsEnabled() {
return delegate.isDelayBatchFetchLoaderCreationsEnabled();
}
@Override
public int getDefaultBatchFetchSize() {
return delegate.getDefaultBatchFetchSize();

View File

@ -161,6 +161,8 @@ public interface SessionFactoryOptions {
BatchFetchStyle getBatchFetchStyle();
boolean isDelayBatchFetchLoaderCreationsEnabled();
int getDefaultBatchFetchSize();
Integer getMaximumFetchDepth();

View File

@ -7,7 +7,6 @@
package org.hibernate.cfg;
import java.util.function.Supplier;
import javax.persistence.GeneratedValue;
import org.hibernate.HibernateException;
@ -1560,6 +1559,20 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/
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")
* -- 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
*/
String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding";
}

View File

@ -14,11 +14,13 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -4113,7 +4115,12 @@ public abstract class AbstractEntityPersister
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() {
return loaders;
}
@ -4125,9 +4132,17 @@ public abstract class AbstractEntityPersister
noneLockLoader = createEntityLoader( LockMode.NONE );
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(
"merge",
@ -4147,10 +4162,10 @@ public abstract class AbstractEntityPersister
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.
// The cast is safe as we will always call this method with a LockMode.
LockMode lockMode = (LockMode) lockModeObject;
@ -4159,23 +4174,26 @@ public abstract class AbstractEntityPersister
case NONE:
case READ:
case OPTIMISTIC:
case OPTIMISTIC_FORCE_INCREMENT:
case OPTIMISTIC_FORCE_INCREMENT: {
return createEntityLoader( lockMode );
}
case UPGRADE:
case UPGRADE_NOWAIT:
case UPGRADE_SKIPLOCKED:
case FORCE:
case PESSIMISTIC_READ:
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?
boolean disableForUpdate = getSubclassTableSpan() > 1 &&
hasSubclasses() &&
!getFactory().getDialect().supportsOuterJoinForUpdate();
boolean disableForUpdate = getSubclassTableSpan() > 1
&& hasSubclasses()
&& !getFactory().getDialect().supportsOuterJoinForUpdate();
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 @@ public abstract class AbstractEntityPersister
// Next, we consider whether an 'internal' fetch profile has been set.
// This indicates a special fetch profile Hibernate needs applied
// (for its merge loading process e.g.).
return (UniqueEntityLoader) getLoaders().get( session.getLoadQueryInfluencers().getInternalFetchProfile() );
return loaders.get( session.getLoadQueryInfluencers().getInternalFetchProfile() );
}
else if ( isAffectedByEnabledFetchProfiles( session ) ) {
// If the session has associated influencers we need to adjust the
@ -4272,7 +4290,7 @@ public abstract class AbstractEntityPersister
return createEntityLoader( lockOptions, session.getLoadQueryInfluencers() );
}
else {
return (UniqueEntityLoader) getLoaderByLockMode( lockOptions.getLockMode() );
return getLoaderByLockMode( lockOptions.getLockMode() );
}
}