Skip to content

Commit 60f4645

Browse files
committed
HHH-12558 - Lazy load EntityLoaders to improve memory usage
1 parent 7943fe3 commit 60f4645

8 files changed

+96
-26
lines changed

hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77
package org.hibernate.boot;
88

9+
import java.util.Map;
10+
import java.util.function.Supplier;
11+
912
import org.hibernate.ConnectionReleaseMode;
1013
import org.hibernate.CustomEntityDirtinessStrategy;
1114
import org.hibernate.EntityMode;
@@ -27,9 +30,6 @@
2730
import org.hibernate.tuple.entity.EntityTuplizer;
2831
import org.hibernate.tuple.entity.EntityTuplizerFactory;
2932

30-
import java.util.Map;
31-
import java.util.function.Supplier;
32-
3333
/**
3434
* The contract for building a {@link org.hibernate.SessionFactory} given a number of options.
3535
*
@@ -303,6 +303,14 @@ SessionFactoryBuilder applyEntityTuplizer(
303303
*/
304304
SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style);
305305

306+
/**
307+
* Should entity Loaders be generated immediately? Or should the creation
308+
* be delayed until first need?
309+
*
310+
* @see org.hibernate.cfg.AvailableSettings#DELAY_ENTITY_LOADER_CREATIONS
311+
*/
312+
SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay);
313+
306314
/**
307315
* Allows specifying a default batch-fetch size for all entities and collections
308316
* which do not otherwise specify a batch-fetch size.

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ public SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style) {
219219
return this;
220220
}
221221

222+
@Override
223+
public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) {
224+
this.optionsBuilder.applyDelayedEntityLoaderCreations( delay );
225+
return this;
226+
}
227+
222228
@Override
223229
public SessionFactoryBuilder applyDefaultBatchFetchSize(int size) {
224230
this.optionsBuilder.applyDefaultBatchFetchSize( size );

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import static org.hibernate.cfg.AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY;
8383
import static org.hibernate.cfg.AvailableSettings.DEFAULT_BATCH_FETCH_SIZE;
8484
import static org.hibernate.cfg.AvailableSettings.DEFAULT_ENTITY_MODE;
85+
import static org.hibernate.cfg.AvailableSettings.DELAY_ENTITY_LOADER_CREATIONS;
8586
import static org.hibernate.cfg.AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS;
8687
import static org.hibernate.cfg.AvailableSettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
8788
import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION;
@@ -183,6 +184,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
183184
private MultiTableBulkIdStrategy multiTableBulkIdStrategy;
184185
private TempTableDdlTransactionHandling tempTableDdlTransactionHandling;
185186
private BatchFetchStyle batchFetchStyle;
187+
private boolean delayBatchFetchLoaderCreations;
186188
private int defaultBatchFetchSize;
187189
private Integer maximumFetchDepth;
188190
private NullPrecedence defaultNullPrecedence;
@@ -324,6 +326,7 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo
324326
);
325327

326328
this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) );
329+
this.delayBatchFetchLoaderCreations = cfgService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true );
327330
this.defaultBatchFetchSize = ConfigurationHelper.getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 );
328331
this.maximumFetchDepth = ConfigurationHelper.getInteger( MAX_FETCH_DEPTH, configurationSettings );
329332
final String defaultNullPrecedence = ConfigurationHelper.getString(
@@ -773,6 +776,11 @@ public BatchFetchStyle getBatchFetchStyle() {
773776
return batchFetchStyle;
774777
}
775778

779+
@Override
780+
public boolean isDelayBatchFetchLoaderCreationsEnabled() {
781+
return delayBatchFetchLoaderCreations;
782+
}
783+
776784
@Override
777785
public int getDefaultBatchFetchSize() {
778786
return defaultBatchFetchSize;
@@ -1110,6 +1118,10 @@ public void applyBatchFetchStyle(BatchFetchStyle style) {
11101118
this.batchFetchStyle = style;
11111119
}
11121120

1121+
public void applyDelayedEntityLoaderCreations(boolean delay) {
1122+
this.delayBatchFetchLoaderCreations = delay;
1123+
}
1124+
11131125
public void applyDefaultBatchFetchSize(int size) {
11141126
this.defaultBatchFetchSize = size;
11151127
}
@@ -1300,5 +1312,4 @@ public SessionFactoryOptions buildOptions() {
13001312

13011313
return this;
13021314
}
1303-
13041315
}

hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77
package org.hibernate.boot.spi;
88

9+
import java.util.Map;
10+
import java.util.function.Supplier;
11+
912
import org.hibernate.ConnectionReleaseMode;
1013
import org.hibernate.CustomEntityDirtinessStrategy;
1114
import org.hibernate.EntityMode;
@@ -28,9 +31,6 @@
2831
import org.hibernate.tuple.entity.EntityTuplizer;
2932
import org.hibernate.tuple.entity.EntityTuplizerFactory;
3033

31-
import java.util.Map;
32-
import java.util.function.Supplier;
33-
3434
/**
3535
* Convenience base class for custom implementors of SessionFactoryBuilder, using delegation
3636
*
@@ -193,6 +193,12 @@ public T applyBatchFetchStyle(BatchFetchStyle style) {
193193
return getThis();
194194
}
195195

196+
@Override
197+
public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) {
198+
delegate.applyDelayedEntityLoaderCreations( delay );
199+
return getThis();
200+
}
201+
196202
@Override
197203
public T applyDefaultBatchFetchSize(int size) {
198204
delegate.applyDefaultBatchFetchSize( size );

hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
*/
77
package org.hibernate.boot.spi;
88

