HHH-14271 Lazy initialization of UniqueEntityLoader for most LockMode types

This commit is contained in:
Sanne Grinovero 2020-10-21 14:28:26 +01:00
parent c60765a528
commit 81d526e4db
3 changed files with 94 additions and 56 deletions

View File

@ -1665,12 +1665,17 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
/** /**
* Controls how the individual Loaders for an entity are created. * Controls how the individual Loaders for an entity are created.
* *
* When `true` (the default), only the minimal set of Loaders are * When `true` (the default), the loaders are only created on first
* created. These include the handling for {@link org.hibernate.LockMode#READ} * access; this ensures that all access patterns which are not useful
* and {@link org.hibernate.LockMode#NONE} as well as specialized Loaders for * to the application are never instantiated, possibly saving a
* merge and refresh handling. * substantial amount of memory for applications having many entities.
* The only exception is the loader for <code>LockMode.NONE</code>,
* which will always be eagerly initialized; this is necessary to
* detect mapping errors.
* *
* `false` indicates that all loaders should be created up front * `false` indicates that all loaders should be created up front; this
* will consume more memory but ensures all necessary memory is
* allocated right away.
* *
* @since 5.3 * @since 5.3
*/ */

View File

@ -14,7 +14,6 @@ import java.util.ArrayList;
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;
@ -23,7 +22,6 @@ import java.util.List;
import java.util.Locale; 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 org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
@ -69,6 +67,7 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
@ -254,9 +253,7 @@ public abstract class AbstractEntityPersister
private final LockModeEnumMap<LockingStrategy> lockers = new LockModeEnumMap<>(); private final LockModeEnumMap<LockingStrategy> lockers = new LockModeEnumMap<>();
private UniqueEntityLoader noneLockLoader; private final EntityLoaderLazyCollection loaders = new EntityLoaderLazyCollection();
private UniqueEntityLoader readLockLoader;
private final Map<Object, UniqueEntityLoader> loaders = new ConcurrentHashMap<>();
// SQL strings // SQL strings
private String sqlVersionSelectString; private String sqlVersionSelectString;
@ -4293,61 +4290,38 @@ public abstract class AbstractEntityPersister
protected void doPostInstantiate() { protected void doPostInstantiate() {
} }
/**
* "Needed" by subclasses to override the createLoader strategy
*
* @deprecated Because there are better patterns for this
*/
@Deprecated
protected Map getLoaders() {
return loaders;
}
//Relational based Persisters should be content with this implementation //Relational based Persisters should be content with this implementation
protected void createLoaders() { protected void createLoaders() {
// We load the entity loaders for the most common lock modes. // The loaders for each lock mode are lazily loaded, unless this setting is disabled
noneLockLoader = createEntityLoader( LockMode.NONE );
readLockLoader = createEntityLoader( LockMode.READ );
// 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() ) { if ( ! factory.getSessionFactoryOptions().isDelayBatchFetchLoaderCreationsEnabled() ) {
for ( LockMode lockMode : EnumSet.complementOf( EnumSet.of( LockMode.NONE, LockMode.READ, LockMode.WRITE ) ) ) { for ( LockMode lockMode : LockMode.values() ) {
loaders.put( lockMode, createEntityLoader( lockMode ) ); //Trigger eager initialization
loaders.getOrBuildByLockMode( lockMode, this::createEntityLoader );
}
//Also, we have two special internal fetch profiles to eagerly initialize in this case:
loaders.getOrCreateByInternalFetchProfileMerge( this::buildMergeCascadeEntityLoader );
loaders.getOrCreateByInternalFetchProfileRefresh( this::buildRefreshCascadeEntityLoader );
}
else {
//At least initialize this one: it's almost certain to be used,
//and also will allow to report mapping errors during initialization.
loaders.getOrBuildByLockMode( LockMode.NONE, this::createEntityLoader );
} }
} }
private UniqueEntityLoader buildMergeCascadeEntityLoader(LockMode ignored) {
return new CascadeEntityLoader( this, CascadingActions.MERGE, getFactory() );
}
// And finally, create the internal merge and refresh load plans private UniqueEntityLoader buildRefreshCascadeEntityLoader(LockMode ignored) {
return new CascadeEntityLoader( this, CascadingActions.REFRESH, getFactory() );
loaders.put(
"merge",
new CascadeEntityLoader( this, CascadingActions.MERGE, getFactory() )
);
loaders.put(
"refresh",
new CascadeEntityLoader( this, CascadingActions.REFRESH, getFactory() )
);
} }
protected final UniqueEntityLoader getLoaderByLockMode(LockMode lockMode) { protected final UniqueEntityLoader getLoaderByLockMode(LockMode lockMode) {
if ( LockMode.NONE == lockMode ) { return loaders.getOrBuildByLockMode( lockMode, this::createEntityLoader );
return noneLockLoader;
}
else if ( LockMode.READ == lockMode ) {
return readLockLoader;
} }
return loaders.computeIfAbsent( lockMode, this::generateDelayedEntityLoader ); private UniqueEntityLoader generateDelayedEntityLoader(final LockMode lockMode) {
}
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;
switch ( lockMode ) { switch ( lockMode ) {
case NONE: case NONE:
case READ: case READ:
@ -4367,7 +4341,7 @@ public abstract class AbstractEntityPersister
&& hasSubclasses() && hasSubclasses()
&& !getFactory().getDialect().supportsOuterJoinForUpdate(); && !getFactory().getDialect().supportsOuterJoinForUpdate();
return disableForUpdate ? readLockLoader : createEntityLoader( lockMode ); return disableForUpdate ? getLoaderByLockMode( LockMode.READ ) : createEntityLoader( lockMode );
} }
default: { default: {
throw new IllegalStateException( String.format( Locale.ROOT, "Lock mode %1$s not supported by entity loaders.", lockMode ) ); throw new IllegalStateException( String.format( Locale.ROOT, "Lock mode %1$s not supported by entity loaders.", lockMode ) );
@ -4433,7 +4407,7 @@ public abstract class AbstractEntityPersister
loaded = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( loadEvent, this, entityKey ); loaded = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( loadEvent, this, entityKey );
} }
if ( loaded == null ) { if ( loaded == null ) {
loaded = readLockLoader.load( loaded = getLoaderByLockMode( LockMode.READ ).load(
identifier, identifier,
entity, entity,
session, session,
@ -4527,7 +4501,8 @@ public abstract class AbstractEntityPersister
// 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 loaders.get( session.getLoadQueryInfluencers().getInternalFetchProfile() ); final String internalFetchProfile = session.getLoadQueryInfluencers().getInternalFetchProfile();
return getLoaderByString( internalFetchProfile );
} }
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
@ -4545,6 +4520,19 @@ public abstract class AbstractEntityPersister
} }
} }
private UniqueEntityLoader getLoaderByString(String internalFetchProfile) {
if ( "merge".equals( internalFetchProfile ) ) {
return loaders.getOrCreateByInternalFetchProfileMerge( this::buildMergeCascadeEntityLoader );
}
else if ( "refresh".equals( internalFetchProfile ) ) {
return loaders.getOrCreateByInternalFetchProfileRefresh( this::buildRefreshCascadeEntityLoader );
}
else {
//At this time there's no code storing any other fetch profiles; also, the map implementation isn't supporting the option.
return null;
}
}
public final boolean isAllNull(Object[] array, int tableNumber) { public final boolean isAllNull(Object[] array, int tableNumber) {
for ( int i = 0; i < array.length; i++ ) { for ( int i = 0; i < array.length; i++ ) {
if ( isPropertyOfTable( i, tableNumber ) && array[i] != null ) { if ( isPropertyOfTable( i, tableNumber ) && array[i] != null ) {

View File

@ -0,0 +1,45 @@
/*
* 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.persister.entity;
import java.util.function.Function;
import org.hibernate.LockMode;
import org.hibernate.internal.util.collections.LazyIndexedMap;
import org.hibernate.loader.entity.UniqueEntityLoader;
final class EntityLoaderLazyCollection extends LazyIndexedMap<Object,UniqueEntityLoader> {
/**
* The need here is weird: we need to store an instance of UniqueEntityLoader
* for each value of the LockMode enum, but also store two special internal
* fetch profiles called "merge" and "refresh".
* We assign these two their own ordinal ids such as to be treated as two
* additional enum states; but to access these they will have their own
* dedicated method implementations.
*/
private static final int MERGE_INDEX = LockMode.values().length;
private static final int REFRESH_INDEX = MERGE_INDEX + 1;
private static final int TOTAL_STORAGE_SIZE = REFRESH_INDEX + 1;
public EntityLoaderLazyCollection() {
super( TOTAL_STORAGE_SIZE );
}
UniqueEntityLoader getOrBuildByLockMode(LockMode lockMode, Function<LockMode,UniqueEntityLoader> builderFunction) {
return super.computeIfAbsent( lockMode.ordinal(), lockMode, builderFunction );
}
UniqueEntityLoader getOrCreateByInternalFetchProfileMerge(Function<LockMode,UniqueEntityLoader> builderFunction) {
return super.computeIfAbsent( MERGE_INDEX, null, builderFunction );
}
UniqueEntityLoader getOrCreateByInternalFetchProfileRefresh(Function<LockMode,UniqueEntityLoader> builderFunction) {
return super.computeIfAbsent( REFRESH_INDEX, null, builderFunction );
}
}