HHH-15566 Improve efficiency of CallbackRegistryImpl

This commit is contained in:
Sanne Grinovero 2022-10-18 16:43:24 +01:00 committed by Sanne Grinovero
parent 24f75fb8e8
commit 08d1d9704b
9 changed files with 191 additions and 92 deletions

View File

@ -43,7 +43,7 @@ import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistrationException;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.jpa.event.internal.CallbackRegistryImplementor;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import static org.hibernate.event.spi.EventType.AUTO_FLUSH;
import static org.hibernate.event.spi.EventType.CLEAR;
@ -201,14 +201,14 @@ public class EventListenerRegistryImpl implements EventListenerRegistry {
// Builder
public static class Builder {
private final CallbackRegistryImplementor callbackRegistry;
private final CallbackRegistry callbackRegistry;
private final boolean jpaBootstrap;
private final Map<EventType<?>,EventListenerGroup<?>> listenerGroupMap = new TreeMap<>(
Comparator.comparing( EventType::ordinal )
);
public Builder(CallbackRegistryImplementor callbackRegistry, boolean jpaBootstrap) {
public Builder(CallbackRegistry callbackRegistry, boolean jpaBootstrap) {
this.callbackRegistry = callbackRegistry;
this.jpaBootstrap = jpaBootstrap;

View File

@ -20,8 +20,8 @@ import org.hibernate.event.service.internal.EventListenerRegistryImpl;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.event.internal.CallbackRegistryImplementor;
import org.hibernate.jpa.event.internal.CallbacksFactory;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.service.spi.Stoppable;
/**
@ -34,7 +34,7 @@ public class EventEngine {
private final Map<String,EventType<?>> registeredEventTypes;
private final EventListenerRegistry listenerRegistry;
private final CallbackRegistryImplementor callbackRegistry;
private final CallbackRegistry callbackRegistry;
public EventEngine(
MetadataImplementor mappings,
@ -151,7 +151,7 @@ public class EventEngine {
return listenerRegistry;
}
public CallbackRegistryImplementor getCallbackRegistry() {
public CallbackRegistry getCallbackRegistry() {
return callbackRegistry;
}

View File

@ -0,0 +1,66 @@
/*
* 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.internal.util.collections;
import java.util.Map;
/**
* For efficient lookup based on Class types as key,
* a ClassValue should be used; however it requires
* lazy association of values; this helper wraps
* a plain HashMap but optimises lookups via the ClassValue.
* N.B. there is a cost in memory and in terms of weak references,
* so let's use this only where proven that a simple Map lookup
* is otherwise too costly.
* @param <V> the type of the values stored in the Maps.
* @author Sanne Grinovero
* @since 6.2
*/
public final class MapBackedClassValue<V> {
private volatile Map<Class<?>, V> map;
private final ClassValue<V> classValue = new ClassValue<>() {
@Override
protected V computeValue(final Class<?> type) {
final Map<Class<?>, V> m = map;
if ( m == null ) {
throw new IllegalStateException( "This MapBackedClassValue has been disposed" );
}
else {
return map.get( type );
}
}
};
public MapBackedClassValue(final Map<Class<?>, V> map) {
//Defensive copy, and implicit null check.
//Choose the Map.copyOf implementation as it has a compact layout;
//it doesn't have great get() performance but it's acceptable since we're performing that at most
//once per key before caching it via the ClassValue.
this.map = Map.copyOf( map );
}
public V get(Class<?> key) {
return classValue.get( key );
}
/**
* Use this to wipe the backing map, but N.B.
* we won't be clearing the ClassValue: this is useful
* only to avoid classloader leaks since the Map
* may hold references to user classes.
* Since ClassValue is also possibly caching state,
* it might be possible to retrieve some values after this
* but shouldn't be relied on.
* ClassValue doesn't leak references to classes.
*/
public void dispose() {
this.map = null;
}
}

View File

@ -7,10 +7,14 @@
package org.hibernate.jpa.event.internal;
import java.util.HashMap;
import java.util.Map;
import jakarta.persistence.PersistenceException;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.MapBackedClassValue;
import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackType;
/**
@ -18,39 +22,41 @@ import org.hibernate.jpa.event.spi.CallbackType;
*
* @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
* @author Steve Ebersole
* @author Sanne Grinovero
*/
final class CallbackRegistryImpl implements CallbackRegistryImplementor {
private final HashMap<Class<?>, Callback[]> preCreates = new HashMap<>();
private final HashMap<Class<?>, Callback[]> postCreates = new HashMap<>();
private final HashMap<Class<?>, Callback[]> preRemoves = new HashMap<>();
private final HashMap<Class<?>, Callback[]> postRemoves = new HashMap<>();
private final HashMap<Class<?>, Callback[]> preUpdates = new HashMap<>();
private final HashMap<Class<?>, Callback[]> postUpdates = new HashMap<>();
private final HashMap<Class<?>, Callback[]> postLoads = new HashMap<>();
final class CallbackRegistryImpl implements CallbackRegistry {
private final MapBackedClassValue<Callback[]> preCreates;
private final MapBackedClassValue<Callback[]> postCreates;
private final MapBackedClassValue<Callback[]> preRemoves;
private final MapBackedClassValue<Callback[]> postRemoves;
private final MapBackedClassValue<Callback[]> preUpdates;
private final MapBackedClassValue<Callback[]> postUpdates;
private final MapBackedClassValue<Callback[]> postLoads;
public CallbackRegistryImpl(
Map<Class<?>, Callback[]> preCreates,
Map<Class<?>, Callback[]> postCreates,
Map<Class<?>, Callback[]> preRemoves,
Map<Class<?>, Callback[]> postRemoves,
Map<Class<?>, Callback[]> preUpdates,
Map<Class<?>, Callback[]> postUpdates,
Map<Class<?>, Callback[]> postLoads) {
this.preCreates = new MapBackedClassValue<>( preCreates );
this.postCreates = new MapBackedClassValue<>( postCreates );
this.preRemoves = new MapBackedClassValue<>( preRemoves );
this.postRemoves = new MapBackedClassValue<>( postRemoves );
this.preUpdates = new MapBackedClassValue<>( preUpdates );
this.postUpdates = new MapBackedClassValue<>( postUpdates );
this.postLoads = new MapBackedClassValue<>( postLoads );
}
@Override
public boolean hasRegisteredCallbacks(Class<?> entityClass, CallbackType callbackType) {
final HashMap<Class<?>, Callback[]> map = determineAppropriateCallbackMap( callbackType );
final MapBackedClassValue<Callback[]> map = determineAppropriateCallbackMap( callbackType );
return notEmpty( map.get( entityClass ) );
}
@Override
public void registerCallbacks(Class<?> entityClass, Callback[] callbacks) {
if ( callbacks == null || callbacks.length == 0 ) {
return;
}
for ( Callback callback : callbacks ) {
final HashMap<Class<?>, Callback[]> map = determineAppropriateCallbackMap( callback.getCallbackType() );
Callback[] entityCallbacks = map.get( entityClass );
if ( entityCallbacks == null ) {
entityCallbacks = new Callback[0];
}
entityCallbacks = ArrayHelper.join( entityCallbacks, callback );
map.put( entityClass, entityCallbacks );
}
}
@Override
public void preCreate(Object bean) {
callback( preCreates.get( bean.getClass() ), bean );
@ -90,6 +96,17 @@ final class CallbackRegistryImpl implements CallbackRegistryImplementor {
return callback( postLoads.get( bean.getClass() ), bean );
}
@Override
public void release() {
this.preCreates.dispose();
this.postCreates.dispose();
this.preRemoves.dispose();
this.postRemoves.dispose();
this.preUpdates.dispose();
this.postUpdates.dispose();
this.postLoads.dispose();
}
private boolean callback(Callback[] callbacks, Object bean) {
if ( callbacks != null && callbacks.length != 0 ) {
for ( Callback callback : callbacks ) {
@ -102,7 +119,7 @@ final class CallbackRegistryImpl implements CallbackRegistryImplementor {
}
}
private HashMap<Class<?>, Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
private MapBackedClassValue<Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
if ( callbackType == CallbackType.PRE_PERSIST ) {
return preCreates;
}
@ -134,16 +151,66 @@ final class CallbackRegistryImpl implements CallbackRegistryImplementor {
throw new PersistenceException( "Unrecognized JPA callback type [" + callbackType + "]" );
}
public void release() {
preCreates.clear();
postCreates.clear();
public static class Builder {
private final Map<Class<?>, Callback[]> preCreates = new HashMap<>();
private final Map<Class<?>, Callback[]> postCreates = new HashMap<>();
private final Map<Class<?>, Callback[]> preRemoves = new HashMap<>();
private final Map<Class<?>, Callback[]> postRemoves = new HashMap<>();
private final Map<Class<?>, Callback[]> preUpdates = new HashMap<>();
private final Map<Class<?>, Callback[]> postUpdates = new HashMap<>();
private final Map<Class<?>, Callback[]> postLoads = new HashMap<>();
preRemoves.clear();
postRemoves.clear();
public void registerCallbacks(Class<?> entityClass, Callback[] callbacks) {
if ( callbacks == null || callbacks.length == 0 ) {
return;
}
for ( Callback callback : callbacks ) {
final Map<Class<?>, Callback[]> map = determineAppropriateCallbackMap( callback.getCallbackType() );
Callback[] entityCallbacks = map.get( entityClass );
if ( entityCallbacks == null ) {
entityCallbacks = new Callback[0];
}
entityCallbacks = ArrayHelper.join( entityCallbacks, callback );
map.put( entityClass, entityCallbacks );
}
}
private Map<Class<?>, Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
if ( callbackType == CallbackType.PRE_PERSIST ) {
return preCreates;
}
if ( callbackType == CallbackType.POST_PERSIST ) {
return postCreates;
}
if ( callbackType == CallbackType.PRE_REMOVE ) {
return preRemoves;
}
if ( callbackType == CallbackType.POST_REMOVE ) {
return postRemoves;
}
if ( callbackType == CallbackType.PRE_UPDATE ) {
return preUpdates;
}
if ( callbackType == CallbackType.POST_UPDATE ) {
return postUpdates;
}
if ( callbackType == CallbackType.POST_LOAD ) {
return postLoads;
}
throw new PersistenceException( "Unrecognized JPA callback type [" + callbackType + "]" );
}
protected CallbackRegistryImpl build() {
return new CallbackRegistryImpl( preCreates, postCreates, preRemoves, postRemoves, preUpdates, postUpdates, postLoads );
}
preUpdates.clear();
postUpdates.clear();
postLoads.clear();
}
}

View File

@ -1,15 +0,0 @@
/*
* 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.jpa.event.internal;
import org.hibernate.jpa.event.spi.CallbackRegistrar;
public interface CallbackRegistryImplementor extends CallbackRegistrar {
void release();
}

View File

@ -9,13 +9,13 @@ package org.hibernate.jpa.event.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackDefinition;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
@ -31,13 +31,12 @@ import org.jboss.logging.Logger;
public final class CallbacksFactory {
private static final Logger log = Logger.getLogger( CallbacksFactory.class );
public static CallbackRegistryImplementor buildCallbackRegistry(SessionFactoryOptions options,
ServiceRegistry serviceRegistry, Collection<PersistentClass> entityBindings) {
public static CallbackRegistry buildCallbackRegistry(SessionFactoryOptions options, ServiceRegistry serviceRegistry, Collection<PersistentClass> entityBindings) {
if ( !jpaCallBacksEnabled( options ) ) {
return new EmptyCallbackRegistryImpl();
}
ManagedBeanRegistry beanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class );
CallbackRegistryImplementor registry = new CallbackRegistryImpl();
CallbackRegistryImpl.Builder registryBuilder = new CallbackRegistryImpl.Builder();
Set<Class<?>> entityClasses = new HashSet<>();
for ( PersistentClass persistentClass : entityBindings ) {
@ -63,16 +62,16 @@ public final class CallbacksFactory {
continue;
}
registry.registerCallbacks( persistentClass.getMappedClass(),
registryBuilder.registerCallbacks( persistentClass.getMappedClass(),
buildCallbacks( persistentClass.getCallbackDefinitions(), beanRegistry ) );
for ( Property property : persistentClass.getDeclaredProperties() ) {
registry.registerCallbacks( persistentClass.getMappedClass(),
registryBuilder.registerCallbacks( persistentClass.getMappedClass(),
buildCallbacks( property.getCallbackDefinitions(), beanRegistry ) );
}
}
return registry;
return registryBuilder.build();
}
private static Callback[] buildCallbacks(List<CallbackDefinition> callbackDefinitions,

View File

@ -6,10 +6,10 @@
*/
package org.hibernate.jpa.event.internal;
import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackType;
final class EmptyCallbackRegistryImpl implements CallbackRegistryImplementor {
final class EmptyCallbackRegistryImpl implements CallbackRegistry {
@Override
public boolean hasRegisteredCallbacks(final Class<?> entityClass, final CallbackType callbackType) {
@ -56,9 +56,4 @@ final class EmptyCallbackRegistryImpl implements CallbackRegistryImplementor {
//no-op
}
@Override
public void registerCallbacks(Class<?> entityClass, Callback[] callbacks) {
//no-op
}
}

View File

@ -1,19 +0,0 @@
/*
* 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.jpa.event.spi;
public interface CallbackRegistrar extends CallbackRegistry {
/**
* Register the callback against the given entity.
*
* @param entityClass The entity Class to register the Callbacks against
* @param callbacks The Callbacks to register against the given entity Class
*/
void registerCallbacks(Class<?> entityClass, Callback[] callbacks);
}

View File

@ -6,14 +6,12 @@
*/
package org.hibernate.jpa.event.spi;
import java.io.Serializable;
/**
* Registry of Callbacks by entity and type
*
* @author Steve Ebersole
*/
public interface CallbackRegistry extends Serializable {
public interface CallbackRegistry {
/**
* Do we have any registered callbacks of the given type for the given entity?
*
@ -35,4 +33,12 @@ public interface CallbackRegistry extends Serializable {
void postRemove(Object entity);
boolean postLoad(Object entity);
/**
* Signals that the CallbackRegistry will no longer be used.
* In particular it is important to release references to class types
* to avoid classloader leaks.
*/
void release();
}