HHH-14124 Avoid too many list allocations when hydrating query results
I suspect the original goal of having a single list of hydrated objects for all rows was to avoid instantiating one list per row, but we did just that in the fix in the last commit. This introduces a hack to signal that we're starting to process a new row while still keeping a flat, single-list structure: we inserting null elements in the list of hydrated objects. This is admittedly very ugly, but it's the only solution I can come up with if we want to keep the number of memory allocations similar to what we used to have. And hopefully this code will disappear in ORM 6.0.
This commit is contained in:
parent
3bdc5af63d
commit
2c98ab236d
|
@ -406,7 +406,7 @@ public abstract class Loader {
|
|||
}
|
||||
|
||||
initializeEntitiesAndCollections(
|
||||
hydratedObjects == null ? null : Collections.singletonList( hydratedObjects ),
|
||||
hydratedObjects,
|
||||
resultSet,
|
||||
session,
|
||||
queryParameters.isReadOnly( session )
|
||||
|
@ -423,7 +423,7 @@ public abstract class Loader {
|
|||
final EntityKey keyToRead) throws HibernateException {
|
||||
|
||||
final int entitySpan = getEntityPersisters().length;
|
||||
final List<List<Object>> hydratedObjectsPerRow = entitySpan == 0 ?
|
||||
final List<Object> nullSeparatedHydratedObjects = entitySpan == 0 ?
|
||||
null : new ArrayList<>( entitySpan );
|
||||
|
||||
Object result = null;
|
||||
|
@ -431,26 +431,18 @@ public abstract class Loader {
|
|||
|
||||
try {
|
||||
do {
|
||||
List<Object> hydratedObjects;
|
||||
if ( hydratedObjectsPerRow == null ) {
|
||||
hydratedObjects = null;
|
||||
}
|
||||
else {
|
||||
hydratedObjects = new ArrayList<>( entitySpan );
|
||||
}
|
||||
Object loaded = getRowFromResultSet(
|
||||
resultSet,
|
||||
session,
|
||||
queryParameters,
|
||||
getLockModes( queryParameters.getLockOptions() ),
|
||||
null,
|
||||
hydratedObjects,
|
||||
nullSeparatedHydratedObjects,
|
||||
loadedKeys,
|
||||
returnProxies
|
||||
);
|
||||
if ( hydratedObjects != null && !hydratedObjects.isEmpty() ) {
|
||||
hydratedObjectsPerRow.add( hydratedObjects );
|
||||
}
|
||||
// Signal that a new row starts. Used in initializeEntitiesAndCollections
|
||||
nullSeparatedHydratedObjects.add( null );
|
||||
if ( !keyToRead.equals( loadedKeys[0] ) ) {
|
||||
throw new AssertionFailure(
|
||||
String.format(
|
||||
|
@ -476,7 +468,7 @@ public abstract class Loader {
|
|||
}
|
||||
|
||||
initializeEntitiesAndCollections(
|
||||
hydratedObjectsPerRow,
|
||||
nullSeparatedHydratedObjects,
|
||||
resultSet,
|
||||
session,
|
||||
queryParameters.isReadOnly( session )
|
||||
|
@ -996,7 +988,7 @@ public abstract class Loader {
|
|||
final int entitySpan = getEntityPersisters().length;
|
||||
final boolean createSubselects = isSubselectLoadingEnabled();
|
||||
final List<EntityKey[]> subselectResultKeys = createSubselects ? new ArrayList<>() : null;
|
||||
final List<List<Object>> hydratedObjectsPerRow = entitySpan == 0 ? null : new ArrayList<>();
|
||||
final List<Object> nullSeparatedHydratedObjectsPerRow = entitySpan == 0 ? null : new ArrayList<>();
|
||||
|
||||
final List results = getRowsFromResultSet(
|
||||
rs,
|
||||
|
@ -1005,12 +997,12 @@ public abstract class Loader {
|
|||
returnProxies,
|
||||
forcedResultTransformer,
|
||||
maxRows,
|
||||
hydratedObjectsPerRow,
|
||||
nullSeparatedHydratedObjectsPerRow,
|
||||
subselectResultKeys
|
||||
);
|
||||
|
||||
initializeEntitiesAndCollections(
|
||||
hydratedObjectsPerRow,
|
||||
nullSeparatedHydratedObjectsPerRow,
|
||||
rs,
|
||||
session,
|
||||
queryParameters.isReadOnly( session ),
|
||||
|
@ -1029,7 +1021,7 @@ public abstract class Loader {
|
|||
boolean returnProxies,
|
||||
ResultTransformer forcedResultTransformer,
|
||||
int maxRows,
|
||||
List<List<Object>> hydratedObjectsPerRow,
|
||||
List<Object> nullSeparatedHydratedObjects,
|
||||
List<EntityKey[]> subselectResultKeys) throws SQLException {
|
||||
final int entitySpan = getEntityPersisters().length;
|
||||
final boolean createSubselects = isSubselectLoadingEnabled();
|
||||
|
@ -1047,28 +1039,20 @@ public abstract class Loader {
|
|||
if ( debugEnabled ) {
|
||||
LOG.debugf( "Result set row: %s", count );
|
||||
}
|
||||
List<Object> hydratedObjects;
|
||||
if ( hydratedObjectsPerRow == null ) {
|
||||
hydratedObjects = null;
|
||||
}
|
||||
else {
|
||||
hydratedObjects = new ArrayList<>( entitySpan );
|
||||
}
|
||||
Object result = getRowFromResultSet(
|
||||
rs,
|
||||
session,
|
||||
queryParameters,
|
||||
lockModesArray,
|
||||
optionalObjectKey,
|
||||
hydratedObjects,
|
||||
nullSeparatedHydratedObjects,
|
||||
keys,
|
||||
returnProxies,
|
||||
forcedResultTransformer
|
||||
);
|
||||
results.add( result );
|
||||
if ( hydratedObjects != null && !hydratedObjects.isEmpty() ) {
|
||||
hydratedObjectsPerRow.add( hydratedObjects );
|
||||
}
|
||||
// Signal that a new row starts. Used in initializeEntitiesAndCollections
|
||||
nullSeparatedHydratedObjects.add( null );
|
||||
if ( createSubselects ) {
|
||||
subselectResultKeys.add( keys );
|
||||
keys = new EntityKey[entitySpan]; //can't reuse in this case
|
||||
|
@ -1159,12 +1143,12 @@ public abstract class Loader {
|
|||
}
|
||||
|
||||
private void initializeEntitiesAndCollections(
|
||||
final List<List<Object>> hydratedObjectsPerRow,
|
||||
final List<Object> nullSeparatedHydratedObjects,
|
||||
final Object resultSetId,
|
||||
final SharedSessionContractImplementor session,
|
||||
final boolean readOnly) throws HibernateException {
|
||||
initializeEntitiesAndCollections(
|
||||
hydratedObjectsPerRow,
|
||||
nullSeparatedHydratedObjects,
|
||||
resultSetId,
|
||||
session,
|
||||
readOnly,
|
||||
|
@ -1173,7 +1157,7 @@ public abstract class Loader {
|
|||
}
|
||||
|
||||
private void initializeEntitiesAndCollections(
|
||||
final List<List<Object>> hydratedObjectsPerRow,
|
||||
final List<Object> nullSeparatedHydratedObjects,
|
||||
final Object resultSetId,
|
||||
final SharedSessionContractImplementor session,
|
||||
final boolean readOnly,
|
||||
|
@ -1205,11 +1189,13 @@ public abstract class Loader {
|
|||
post = null;
|
||||
}
|
||||
|
||||
if ( hydratedObjectsPerRow != null && !hydratedObjectsPerRow.isEmpty() ) {
|
||||
if ( nullSeparatedHydratedObjects != null && !nullSeparatedHydratedObjects.isEmpty() ) {
|
||||
if ( LOG.isTraceEnabled() ) {
|
||||
int hydratedObjectsSize = 0;
|
||||
for ( List<Object> hydratedObjects : hydratedObjectsPerRow ) {
|
||||
hydratedObjectsSize += hydratedObjects.size();
|
||||
for ( Object hydratedObject : nullSeparatedHydratedObjects ) {
|
||||
if ( hydratedObject != null ) {
|
||||
++hydratedObjectsSize;
|
||||
}
|
||||
}
|
||||
LOG.tracev( "Total objects hydrated: {0}", hydratedObjectsSize );
|
||||
}
|
||||
|
@ -1222,17 +1208,21 @@ public abstract class Loader {
|
|||
.listeners();
|
||||
|
||||
GraphImplementor<?> fetchGraphLoadContextToRestore = session.getFetchGraphLoadContext();
|
||||
for ( List<?> hydratedObjectsForRow : hydratedObjectsPerRow ) {
|
||||
for ( Object hydratedObject : hydratedObjectsForRow ) {
|
||||
TwoPhaseLoad.initializeEntity( hydratedObject, readOnly, session, pre, listeners );
|
||||
for ( Object hydratedObject : nullSeparatedHydratedObjects ) {
|
||||
if ( hydratedObject == null ) {
|
||||
// This is a hack to signal that we're starting to process a new row
|
||||
|
||||
// HHH-14124: TwoPhaseLoad has nasty side-effects in order to handle sub-graphs.
|
||||
// That's very fragile, but someone would need to spend much more time on this
|
||||
// in order to implement it correctly, and apparently that's already been done in ORM 6.0.
|
||||
// So for now, we'll just ensure side-effects (and whatever bugs they lead to)
|
||||
// are limited to each row.
|
||||
session.setFetchGraphLoadContext( fetchGraphLoadContextToRestore );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// HHH-14124: TwoPhaseLoad has nasty side-effects in order to handle sub-graphs.
|
||||
// That's very fragile, but someone would need to spend much more time on this
|
||||
// in order to implement it correctly, and apparently that's already been done in ORM 6.0.
|
||||
// So for now, we'll just ensure side-effects (and whatever bugs they lead to)
|
||||
// are limited to each row.
|
||||
session.setFetchGraphLoadContext( fetchGraphLoadContextToRestore );
|
||||
TwoPhaseLoad.initializeEntity( hydratedObject, readOnly, session, pre, listeners );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1248,11 +1238,14 @@ public abstract class Loader {
|
|||
}
|
||||
}
|
||||
|
||||
if ( hydratedObjectsPerRow != null ) {
|
||||
for ( List<?> hydratedObjectsForRow : hydratedObjectsPerRow ) {
|
||||
for ( Object hydratedObject : hydratedObjectsForRow ) {
|
||||
TwoPhaseLoad.afterInitialize( hydratedObject, session );
|
||||
if ( nullSeparatedHydratedObjects != null ) {
|
||||
for ( Object hydratedObject : nullSeparatedHydratedObjects ) {
|
||||
if ( hydratedObject == null ) {
|
||||
// This is a hack to signal that we're starting to process a new row
|
||||
// Ignore
|
||||
continue;
|
||||
}
|
||||
TwoPhaseLoad.afterInitialize( hydratedObject, session );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1261,21 +1254,24 @@ public abstract class Loader {
|
|||
// endCollectionLoad to ensure the collection is in the
|
||||
// persistence context.
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
if ( hydratedObjectsPerRow != null && !hydratedObjectsPerRow.isEmpty() ) {
|
||||
for ( List<?> hydratedObjectsForRow : hydratedObjectsPerRow ) {
|
||||
for ( Object hydratedObject : hydratedObjectsForRow ) {
|
||||
TwoPhaseLoad.postLoad( hydratedObject, session, post );
|
||||
if ( afterLoadActions != null ) {
|
||||
for ( AfterLoadAction afterLoadAction : afterLoadActions ) {
|
||||
final EntityEntry entityEntry = persistenceContext.getEntry( hydratedObject );
|
||||
if ( entityEntry == null ) {
|
||||
// big problem
|
||||
throw new HibernateException(
|
||||
"Could not locate EntityEntry immediately after two-phase load"
|
||||
);
|
||||
}
|
||||
afterLoadAction.afterLoad( session, hydratedObject, (Loadable) entityEntry.getPersister() );
|
||||
if ( nullSeparatedHydratedObjects != null && !nullSeparatedHydratedObjects.isEmpty() ) {
|
||||
for ( Object hydratedObject : nullSeparatedHydratedObjects ) {
|
||||
if ( hydratedObject == null ) {
|
||||
// This is a hack to signal that we're starting to process a new row
|
||||
// Ignore
|
||||
continue;
|
||||
}
|
||||
TwoPhaseLoad.postLoad( hydratedObject, session, post );
|
||||
if ( afterLoadActions != null ) {
|
||||
for ( AfterLoadAction afterLoadAction : afterLoadActions ) {
|
||||
final EntityEntry entityEntry = persistenceContext.getEntry( hydratedObject );
|
||||
if ( entityEntry == null ) {
|
||||
// big problem
|
||||
throw new HibernateException(
|
||||
"Could not locate EntityEntry immediately after two-phase load"
|
||||
);
|
||||
}
|
||||
afterLoadAction.afterLoad( session, hydratedObject, (Loadable) entityEntry.getPersister() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue