HHH-12558 - Lazy load EntityLoaders to improve memory usage
This commit is contained in:
parent
7943fe3fc2
commit
60f4645036
|
@ -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.
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -161,6 +161,8 @@ public interface SessionFactoryOptions {
|
|||
|
||||
BatchFetchStyle getBatchFetchStyle();
|
||||
|
||||
boolean isDelayBatchFetchLoaderCreationsEnabled();
|
||||
|
||||
int getDefaultBatchFetchSize();
|
||||
|
||||
Integer getMaximumFetchDepth();
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue