HHH-16704 Avoid iterating a LinkedHashMap during ActionQueue processing

This commit is contained in:
Sanne Grinovero 2023-05-25 16:24:30 +01:00 committed by Sanne Grinovero
parent 9e09c057c5
commit c9457db5b6
1 changed files with 142 additions and 153 deletions

View File

@ -47,7 +47,6 @@ import org.hibernate.event.spi.EventSource;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -105,125 +104,114 @@ public class ActionQueue {
private AfterTransactionCompletionProcessQueue afterTransactionProcesses; private AfterTransactionCompletionProcessQueue afterTransactionProcesses;
private BeforeTransactionCompletionProcessQueue beforeTransactionProcesses; private BeforeTransactionCompletionProcessQueue beforeTransactionProcesses;
/** //Extract this as a constant to perform efficient iterations:
* A LinkedHashMap containing providers for all the ExecutableLists, inserted in execution order //method values() otherwise allocates a new array on each invocation.
*/ private static final OrderedActions[] ORDERED_OPERATIONS = OrderedActions.values();
private static final LinkedHashMap<Class<? extends Executable>,ListProvider<?>> EXECUTABLE_LISTS_MAP;
static { //The order of these operations is very important
EXECUTABLE_LISTS_MAP = CollectionHelper.linkedMapOfSize( 8 ); private enum OrderedActions {
/* CollectionRemoveAction {
CollectionRemoveAction actions have to be executed before OrphanRemovalAction actions, to prevent a constraint violation @Override
when deleting an orphan Entity that contains an ElementCollection (see HHH-15159) public ExecutableList<?> getActions(ActionQueue instance) {
*/
EXECUTABLE_LISTS_MAP.put(
CollectionRemoveAction.class,
new ListProvider<CollectionRemoveAction>() {
ExecutableList<CollectionRemoveAction> get(ActionQueue instance) {
return instance.collectionRemovals; return instance.collectionRemovals;
} }
ExecutableList<CollectionRemoveAction> init(ActionQueue instance) { @Override
return instance.collectionRemovals = new ExecutableList<>( public void ensureInitialized(ActionQueue instance) {
instance.isOrderUpdatesEnabled() if ( instance.collectionRemovals == null ) {
); instance.collectionRemovals = new ExecutableList<>( instance.isOrderUpdatesEnabled() );
} }
} }
); },
EXECUTABLE_LISTS_MAP.put( OrphanRemovalAction {
OrphanRemovalAction.class, @Override
new ListProvider<OrphanRemovalAction>() { public ExecutableList<?> getActions(ActionQueue instance) {
ExecutableList<OrphanRemovalAction> get(ActionQueue instance) {
return instance.orphanRemovals; return instance.orphanRemovals;
} }
ExecutableList<OrphanRemovalAction> init(ActionQueue instance) { @Override
// OrphanRemovalAction executables never require sorting. public void ensureInitialized(ActionQueue instance) {
return instance.orphanRemovals = new ExecutableList<>( false ); if ( instance.orphanRemovals == null ) {
instance.orphanRemovals = new ExecutableList<>( false );
} }
} }
); },
EXECUTABLE_LISTS_MAP.put( EntityInsertAction {
AbstractEntityInsertAction.class, @Override
new ListProvider<AbstractEntityInsertAction>() { public ExecutableList<?> getActions(ActionQueue instance) {
ExecutableList<AbstractEntityInsertAction> get(ActionQueue instance) {
return instance.insertions; return instance.insertions;
} }
ExecutableList<AbstractEntityInsertAction> init(ActionQueue instance) { @Override
if ( instance.isOrderInsertsEnabled() ) { public void ensureInitialized(final ActionQueue instance) {
return instance.insertions = new ExecutableList<>( if ( instance.insertions == null ) {
InsertActionSorter.INSTANCE //Special case of initialization
); instance.insertions = instance.isOrderInsertsEnabled()
} ? new ExecutableList<>( InsertActionSorter.INSTANCE )
else { : new ExecutableList<>( false );
return instance.insertions = new ExecutableList<>(
false
);
} }
} }
} },
); EntityUpdateAction {
EXECUTABLE_LISTS_MAP.put( @Override
EntityUpdateAction.class, public ExecutableList<?> getActions(ActionQueue instance) {
new ListProvider<EntityUpdateAction>() {
ExecutableList<EntityUpdateAction> get(ActionQueue instance) {
return instance.updates; return instance.updates;
} }
ExecutableList<EntityUpdateAction> init(ActionQueue instance) { @Override
return instance.updates = new ExecutableList<>( public void ensureInitialized(ActionQueue instance) {
instance.isOrderUpdatesEnabled() if ( instance.updates == null ) {
); instance.updates = new ExecutableList<>( instance.isOrderUpdatesEnabled() );
} }
} }
); },
EXECUTABLE_LISTS_MAP.put( QueuedOperationCollectionAction {
QueuedOperationCollectionAction.class, @Override
new ListProvider<QueuedOperationCollectionAction>() { public ExecutableList<?> getActions(ActionQueue instance) {
ExecutableList<QueuedOperationCollectionAction> get(ActionQueue instance) {
return instance.collectionQueuedOps; return instance.collectionQueuedOps;
} }
ExecutableList<QueuedOperationCollectionAction> init(ActionQueue instance) { @Override
return instance.collectionQueuedOps = new ExecutableList<>( public void ensureInitialized(ActionQueue instance) {
instance.isOrderUpdatesEnabled() if ( instance.collectionQueuedOps == null ) {
); instance.collectionQueuedOps = new ExecutableList<>( instance.isOrderUpdatesEnabled() );
} }
} }
); },
EXECUTABLE_LISTS_MAP.put( CollectionUpdateAction {
CollectionUpdateAction.class, @Override
new ListProvider<CollectionUpdateAction>() { public ExecutableList<?> getActions(ActionQueue instance) {
ExecutableList<CollectionUpdateAction> get(ActionQueue instance) {
return instance.collectionUpdates; return instance.collectionUpdates;
} }
ExecutableList<CollectionUpdateAction> init(ActionQueue instance) { @Override
return instance.collectionUpdates = new ExecutableList<>( public void ensureInitialized(ActionQueue instance) {
instance.isOrderUpdatesEnabled() if ( instance.collectionUpdates == null ) {
); instance.collectionUpdates = new ExecutableList<>( instance.isOrderUpdatesEnabled() );
} }
} }
); },
EXECUTABLE_LISTS_MAP.put( CollectionRecreateAction {
CollectionRecreateAction.class, @Override
new ListProvider<CollectionRecreateAction>() { public ExecutableList<?> getActions(ActionQueue instance) {
ExecutableList<CollectionRecreateAction> get(ActionQueue instance) {
return instance.collectionCreations; return instance.collectionCreations;
} }
ExecutableList<CollectionRecreateAction> init(ActionQueue instance) { @Override
return instance.collectionCreations = new ExecutableList<>( public void ensureInitialized(ActionQueue instance) {
instance.isOrderUpdatesEnabled() if ( instance.collectionCreations == null ) {
); instance.collectionCreations = new ExecutableList<>( instance.isOrderUpdatesEnabled() );
} }
} }
); },
EXECUTABLE_LISTS_MAP.put( EntityDeleteAction {
EntityDeleteAction.class, @Override
new ListProvider<EntityDeleteAction>() { public ExecutableList<?> getActions(ActionQueue instance) {
ExecutableList<EntityDeleteAction> get(ActionQueue instance) {
return instance.deletions; return instance.deletions;
} }
ExecutableList<EntityDeleteAction> init(ActionQueue instance) { @Override
// EntityDeleteAction executables never require sorting. public void ensureInitialized(ActionQueue instance) {
return instance.deletions = new ExecutableList<>( false ); if ( instance.deletions == null ) {
instance.deletions = new ExecutableList<>( false );
} }
} }
); };
public abstract ExecutableList<?> getActions(ActionQueue instance);
public abstract void ensureInitialized(ActionQueue instance);
} }
/** /**
@ -237,12 +225,12 @@ public class ActionQueue {
} }
public void clear() { public void clear() {
EXECUTABLE_LISTS_MAP.forEach( (k,listProvider) -> { for ( OrderedActions value : ORDERED_OPERATIONS ) {
ExecutableList<?> l = listProvider.get( this ); final ExecutableList<?> list = value.getActions( this );
if ( l != null ) { if ( list != null ) {
l.clear(); list.clear();
}
} }
} );
if ( unresolvedInsertions != null ) { if ( unresolvedInsertions != null ) {
unresolvedInsertions.clear(); unresolvedInsertions.clear();
} }
@ -291,7 +279,8 @@ public class ActionQueue {
} }
else { else {
LOG.trace( "Adding resolved non-early insert action." ); LOG.trace( "Adding resolved non-early insert action." );
addAction( AbstractEntityInsertAction.class, insert ); OrderedActions.EntityInsertAction.ensureInitialized( this );
this.insertions.add( insert );
} }
if ( !insert.isVeto() ) { if ( !insert.isVeto() ) {
insert.makeEntityManaged(); insert.makeEntityManaged();
@ -310,15 +299,6 @@ public class ActionQueue {
} }
} }
private <T extends Executable & Comparable<? super T> & Serializable> void addAction(Class<T> executableClass, T action) {
listProvider( executableClass ).getOrInit( this ).add( action );
}
@SuppressWarnings("unchecked")
private <T extends Executable & Comparable<? super T> & Serializable> ListProvider<T> listProvider(Class<T> actionClass) {
return (ListProvider<T>) EXECUTABLE_LISTS_MAP.get(actionClass);
}
/** /**
* Adds an entity (IDENTITY) insert action * Adds an entity (IDENTITY) insert action
* *
@ -335,7 +315,8 @@ public class ActionQueue {
* @param action The action representing the entity deletion * @param action The action representing the entity deletion
*/ */
public void addAction(EntityDeleteAction action) { public void addAction(EntityDeleteAction action) {
addAction( EntityDeleteAction.class, action ); OrderedActions.EntityDeleteAction.ensureInitialized( this );
this.deletions.add( action );
} }
/** /**
@ -343,8 +324,9 @@ public class ActionQueue {
* *
* @param action The action representing the orphan removal * @param action The action representing the orphan removal
*/ */
public void addAction(OrphanRemovalAction action) { public void addAction(final OrphanRemovalAction action) {
addAction( OrphanRemovalAction.class, action ); OrderedActions.OrphanRemovalAction.ensureInitialized( this );
this.orphanRemovals.add( action );
} }
/** /**
@ -352,8 +334,9 @@ public class ActionQueue {
* *
* @param action The action representing the entity update * @param action The action representing the entity update
*/ */
public void addAction(EntityUpdateAction action) { public void addAction(final EntityUpdateAction action) {
addAction( EntityUpdateAction.class, action ); OrderedActions.EntityUpdateAction.ensureInitialized( this );
this.updates.add( action );
} }
/** /**
@ -361,8 +344,9 @@ public class ActionQueue {
* *
* @param action The action representing the (re)creation of a collection * @param action The action representing the (re)creation of a collection
*/ */
public void addAction(CollectionRecreateAction action) { public void addAction(final CollectionRecreateAction action) {
addAction( CollectionRecreateAction.class, action ); OrderedActions.CollectionRecreateAction.ensureInitialized( this );
this.collectionCreations.add( action );
} }
/** /**
@ -370,8 +354,9 @@ public class ActionQueue {
* *
* @param action The action representing the removal of a collection * @param action The action representing the removal of a collection
*/ */
public void addAction(CollectionRemoveAction action) { public void addAction(final CollectionRemoveAction action) {
addAction( CollectionRemoveAction.class, action ); OrderedActions.CollectionRemoveAction.ensureInitialized( this );
this.collectionRemovals.add( action );
} }
/** /**
@ -379,8 +364,9 @@ public class ActionQueue {
* *
* @param action The action representing the update of a collection * @param action The action representing the update of a collection
*/ */
public void addAction(CollectionUpdateAction action) { public void addAction(final CollectionUpdateAction action) {
addAction( CollectionUpdateAction.class, action ); OrderedActions.CollectionUpdateAction.ensureInitialized( this );
this.collectionUpdates.add( action );
} }
/** /**
@ -389,7 +375,8 @@ public class ActionQueue {
* @param action The action representing the queued operation * @param action The action representing the queued operation
*/ */
public void addAction(QueuedOperationCollectionAction action) { public void addAction(QueuedOperationCollectionAction action) {
addAction( QueuedOperationCollectionAction.class, action ); OrderedActions.QueuedOperationCollectionAction.ensureInitialized( this );
this.collectionQueuedOps.add( action );
} }
/** /**
@ -484,12 +471,9 @@ public class ActionQueue {
throw new IllegalStateException( "About to execute actions, but there are unresolved entity insert actions." ); throw new IllegalStateException( "About to execute actions, but there are unresolved entity insert actions." );
} }
EXECUTABLE_LISTS_MAP.forEach( (k,listProvider) -> { for ( OrderedActions action : ORDERED_OPERATIONS ) {
ExecutableList<?> l = listProvider.get( this ); executeActions( action.getActions( this ) );
if ( l != null && !l.isEmpty() ) {
executeActions( l );
} }
} );
} }
/** /**
@ -562,9 +546,9 @@ public class ActionQueue {
if ( tables.isEmpty() ) { if ( tables.isEmpty() ) {
return false; return false;
} }
for ( ListProvider<?> listProvider : EXECUTABLE_LISTS_MAP.values() ) { for ( OrderedActions action : ORDERED_OPERATIONS ) {
ExecutableList<?> l = listProvider.get( this ); final ExecutableList<?> list = action.getActions( this );
if ( areTablesToBeUpdated( l, tables ) ) { if ( areTablesToBeUpdated( list, tables ) ) {
return true; return true;
} }
} }
@ -610,6 +594,9 @@ public class ActionQueue {
*/ */
private <E extends Executable & Comparable<? super E> & Serializable> void executeActions(ExecutableList<E> list) private <E extends Executable & Comparable<? super E> & Serializable> void executeActions(ExecutableList<E> list)
throws HibernateException { throws HibernateException {
if ( list == null || list.isEmpty() ) {
return;
}
// todo : consider ways to improve the double iteration of Executables here: // todo : consider ways to improve the double iteration of Executables here:
// 1) we explicitly iterate list here to perform Executable#execute() // 1) we explicitly iterate list here to perform Executable#execute()
// 2) ExecutableList#getQuerySpaces also iterates the Executables to collect query spaces. // 2) ExecutableList#getQuerySpaces also iterates the Executables to collect query spaces.
@ -908,8 +895,8 @@ public class ActionQueue {
} }
unresolvedInsertions.serialize( oos ); unresolvedInsertions.serialize( oos );
for ( ListProvider<?> p : EXECUTABLE_LISTS_MAP.values() ) { for ( OrderedActions action : ORDERED_OPERATIONS ) {
ExecutableList<?> l = p.get( this ); ExecutableList<?> l = action.getActions( this );
if ( l == null ) { if ( l == null ) {
oos.writeBoolean( false ); oos.writeBoolean( false );
} }
@ -939,12 +926,14 @@ public class ActionQueue {
rtn.unresolvedInsertions = UnresolvedEntityInsertActions.deserialize( ois, session ); rtn.unresolvedInsertions = UnresolvedEntityInsertActions.deserialize( ois, session );
for ( ListProvider<?> provider : EXECUTABLE_LISTS_MAP.values() ) { for ( OrderedActions action : ORDERED_OPERATIONS ) {
ExecutableList<?> l = provider.get( rtn ); ExecutableList<?> l = action.getActions( rtn );
boolean notNull = ois.readBoolean(); boolean notNull = ois.readBoolean();
if ( notNull ) { if ( notNull ) {
if ( l == null ) { if ( l == null ) {
l = provider.init( rtn ); //sorry.. trying hard to avoid generic initializations mess.
action.ensureInitialized( rtn );
l = action.getActions( rtn );
} }
l.readExternal( ois ); l.readExternal( ois );