HHH-13565 Ensure all events from EventListenerGroup can be fired without allocations
This commit is contained in:
parent
646a8756a9
commit
9bfffd85d7
|
@ -6,15 +6,19 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.event.service.internal;
|
package org.hibernate.event.service.internal;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.hibernate.event.service.spi.DuplicationStrategy;
|
import org.hibernate.event.service.spi.DuplicationStrategy;
|
||||||
|
import org.hibernate.event.service.spi.EventActionWithParameter;
|
||||||
import org.hibernate.event.service.spi.EventListenerGroup;
|
import org.hibernate.event.service.spi.EventListenerGroup;
|
||||||
import org.hibernate.event.service.spi.EventListenerRegistrationException;
|
import org.hibernate.event.service.spi.EventListenerRegistrationException;
|
||||||
import org.hibernate.event.service.spi.JpaBootstrapSensitive;
|
import org.hibernate.event.service.spi.JpaBootstrapSensitive;
|
||||||
|
@ -23,6 +27,7 @@ import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
* @author Sanne Grinovero
|
||||||
*/
|
*/
|
||||||
class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
private EventType<T> eventType;
|
private EventType<T> eventType;
|
||||||
|
@ -30,9 +35,14 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
|
|
||||||
private final Set<DuplicationStrategy> duplicationStrategies = new LinkedHashSet<>();
|
private final Set<DuplicationStrategy> duplicationStrategies = new LinkedHashSet<>();
|
||||||
|
|
||||||
// Performance: make sure a forEach iteration on this type is efficient; in particular we choose ArrayList
|
// Performance: make sure iteration on this type is efficient; in particular we do not want to allocate iterators,
|
||||||
// so to avoid allocating iterators.
|
// not having to capture state in lambdas.
|
||||||
private ArrayList<T> listeners;
|
// So we keep the listeners in both a List (for convenience) and in an array (for iteration). Make sure
|
||||||
|
// their content stays in synch!
|
||||||
|
private T[] listeners = null;
|
||||||
|
|
||||||
|
//Update both fields when making changes!
|
||||||
|
private List<T> listenersAsList;
|
||||||
|
|
||||||
public EventListenerGroupImpl(EventType<T> eventType, EventListenerRegistryImpl listenerRegistry) {
|
public EventListenerGroupImpl(EventType<T> eventType, EventListenerRegistryImpl listenerRegistry) {
|
||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
|
@ -66,7 +76,8 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int count() {
|
public int count() {
|
||||||
return listeners == null ? 0 : listeners.size();
|
final T[] ls = listeners;
|
||||||
|
return ls == null ? 0 : ls.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,16 +85,16 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
if ( duplicationStrategies != null ) {
|
if ( duplicationStrategies != null ) {
|
||||||
duplicationStrategies.clear();
|
duplicationStrategies.clear();
|
||||||
}
|
}
|
||||||
if ( listeners != null ) {
|
listeners = null;
|
||||||
listeners.clear();
|
listenersAsList = null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final <U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent) {
|
public final <U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent) {
|
||||||
if ( listeners != null && listeners.size() != 0 ) {
|
final T[] ls = listeners;
|
||||||
|
if ( ls != null && ls.length != 0 ) {
|
||||||
final U event = eventSupplier.get();
|
final U event = eventSupplier.get();
|
||||||
for ( T listener : this.listeners ) {
|
for ( T listener : ls ) {
|
||||||
actionOnEvent.accept( listener, event );
|
actionOnEvent.accept( listener, event );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,13 +102,24 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final <U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent) {
|
public final <U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent) {
|
||||||
if ( listeners != null ) {
|
final T[] ls = listeners;
|
||||||
for ( T listener : this.listeners ) {
|
if ( ls != null ) {
|
||||||
|
for ( T listener : ls ) {
|
||||||
actionOnEvent.accept( listener, event );
|
actionOnEvent.accept( listener, event );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U,X> void fireEventOnEachListener(final U event, final X parameter, final EventActionWithParameter<T, U, X> actionOnEvent) {
|
||||||
|
final T[] ls = listeners;
|
||||||
|
if ( ls != null ) {
|
||||||
|
for ( T listener : ls ) {
|
||||||
|
actionOnEvent.applyEventToListener( listener, event, parameter );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addDuplicationStrategy(DuplicationStrategy strategy) {
|
public void addDuplicationStrategy(DuplicationStrategy strategy) {
|
||||||
duplicationStrategies.add( strategy );
|
duplicationStrategies.add( strategy );
|
||||||
|
@ -105,22 +127,44 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation note: should be final for performance reasons.
|
* Implementation note: should be final for performance reasons.
|
||||||
|
* @deprecated this is not the most efficient way for iterating the event listeners.
|
||||||
|
* See {@link #fireEventOnEachListener(Object, BiConsumer)} and co. for better alternatives.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public final Iterable<T> listeners() {
|
public final Iterable<T> listeners() {
|
||||||
return listeners == null ? Collections.EMPTY_LIST : listeners;
|
final List<T> ls = listenersAsList;
|
||||||
|
return ls == null ? Collections.EMPTY_LIST : ls;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final void appendListeners(T... listeners) {
|
public final void appendListeners(T... listeners) {
|
||||||
|
internalAppendListeners( listeners );
|
||||||
|
checkForArrayRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForArrayRefresh() {
|
||||||
|
final List<T> list = listenersAsList;
|
||||||
|
if ( this.listeners == null ) {
|
||||||
|
T[] a = (T[]) Array.newInstance( eventType.baseListenerInterface(), list.size() );
|
||||||
|
listeners = list.<T>toArray( a );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalAppendListeners(T[] listeners) {
|
||||||
for ( T listener : listeners ) {
|
for ( T listener : listeners ) {
|
||||||
appendListener( listener );
|
internalAppendListener( listener );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void appendListener(T listener) {
|
public void appendListener(T listener) {
|
||||||
|
internalAppendListener( listener );
|
||||||
|
checkForArrayRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalAppendListener(T listener) {
|
||||||
if ( listenerShouldGetAdded( listener ) ) {
|
if ( listenerShouldGetAdded( listener ) ) {
|
||||||
internalAppend( listener );
|
internalAppend( listener );
|
||||||
}
|
}
|
||||||
|
@ -129,28 +173,39 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
@Override
|
@Override
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final void prependListeners(T... listeners) {
|
public final void prependListeners(T... listeners) {
|
||||||
|
internalPrependListeners( listeners );
|
||||||
|
checkForArrayRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalPrependListeners(T[] listeners) {
|
||||||
for ( T listener : listeners ) {
|
for ( T listener : listeners ) {
|
||||||
prependListener( listener );
|
internalPreprendListener( listener );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prependListener(T listener) {
|
public void prependListener(T listener) {
|
||||||
|
internalPreprendListener( listener );
|
||||||
|
checkForArrayRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalPreprendListener(T listener) {
|
||||||
if ( listenerShouldGetAdded( listener ) ) {
|
if ( listenerShouldGetAdded( listener ) ) {
|
||||||
internalPrepend( listener );
|
internalPrepend( listener );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean listenerShouldGetAdded(T listener) {
|
private boolean listenerShouldGetAdded(T listener) {
|
||||||
if ( listeners == null ) {
|
final List<T> ts = listenersAsList;
|
||||||
listeners = new ArrayList<>();
|
if ( ts == null ) {
|
||||||
|
listenersAsList = new ArrayList<>();
|
||||||
return true;
|
return true;
|
||||||
// no need to do de-dup checks
|
// no need to do de-dup checks
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean doAdd = true;
|
boolean doAdd = true;
|
||||||
strategy_loop: for ( DuplicationStrategy strategy : duplicationStrategies ) {
|
strategy_loop: for ( DuplicationStrategy strategy : duplicationStrategies ) {
|
||||||
final ListIterator<T> itr = listeners.listIterator();
|
final ListIterator<T> itr = ts.listIterator();
|
||||||
while ( itr.hasNext() ) {
|
while ( itr.hasNext() ) {
|
||||||
final T existingListener = itr.next();
|
final T existingListener = itr.next();
|
||||||
if ( strategy.areMatch( listener, existingListener ) ) {
|
if ( strategy.areMatch( listener, existingListener ) ) {
|
||||||
|
@ -180,7 +235,8 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
private void internalPrepend(T listener) {
|
private void internalPrepend(T listener) {
|
||||||
checkAgainstBaseInterface( listener );
|
checkAgainstBaseInterface( listener );
|
||||||
performInjections( listener );
|
performInjections( listener );
|
||||||
listeners.add( 0, listener );
|
listenersAsList.add( 0, listener );
|
||||||
|
listeners = null; //Marks it for refreshing
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performInjections(T listener) {
|
private void performInjections(T listener) {
|
||||||
|
@ -206,6 +262,7 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||||
private void internalAppend(T listener) {
|
private void internalAppend(T listener) {
|
||||||
checkAgainstBaseInterface( listener );
|
checkAgainstBaseInterface( listener );
|
||||||
performInjections( listener );
|
performInjections( listener );
|
||||||
listeners.add( listener );
|
listenersAsList.add( listener );
|
||||||
|
listeners = null; //Marks it for refreshing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* 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.event.service.spi;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
|
||||||
|
@Incubating
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface EventActionWithParameter<T, U, X> {
|
||||||
|
|
||||||
|
void applyEventToListener(T eventListener, U action, X param);
|
||||||
|
|
||||||
|
}
|
|
@ -7,9 +7,11 @@
|
||||||
package org.hibernate.event.service.spi;
|
package org.hibernate.event.service.spi;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
import org.hibernate.event.spi.EventType;
|
import org.hibernate.event.spi.EventType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +37,12 @@ public interface EventListenerGroup<T> extends Serializable {
|
||||||
|
|
||||||
public int count();
|
public int count();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated this is not the most efficient way for iterating the event listeners.
|
||||||
|
* See {@link #fireEventOnEachListener(Object, BiConsumer)} and its overloaded variants for better alternatives.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public Iterable<T> listeners();
|
public Iterable<T> listeners();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,6 +75,7 @@ public interface EventListenerGroup<T> extends Serializable {
|
||||||
* @param actionOnEvent
|
* @param actionOnEvent
|
||||||
* @param <U> the kind of event
|
* @param <U> the kind of event
|
||||||
*/
|
*/
|
||||||
|
@Incubating
|
||||||
<U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent);
|
<U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,6 +85,10 @@ public interface EventListenerGroup<T> extends Serializable {
|
||||||
* @param actionOnEvent
|
* @param actionOnEvent
|
||||||
* @param <U> the kind of event
|
* @param <U> the kind of event
|
||||||
*/
|
*/
|
||||||
|
@Incubating
|
||||||
<U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent);
|
<U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent);
|
||||||
|
|
||||||
|
@Incubating
|
||||||
|
<U,X> void fireEventOnEachListener(final U event, X param, final EventActionWithParameter<T,U,X> actionOnEvent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -724,7 +724,7 @@ public final class SessionImpl
|
||||||
try {
|
try {
|
||||||
//Uses a capturing lambda in this case as we need to carry the additional Map parameter:
|
//Uses a capturing lambda in this case as we need to carry the additional Map parameter:
|
||||||
fastSessionServices.eventListenerGroup_PERSIST
|
fastSessionServices.eventListenerGroup_PERSIST
|
||||||
.fireEventOnEachListener( event, (l, e) -> l.onPersist( e, copiedAlready ) );
|
.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist );
|
||||||
}
|
}
|
||||||
catch ( MappingException e ) {
|
catch ( MappingException e ) {
|
||||||
throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ;
|
throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ;
|
||||||
|
@ -745,7 +745,7 @@ public final class SessionImpl
|
||||||
checkOpenOrWaitingForAutoClose();
|
checkOpenOrWaitingForAutoClose();
|
||||||
pulseTransactionCoordinator();
|
pulseTransactionCoordinator();
|
||||||
PersistEvent event = new PersistEvent( entityName, object, this );
|
PersistEvent event = new PersistEvent( entityName, object, this );
|
||||||
fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, (l,e) -> l.onPersist( e, copiedAlready ) );
|
fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist );
|
||||||
delayedAfterCompletion();
|
delayedAfterCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,7 +793,7 @@ public final class SessionImpl
|
||||||
private void fireMerge(final Map copiedAlready, final MergeEvent event) {
|
private void fireMerge(final Map copiedAlready, final MergeEvent event) {
|
||||||
try {
|
try {
|
||||||
pulseTransactionCoordinator();
|
pulseTransactionCoordinator();
|
||||||
fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, (l,e) -> l.onMerge( e, copiedAlready ) );
|
fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, copiedAlready, MergeEventListener::onMerge );
|
||||||
}
|
}
|
||||||
catch ( ObjectDeletedException sse ) {
|
catch ( ObjectDeletedException sse ) {
|
||||||
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
|
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
|
||||||
|
@ -904,7 +904,7 @@ public final class SessionImpl
|
||||||
private void fireDelete(final DeleteEvent event, final Set transientEntities) {
|
private void fireDelete(final DeleteEvent event, final Set transientEntities) {
|
||||||
try{
|
try{
|
||||||
pulseTransactionCoordinator();
|
pulseTransactionCoordinator();
|
||||||
fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, (l,e) -> l.onDelete( e, transientEntities ) );
|
fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, transientEntities, DeleteEventListener::onDelete );
|
||||||
}
|
}
|
||||||
catch ( ObjectDeletedException sse ) {
|
catch ( ObjectDeletedException sse ) {
|
||||||
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
|
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
|
||||||
|
@ -1179,7 +1179,7 @@ public final class SessionImpl
|
||||||
// it seems they prevent these hot methods from being inlined.
|
// it seems they prevent these hot methods from being inlined.
|
||||||
private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) {
|
private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) {
|
||||||
pulseTransactionCoordinator();
|
pulseTransactionCoordinator();
|
||||||
fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, (l,e) -> l.onLoad( e, loadType ) );
|
fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, loadType, LoadEventListener::onLoad );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireResolveNaturalId(final ResolveNaturalIdEvent event) {
|
private void fireResolveNaturalId(final ResolveNaturalIdEvent event) {
|
||||||
|
@ -1262,7 +1262,7 @@ public final class SessionImpl
|
||||||
private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) {
|
private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) {
|
||||||
try {
|
try {
|
||||||
pulseTransactionCoordinator();
|
pulseTransactionCoordinator();
|
||||||
fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, (l,e) -> l.onRefresh( e, refreshedAlready ) );
|
fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, refreshedAlready, RefreshEventListener::onRefresh );
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
throw getExceptionConverter().convert( e );
|
throw getExceptionConverter().convert( e );
|
||||||
|
|
Loading…
Reference in New Issue