HHH-14271 Lazy initialization of UniqueEntityLoader for most LockMode types
This commit is contained in:
parent
c60765a528
commit
81d526e4db
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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(Object lockModeObject) {
|
private UniqueEntityLoader generateDelayedEntityLoader(final LockMode lockMode) {
|
||||||
// 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 ) {
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue