Skip to content

Commit 08d1d97

Browse files
committed
HHH-15566 Improve efficiency of CallbackRegistryImpl
1 parent 24f75fb commit 08d1d97

File tree

9 files changed

+191
-92
lines changed

9 files changed

+191
-92
lines changed

hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
import org.hibernate.event.service.spi.EventListenerRegistrationException;
4444
import org.hibernate.event.service.spi.EventListenerRegistry;
4545
import org.hibernate.event.spi.EventType;
46-
import org.hibernate.jpa.event.internal.CallbackRegistryImplementor;
46+
import org.hibernate.jpa.event.spi.CallbackRegistry;
4747

4848
import static org.hibernate.event.spi.EventType.AUTO_FLUSH;
4949
import static org.hibernate.event.spi.EventType.CLEAR;
@@ -201,14 +201,14 @@ public final <T> void prependListeners(EventType<T> type, T... listeners) {
201201
// Builder
202202

203203
public static class Builder {
204-
private final CallbackRegistryImplementor callbackRegistry;
204+
private final CallbackRegistry callbackRegistry;
205205
private final boolean jpaBootstrap;
206206

207207
private final Map<EventType<?>,EventListenerGroup<?>> listenerGroupMap = new TreeMap<>(
208208
Comparator.comparing( EventType::ordinal )
209209
);
210210

211-
public Builder(CallbackRegistryImplementor callbackRegistry, boolean jpaBootstrap) {
211+
public Builder(CallbackRegistry callbackRegistry, boolean jpaBootstrap) {
212212
this.callbackRegistry = callbackRegistry;
213213
this.jpaBootstrap = jpaBootstrap;
214214

hibernate-core/src/main/java/org/hibernate/event/spi/EventEngine.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
import org.hibernate.event.service.spi.EventListenerGroup;
2121
import org.hibernate.event.service.spi.EventListenerRegistry;
2222
import org.hibernate.internal.util.collections.CollectionHelper;
23-
import org.hibernate.jpa.event.internal.CallbackRegistryImplementor;
2423
import org.hibernate.jpa.event.internal.CallbacksFactory;
24+
import org.hibernate.jpa.event.spi.CallbackRegistry;
2525
import org.hibernate.service.spi.Stoppable;
2626

2727
/**
@@ -34,7 +34,7 @@ public class EventEngine {
3434
private final Map<String,EventType<?>> registeredEventTypes;
3535
private final EventListenerRegistry listenerRegistry;
3636

37-
private final CallbackRegistryImplementor callbackRegistry;
37+
private final CallbackRegistry callbackRegistry;
3838

3939
public EventEngine(
4040
MetadataImplementor mappings,
@@ -151,7 +151,7 @@ public EventListenerRegistry getListenerRegistry() {
151151
return listenerRegistry;
152152
}
153153

154-
public CallbackRegistryImplementor getCallbackRegistry() {
154+
public CallbackRegistry getCallbackRegistry() {
155155
return callbackRegistry;
156156
}
157157

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.internal.util.collections;
8+
9+
import java.util.Map;
10+
11+
/**
12+
* For efficient lookup based on Class types as key,
13+
* a ClassValue should be used; however it requires
14+
* lazy association of values; this helper wraps
15+
* a plain HashMap but optimises lookups via the ClassValue.
16+
* N.B. there is a cost in memory and in terms of weak references,
17+
* so let's use this only where proven that a simple Map lookup
18+
* is otherwise too costly.
19+
* @param <V> the type of the values stored in the Maps.
20+
* @author Sanne Grinovero
21+
* @since 6.2
22+
*/
23+
public final class MapBackedClassValue<V> {
24+
25+
private volatile Map<Class<?>, V> map;
26+
27+
private final ClassValue<V> classValue = new ClassValue<>() {
28+
@Override
29+
protected V computeValue(final Class<?> type) {
30+
final Map<Class<?>, V> m = map;
31+
if ( m == null ) {
32+
throw new IllegalStateException( "This MapBackedClassValue has been disposed" );
33+
}
34+
else {
35+
return map.get( type );
36+
}
37+
}
38+
};
39+
40+
public MapBackedClassValue(final Map<Class<?>, V> map) {
41+
//Defensive copy, and implicit null check.
42+
//Choose the Map.copyOf implementation as it has a compact layout;
43+
//it doesn't have great get() performance but it's acceptable since we're performing that at most
44+
//once per key before caching it via the ClassValue.
45+
this.map = Map.copyOf( map );
46+
}
47+
48+
public V get(Class<?> key) {
49+
return classValue.get( key );
50+
}
51+
52+
/**
53+
* Use this to wipe the backing map, but N.B.
54+
* we won't be clearing the ClassValue: this is useful
55+
* only to avoid classloader leaks since the Map
56+
* may hold references to user classes.
57+
* Since ClassValue is also possibly caching state,
58+
* it might be possible to retrieve some values after this
59+
* but shouldn't be relied on.
60+
* ClassValue doesn't leak references to classes.
61+
*/
62+
public void dispose() {
63+
this.map = null;
64+
}
65+
66+
}

hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java

Lines changed: 102 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,56 @@
77
package org.hibernate.jpa.event.internal;
88

99
import java.util.HashMap;
10+
import java.util.Map;
11+
1012
import jakarta.persistence.PersistenceException;
1113

1214
import org.hibernate.internal.util.collections.ArrayHelper;
15+
import org.hibernate.internal.util.collections.MapBackedClassValue;
1316
import org.hibernate.jpa.event.spi.Callback;
17+
import org.hibernate.jpa.event.spi.CallbackRegistry;
1418
import org.hibernate.jpa.event.spi.CallbackType;
1519

1620
/**
1721
* Keep track of all lifecycle callbacks and listeners for a given persistence unit
1822
*
1923
* @author <a href="mailto:[email protected]">Kabir Khan</a>
2024
* @author Steve Ebersole
25+
* @author Sanne Grinovero
2126
*/
22-
final class CallbackRegistryImpl implements CallbackRegistryImplementor {
23-
private final HashMap<Class<?>, Callback[]> preCreates = new HashMap<>();
24-
private final HashMap<Class<?>, Callback[]> postCreates = new HashMap<>();
25-
private final HashMap<Class<?>, Callback[]> preRemoves = new HashMap<>();
26-
private final HashMap<Class<?>, Callback[]> postRemoves = new HashMap<>();
27-
private final HashMap<Class<?>, Callback[]> preUpdates = new HashMap<>();
28-
private final HashMap<Class<?>, Callback[]> postUpdates = new HashMap<>();
29-
private final HashMap<Class<?>, Callback[]> postLoads = new HashMap<>();
27+
final class CallbackRegistryImpl implements CallbackRegistry {
28+
29+
private final MapBackedClassValue<Callback[]> preCreates;
30+
private final MapBackedClassValue<Callback[]> postCreates;
31+
private final MapBackedClassValue<Callback[]> preRemoves;
32+
private final MapBackedClassValue<Callback[]> postRemoves;
33+
private final MapBackedClassValue<Callback[]> preUpdates;
34+
private final MapBackedClassValue<Callback[]> postUpdates;
35+
private final MapBackedClassValue<Callback[]> postLoads;
36+
37+
public CallbackRegistryImpl(
38+
Map<Class<?>, Callback[]> preCreates,
39+
Map<Class<?>, Callback[]> postCreates,
40+
Map<Class<?>, Callback[]> preRemoves,
41+
Map<Class<?>, Callback[]> postRemoves,
42+
Map<Class<?>, Callback[]> preUpdates,
43+
Map<Class<?>, Callback[]> postUpdates,
44+
Map<Class<?>, Callback[]> postLoads) {
45+
this.preCreates = new MapBackedClassValue<>( preCreates );
46+
this.postCreates = new MapBackedClassValue<>( postCreates );
47+
this.preRemoves = new MapBackedClassValue<>( preRemoves );
48+
this.postRemoves = new MapBackedClassValue<>( postRemoves );
49+
this.preUpdates = new MapBackedClassValue<>( preUpdates );
50+
this.postUpdates = new MapBackedClassValue<>( postUpdates );
51+
this.postLoads = new MapBackedClassValue<>( postLoads );
52+
}
3053

3154
@Override
3255
public boolean hasRegisteredCallbacks(Class<?> entityClass, CallbackType callbackType) {
33-
final HashMap<Class<?>, Callback[]> map = determineAppropriateCallbackMap( callbackType );
56+
final MapBackedClassValue<Callback[]> map = determineAppropriateCallbackMap( callbackType );
3457
return notEmpty( map.get( entityClass ) );
3558
}
3659

37-
@Override
38-
public void registerCallbacks(Class<?> entityClass, Callback[] callbacks) {
39-
if ( callbacks == null || callbacks.length == 0 ) {
40-
return;
41-
}
42-
43-
for ( Callback callback : callbacks ) {
44-
final HashMap<Class<?>, Callback[]> map = determineAppropriateCallbackMap( callback.getCallbackType() );
45-
Callback[] entityCallbacks = map.get( entityClass );
46-
if ( entityCallbacks == null ) {
47-
entityCallbacks = new Callback[0];
48-
}
49-
entityCallbacks = ArrayHelper.join( entityCallbacks, callback );
50-
map.put( entityClass, entityCallbacks );
51-
}
52-
}
53-
5460
@Override
5561
public void preCreate(Object bean) {
5662
callback( preCreates.get( bean.getClass() ), bean );
@@ -90,6 +96,17 @@ public boolean postLoad(Object bean) {
9096
return callback( postLoads.get( bean.getClass() ), bean );
9197
}
9298

99+
@Override
100+
public void release() {
101+
this.preCreates.dispose();
102+
this.postCreates.dispose();
103+
this.preRemoves.dispose();
104+
this.postRemoves.dispose();
105+
this.preUpdates.dispose();
106+
this.postUpdates.dispose();
107+
this.postLoads.dispose();
108+
}
109+
93110
private boolean callback(Callback[] callbacks, Object bean) {
94111
if ( callbacks != null && callbacks.length != 0 ) {
95112
for ( Callback callback : callbacks ) {
@@ -102,7 +119,7 @@ private boolean callback(Callback[] callbacks, Object bean) {
102119
}
103120
}
104121

105-
private HashMap<Class<?>, Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
122+
private MapBackedClassValue<Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
106123
if ( callbackType == CallbackType.PRE_PERSIST ) {
107124
return preCreates;
108125
}
@@ -134,16 +151,66 @@ private HashMap<Class<?>, Callback[]> determineAppropriateCallbackMap(CallbackTy
134151
throw new PersistenceException( "Unrecognized JPA callback type [" + callbackType + "]" );
135152
}
136153

137-
public void release() {
138-
preCreates.clear();
139-
postCreates.clear();
154+
public static class Builder {
155+
private final Map<Class<?>, Callback[]> preCreates = new HashMap<>();
156+
private final Map<Class<?>, Callback[]> postCreates = new HashMap<>();
157+
private final Map<Class<?>, Callback[]> preRemoves = new HashMap<>();
158+
private final Map<Class<?>, Callback[]> postRemoves = new HashMap<>();
159+
private final Map<Class<?>, Callback[]> preUpdates = new HashMap<>();
160+
private final Map<Class<?>, Callback[]> postUpdates = new HashMap<>();
161+
private final Map<Class<?>, Callback[]> postLoads = new HashMap<>();
162+
163+
public void registerCallbacks(Class<?> entityClass, Callback[] callbacks) {
164+
if ( callbacks == null || callbacks.length == 0 ) {
165+
return;
166+
}
140167

141-
preRemoves.clear();
142-
postRemoves.clear();
168+
for ( Callback callback : callbacks ) {
169+
final Map<Class<?>, Callback[]> map = determineAppropriateCallbackMap( callback.getCallbackType() );
170+
Callback[] entityCallbacks = map.get( entityClass );
171+
if ( entityCallbacks == null ) {
172+
entityCallbacks = new Callback[0];
173+
}
174+
entityCallbacks = ArrayHelper.join( entityCallbacks, callback );
175+
map.put( entityClass, entityCallbacks );
176+
}
177+
}
143178

144-
preUpdates.clear();
145-
postUpdates.clear();
179+
private Map<Class<?>, Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
180+
if ( callbackType == CallbackType.PRE_PERSIST ) {
181+
return preCreates;
182+
}
183+
184+
if ( callbackType == CallbackType.POST_PERSIST ) {
185+
return postCreates;
186+
}
187+
188+
if ( callbackType == CallbackType.PRE_REMOVE ) {
189+
return preRemoves;
190+
}
191+
192+
if ( callbackType == CallbackType.POST_REMOVE ) {
193+
return postRemoves;
194+
}
195+
196+
if ( callbackType == CallbackType.PRE_UPDATE ) {
197+
return preUpdates;
198+
}
199+
200+
if ( callbackType == CallbackType.POST_UPDATE ) {
201+
return postUpdates;
202+
}
203+
204+
if ( callbackType == CallbackType.POST_LOAD ) {
205+
return postLoads;
206+
}
207+
208+
throw new PersistenceException( "Unrecognized JPA callback type [" + callbackType + "]" );
209+
}
210+
211+
protected CallbackRegistryImpl build() {
212+
return new CallbackRegistryImpl( preCreates, postCreates, preRemoves, postRemoves, preUpdates, postUpdates, postLoads );
213+
}
146214

147-
postLoads.clear();
148215
}
149216
}

hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImplementor.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbacksFactory.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
import java.util.ArrayList;
1010
import java.util.Collection;
1111
import java.util.HashSet;
12-
import java.util.Iterator;
1312
import java.util.List;
1413
import java.util.Set;
1514

1615
import org.hibernate.boot.spi.SessionFactoryOptions;
1716
import org.hibernate.jpa.event.spi.Callback;
1817
import org.hibernate.jpa.event.spi.CallbackDefinition;
18+
import org.hibernate.jpa.event.spi.CallbackRegistry;
1919
import org.hibernate.mapping.PersistentClass;
2020
import org.hibernate.mapping.Property;
2121
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
@@ -31,13 +31,12 @@
3131
public final class CallbacksFactory {
3232
private static final Logger log = Logger.getLogger( CallbacksFactory.class );
3333

34-
public static CallbackRegistryImplementor buildCallbackRegistry(SessionFactoryOptions options,
35-
ServiceRegistry serviceRegistry, Collection<PersistentClass> entityBindings) {
34+
public static CallbackRegistry buildCallbackRegistry(SessionFactoryOptions options, ServiceRegistry serviceRegistry, Collection<PersistentClass> entityBindings) {
3635
if ( !jpaCallBacksEnabled( options ) ) {
3736
return new EmptyCallbackRegistryImpl();
3837
}
3938
ManagedBeanRegistry beanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class );
40-
CallbackRegistryImplementor registry = new CallbackRegistryImpl();
39+
CallbackRegistryImpl.Builder registryBuilder = new CallbackRegistryImpl.Builder();
4140
Set<Class<?>> entityClasses = new HashSet<>();
4241

4342
for ( PersistentClass persistentClass : entityBindings ) {
@@ -63,16 +62,16 @@ public static CallbackRegistryImplementor buildCallbackRegistry(SessionFactoryOp
6362
continue;
6463
}
6564

66-
registry.registerCallbacks( persistentClass.getMappedClass(),
65+
registryBuilder.registerCallbacks( persistentClass.getMappedClass(),
6766
buildCallbacks( persistentClass.getCallbackDefinitions(), beanRegistry ) );
6867

6968
for ( Property property : persistentClass.getDeclaredProperties() ) {
70-
registry.registerCallbacks( persistentClass.getMappedClass(),
69+
registryBuilder.registerCallbacks( persistentClass.getMappedClass(),
7170
buildCallbacks( property.getCallbackDefinitions(), beanRegistry ) );
7271
}
7372
}
7473

75-
return registry;
74+
return registryBuilder.build();
7675
}
7776

7877
private static Callback[] buildCallbacks(List<CallbackDefinition> callbackDefinitions,

hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackRegistryImpl.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
*/
77
package org.hibernate.jpa.event.internal;
88

9-
import org.hibernate.jpa.event.spi.Callback;
9+
import org.hibernate.jpa.event.spi.CallbackRegistry;
1010
import org.hibernate.jpa.event.spi.CallbackType;
1111

12-
final class EmptyCallbackRegistryImpl implements CallbackRegistryImplementor {
12+
final class EmptyCallbackRegistryImpl implements CallbackRegistry {
1313

1414
@Override
1515
public boolean hasRegisteredCallbacks(final Class<?> entityClass, final CallbackType callbackType) {
@@ -56,9 +56,4 @@ public void release() {
5656
//no-op
5757
}
5858

59-
@Override
60-
public void registerCallbacks(Class<?> entityClass, Callback[] callbacks) {
61-
//no-op
62-
}
63-
6459
}

0 commit comments

Comments
 (0)