9+
import java.util.Map;
10+
import java.util.TimeZone;
11+
import java.util.function.Supplier;
12+
913
import org.hibernate.ConnectionReleaseMode;
1014
import org.hibernate.CustomEntityDirtinessStrategy;
1115
import org.hibernate.EntityMode;
@@ -31,10 +35,6 @@
3135
import org.hibernate.resource.jdbc.spi.StatementInspector;
3236
import org.hibernate.tuple.entity.EntityTuplizerFactory;
3337

34-
import java.util.Map;
35-
import java.util.TimeZone;
36-
import java.util.function.Supplier;
37-
3838
/**
3939
* Convenience base class for custom implementors of SessionFactoryOptions, using delegation
4040
*
@@ -172,6 +172,11 @@ public BatchFetchStyle getBatchFetchStyle() {
172172
return delegate.getBatchFetchStyle();
173173
}
174174

175+
@Override
176+
public boolean isDelayBatchFetchLoaderCreationsEnabled() {
177+
return delegate.isDelayBatchFetchLoaderCreationsEnabled();
178+
}
179+
175180
@Override
176181
public int getDefaultBatchFetchSize() {
177182
return delegate.getDefaultBatchFetchSize();

hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ default Supplier<? extends Interceptor> getStatelessInterceptorImplementorSuppli
161161

162162
BatchFetchStyle getBatchFetchStyle();
163163

164+
boolean isDelayBatchFetchLoaderCreationsEnabled();
165+
164166
int getDefaultBatchFetchSize();
165167

166168
Integer getMaximumFetchDepth();

hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package org.hibernate.cfg;
88

99
import java.util.function.Supplier;
10-
1110
import javax.persistence.GeneratedValue;
1211

1312
import org.hibernate.HibernateException;
@@ -1560,6 +1559,20 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
15601559
*/
15611560
String BATCH_FETCH_STYLE = "hibernate.batch_fetch_style";
15621561

1562+
/**
1563+
* Controls how the individual Loaders for an entity are created.
1564+
*
1565+
* When `true` (the default), only the minimal set of Loaders are
1566+
* created. These include the handling for {@link org.hibernate.LockMode#READ}
1567+
* and {@link org.hibernate.LockMode#NONE} as well as specialized Loaders for
1568+
* merge and refresh handling.
1569+
*
1570+
* `false` indicates that all loaders should be created up front
1571+
*
1572+
* @since 5.3
1573+
*/
1574+
String DELAY_ENTITY_LOADER_CREATIONS = "hibernate.loader.delay_entity_loader_creations";
1575+
15631576
/**
15641577
* A transaction can be rolled back by another thread ("tracking by thread")
15651578
* -- not the original application. Examples of this include a JTA
@@ -1922,4 +1935,5 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
19221935
* @since 5.2.17
19231936
*/
19241937
String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding";
1938+
19251939
}

hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
import java.util.Arrays;
1515
import java.util.Collections;
1616
import java.util.Comparator;
17+
import java.util.EnumSet;
1718
import java.util.HashMap;
1819
import java.util.HashSet;
1920
import java.util.Iterator;
2021
import java.util.LinkedHashMap;
2122
import java.util.List;
23+
import java.util.Locale;
2224
import java.util.Map;
2325
import java.util.Set;
2426
import java.util.concurrent.ConcurrentHashMap;
@@ -4113,7 +4115,12 @@ public final void postInstantiate() throws MappingException {
41134115
protected void doPostInstantiate() {
41144116
}
41154117

4116-
//needed by subclasses to override the createLoader strategy
4118+
/**
4119+
* "Needed" by subclasses to override the createLoader strategy
4120+
*
4121+
* @deprecated Because there are better patterns for this
4122+
*/
4123+
@Deprecated
41174124
protected Map getLoaders() {
41184125
return loaders;
41194126
}
@@ -4125,9 +4132,17 @@ protected void createLoaders() {
41254132
noneLockLoader = createEntityLoader( LockMode.NONE );
41264133
readLockLoader = createEntityLoader( LockMode.READ );
41274134

4128-
final Map loaders = getLoaders();
41294135

4130-
// The loaders for the other lock modes are lazily loaded and will later be stored in this map.
4136+
// The loaders for the other lock modes are lazily loaded and will later be stored in this map,
4137+
// unless this setting is disabled
4138+
if ( ! factory.getSessionFactoryOptions().isDelayBatchFetchLoaderCreationsEnabled() ) {
4139+
for ( LockMode lockMode : EnumSet.complementOf( EnumSet.of( LockMode.NONE, LockMode.READ ) ) ) {
4140+
loaders.put( lockMode, createEntityLoader( lockMode ) );
4141+
}
4142+
}
4143+
4144+
4145+
// And finally, create the internal merge and refresh load plans
41314146

41324147
loaders.put(
41334148
"merge",
@@ -4147,10 +4162,10 @@ else if ( LockMode.READ == lockMode ) {
41474162
return readLockLoader;
41484163
}
41494164

4150-
return loaders.computeIfAbsent( lockMode, this::createLazyLoadedEntityLoader );
4165+
return loaders.computeIfAbsent( lockMode, this::generateDelayedEntityLoader );
41514166
}
41524167

4153-
private UniqueEntityLoader createLazyLoadedEntityLoader(Object lockModeObject) {
4168+
private UniqueEntityLoader generateDelayedEntityLoader(Object lockModeObject) {
41544169
// Unfortunately, the loaders map mixes LockModes and Strings as keys so we need to accept an Object.
41554170
// The cast is safe as we will always call this method with a LockMode.
41564171
LockMode lockMode = (LockMode) lockModeObject;
@@ -4159,23 +4174,26 @@ private UniqueEntityLoader createLazyLoadedEntityLoader(Object lockModeObject) {
41594174
case NONE:
41604175
case READ:
41614176
case OPTIMISTIC:
4162-
case OPTIMISTIC_FORCE_INCREMENT:
4177+
case OPTIMISTIC_FORCE_INCREMENT: {
41634178
return createEntityLoader( lockMode );
4179+
}
41644180
case UPGRADE:
41654181
case UPGRADE_NOWAIT:
41664182
case UPGRADE_SKIPLOCKED:
41674183
case FORCE:
41684184
case PESSIMISTIC_READ:
41694185
case PESSIMISTIC_WRITE:
4170-
case PESSIMISTIC_FORCE_INCREMENT:
4186+
case PESSIMISTIC_FORCE_INCREMENT: {
41714187
//TODO: inexact, what we really need to know is: are any outer joins used?
4172-
boolean disableForUpdate = getSubclassTableSpan() > 1 &&
4173-
hasSubclasses() &&
4174-
!getFactory().getDialect().supportsOuterJoinForUpdate();
4188+
boolean disableForUpdate = getSubclassTableSpan() > 1
4189+
&& hasSubclasses()
4190+
&& !getFactory().getDialect().supportsOuterJoinForUpdate();
41754191

41764192
return disableForUpdate ? readLockLoader : createEntityLoader( lockMode );
4177-
default:
4178-
throw new IllegalStateException( String.format( "Lock mode %1$s not supported by entity loaders.", lockMode ) );
4193+
}
4194+
default: {
4195+
throw new IllegalStateException( String.format( Locale.ROOT, "Lock mode %1$s not supported by entity loaders.", lockMode ) );
4196+
}
41794197
}
41804198
}
41814199

@@ -4258,7 +4276,7 @@ else if ( session.getLoadQueryInfluencers().getInternalFetchProfile() != null &&
42584276
// Next, we consider whether an 'internal' fetch profile has been set.
42594277
// This indicates a special fetch profile Hibernate needs applied
42604278
// (for its merge loading process e.g.).
4261-
return (UniqueEntityLoader) getLoaders().get( session.getLoadQueryInfluencers().getInternalFetchProfile() );
4279+
return loaders.get( session.getLoadQueryInfluencers().getInternalFetchProfile() );
42624280
}
42634281
else if ( isAffectedByEnabledFetchProfiles( session ) ) {
42644282
// If the session has associated influencers we need to adjust the
@@ -4272,7 +4290,7 @@ else if ( lockOptions.getTimeOut() != LockOptions.WAIT_FOREVER ) {
42724290
return createEntityLoader( lockOptions, session.getLoadQueryInfluencers() );
42734291
}
42744292
else {
4275-
return (UniqueEntityLoader) getLoaderByLockMode( lockOptions.getLockMode() );
4293+
return getLoaderByLockMode( lockOptions.getLockMode() );
42764294
}
42774295
}
42784296

0 commit comments

Comments
 (0)