Skip to content

Commit be895c7

Browse files
committed
HHH-18767 make MultiIdEntityLoaderArrayParam respect explicit BatchSize
keep ignoring the *implicit* upper limit from the Dialect refactor a very long method which was extremely hard to understand Signed-off-by: Gavin King <[email protected]>
1 parent 7f7c861 commit be895c7

File tree

3 files changed

+234
-180
lines changed

3 files changed

+234
-180
lines changed

hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java

Lines changed: 132 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,23 @@
4040
import org.hibernate.sql.results.spi.ManagedResultConsumer;
4141

4242
import org.checkerframework.checker.nullness.qual.NonNull;
43+
import org.hibernate.type.descriptor.java.JavaType;
4344

4445
import static java.lang.Boolean.TRUE;
4546
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
47+
import static org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.loadFromSessionCacheStatic;
4648

4749
/**
4850
* @author Steve Ebersole
4951
*/
5052
public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoader<E> implements SqlArrayMultiKeyLoader {
5153
private final JdbcMapping arrayJdbcMapping;
5254
private final JdbcParameter jdbcParameter;
55+
private final int idJdbcTypeCount;
5356

54-
public MultiIdEntityLoaderArrayParam(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) {
57+
public MultiIdEntityLoaderArrayParam(EntityMappingType entityDescriptor, int identifierColumnSpan, SessionFactoryImplementor sessionFactory) {
5558
super( entityDescriptor, sessionFactory );
59+
this.idJdbcTypeCount = identifierColumnSpan;
5660
final Class<?> arrayClass = createTypedArray( 0 ).getClass();
5761
arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping(
5862
getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( arrayClass ),
@@ -77,88 +81,152 @@ protected <K> List<E> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp
7781
);
7882
}
7983

84+
assert loadOptions.isOrderReturnEnabled();
85+
8086
final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled();
81-
final LockOptions lockOptions = (loadOptions.getLockOptions() == null)
87+
final JavaType<?> idType = getLoadable().getIdentifierMapping().getJavaType();
88+
89+
final LockOptions lockOptions = loadOptions.getLockOptions() == null
8290
? new LockOptions( LockMode.NONE )
8391
: loadOptions.getLockOptions();
8492

93+
final int maxBatchSize = maxBatchSize( ids, loadOptions );
94+
8595
final List<Object> result = CollectionHelper.arrayList( ids.length );
86-
List<Object> idsToLoadFromDatabase = null;
87-
List<Integer> idsToLoadFromDatabaseResultIndexes = null;
8896

89-
for ( int i = 0; i < ids.length; i++ ) {
90-
final Object id;
91-
if ( coerce ) {
92-
id = getLoadable().getIdentifierMapping().getJavaType().coerce( ids[ i ], session );
93-
}
94-
else {
95-
id = ids[ i ];
96-
}
97+
final List<Object> idsInBatch = new ArrayList<>();
98+
final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>();
9799

100+
for ( int i = 0; i < ids.length; i++ ) {
101+
final Object id = coerce ? idType.coerce( ids[i], session ) : ids[i];
98102
final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() );
99103

100-
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
101-
LoadEvent loadEvent = new LoadEvent(
102-
id,
103-
getLoadable().getJavaType().getJavaTypeClass().getName(),
104-
lockOptions,
105-
session,
106-
LoaderHelper.getReadOnlyFromLoadQueryInfluencers(session)
107-
);
104+
if ( !loadFromCaches( loadOptions, session, id, lockOptions, entityKey, result, i ) ) {
105+
// if we did not hit any of the continues above,
106+
// then we need to batch load the entity state.
107+
idsInBatch.add( id );
108108

109-
Object managedEntity = null;
110-
111-
if ( loadOptions.isSessionCheckingEnabled() ) {
112-
// look for it in the Session first
113-
final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.loadFromSessionCacheStatic(
114-
loadEvent,
115-
entityKey,
116-
LoadEventListener.GET
117-
);
118-
managedEntity = persistenceContextEntry.getEntity();
119-
120-
if ( managedEntity != null
121-
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
122-
&& !persistenceContextEntry.isManaged() ) {
123-
// put a null in the result
124-
result.add( i, null );
125-
continue;
126-
}
109+
if ( idsInBatch.size() >= maxBatchSize ) {
110+
// we've hit the allotted max-batch-size, perform an "intermediate load"
111+
loadEntitiesById( loadOptions, session, lockOptions, idsInBatch );
112+
idsInBatch.clear();
127113
}
128114

129-
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
130-
// look for it in the SessionFactory
131-
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
132-
loadEvent,
133-
getLoadable().getEntityPersister(),
134-
entityKey
135-
);
136-
}
115+
// Save the EntityKey instance for use later
116+
result.add( i, entityKey );
117+
elementPositionsLoadedByBatch.add( i );
118+
}
119+
}
137120

138-
if ( managedEntity != null ) {
139-
result.add( i, managedEntity );
140-
continue;
121+
if ( !idsInBatch.isEmpty() ) {
122+
// we still have ids to load from the processing above since
123+
// the last max-batch-size trigger, perform a load for them
124+
loadEntitiesById( loadOptions, session, lockOptions, idsInBatch );
125+
}
126+
127+
// for each result where we set the EntityKey earlier, replace them
128+
handleResults( loadOptions, session, elementPositionsLoadedByBatch, result );
129+
130+
//noinspection unchecked
131+
return (List<E>) result;
132+
}
133+
134+
private boolean loadFromCaches(
135+
MultiIdLoadOptions loadOptions,
136+
EventSource session,
137+
Object id,
138+
LockOptions lockOptions,
139+
EntityKey entityKey,
140+
List<Object> result,
141+
int i) {
142+
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
143+
final LoadEvent loadEvent = new LoadEvent(
144+
id,
145+
getLoadable().getJavaType().getJavaTypeClass().getName(),
146+
lockOptions,
147+
session,
148+
LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session )
149+
);
150+
151+
Object managedEntity = null;
152+
153+
if ( loadOptions.isSessionCheckingEnabled() ) {
154+
// look for it in the Session first
155+
final PersistenceContextEntry persistenceContextEntry =
156+
loadFromSessionCacheStatic( loadEvent, entityKey, LoadEventListener.GET );
157+
managedEntity = persistenceContextEntry.getEntity();
158+
159+
if ( managedEntity != null
160+
&& !loadOptions.isReturnOfDeletedEntitiesEnabled()
161+
&& !persistenceContextEntry.isManaged() ) {
162+
// put a null in the result
163+
result.add( i, null );
164+
return true;
141165
}
142166
}
143167

144-
// if we did not hit any of the continues above, then we need to batch
145-
// load the entity state.
146-
if ( idsToLoadFromDatabase == null ) {
147-
idsToLoadFromDatabase = new ArrayList<>();
148-
idsToLoadFromDatabaseResultIndexes = new ArrayList<>();
168+
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
169+
// look for it in the SessionFactory
170+
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
171+
loadEvent,
172+
getLoadable().getEntityPersister(),
173+
entityKey
174+
);
175+
}
176+
177+
if ( managedEntity != null ) {
178+
result.add( i, managedEntity );
179+
return true;
149180
}
150-
// hold its place in the result with the EntityKey, we'll come back to it later
151-
result.add( i, entityKey );
152-
idsToLoadFromDatabase.add( id );
153-
idsToLoadFromDatabaseResultIndexes.add( i );
154181
}
182+
return false;
183+
}
155184

156-
if ( idsToLoadFromDatabase == null ) {
157-
// all the given ids were already associated with the Session
158-
//noinspection unchecked
159-
return (List<E>) result;
185+
private static void handleResults(
186+
MultiIdLoadOptions loadOptions,
187+
EventSource session,
188+
List<Integer> elementPositionsLoadedByBatch,
189+
List<Object> result) {
190+
final PersistenceContext persistenceContext = session.getPersistenceContext();
191+
for ( Integer position : elementPositionsLoadedByBatch ) {
192+
// the element value at this position in the result List should be
193+
// the EntityKey for that entity - reuse it
194+
final EntityKey entityKey = (EntityKey) result.get( position );
195+
BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session );
196+
Object entity = persistenceContext.getEntity( entityKey );
197+
if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) {
198+
// make sure it is not DELETED
199+
final EntityEntry entry = persistenceContext.getEntry( entity );
200+
if ( entry.getStatus().isDeletedOrGone() ) {
201+
// the entity is locally deleted, and the options ask that we not return such entities...
202+
entity = null;
203+
}
204+
else {
205+
entity = persistenceContext.proxyFor( entity );
206+
}
207+
}
208+
result.set( position, entity );
209+
}
210+
}
211+
212+
private <K> int maxBatchSize(K[] ids, MultiIdLoadOptions loadOptions) {
213+
if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) {
214+
return loadOptions.getBatchSize();
215+
}
216+
else {
217+
// disable batching by default
218+
return ids.length;
219+
// return getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect()
220+
// .getBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
221+
// idJdbcTypeCount,
222+
// ids.length,
223+
// getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled()
224+
// );
160225
}
226+
}
161227

228+
private void loadEntitiesById(
229+
MultiIdLoadOptions loadOptions, EventSource session, LockOptions lockOptions, List<Object> idsToLoadFromDatabase) {
162230
final SelectStatement sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter(
163231
getLoadable(),
164232
getIdentifierMapping(),
@@ -194,37 +262,12 @@ protected <K> List<E> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp
194262
jdbcParameterBindings,
195263
new ExecutionContextWithSubselectFetchHandler( session,
196264
subSelectFetchableKeysHandler,
197-
TRUE.equals( loadOptions.getReadOnly(session) ) ),
265+
TRUE.equals( loadOptions.getReadOnly( session ) ) ),
198266
RowTransformerStandardImpl.instance(),
199267
null,
200268
idsToLoadFromDatabase.size(),
201269
ManagedResultConsumer.INSTANCE
202270
);
203-
204-
for ( int i = 0; i < idsToLoadFromDatabaseResultIndexes.size(); i++ ) {
205-
final Integer resultIndex = idsToLoadFromDatabaseResultIndexes.get(i);
206-
207-
// the element value at this position in the result List should be
208-
// the EntityKey for that entity - reuse it
209-
final EntityKey entityKey = (EntityKey) result.get( resultIndex );
210-
BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session );
211-
Object entity = persistenceContext.getEntity( entityKey );
212-
if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) {
213-
// make sure it is not DELETED
214-
final EntityEntry entry = persistenceContext.getEntry( entity );
215-
if ( entry.getStatus().isDeletedOrGone() ) {
216-
// the entity is locally deleted, and the options ask that we not return such entities...
217-
entity = null;
218-
}
219-
else {
220-
entity = persistenceContext.proxyFor( entity );
221-
}
222-
}
223-
result.set( resultIndex, entity );
224-
}
225-
226-
//noinspection unchecked
227-
return (List<E>) result;
228271
}
229272

230273

@@ -345,7 +388,7 @@ protected final <R,K> K[] processResolvableEntities(
345388
Object resolvedEntity = null;
346389

347390
// look for it in the Session first
348-
final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.loadFromSessionCacheStatic(
391+
final PersistenceContextEntry persistenceContextEntry = loadFromSessionCacheStatic(
349392
loadEvent,
350393
entityKey,
351394
LoadEventListener.GET

0 commit comments

Comments
 (0)