Skip to content

Commit 123103a

Browse files
stalepsebersole
authored andcommitted
HHH-10289 - CPU performance regression in StatefulPersistenceContext.addEntry()
(cherry picked from commit da4593d)
1 parent 189b379 commit 123103a

File tree

2 files changed

+168
-71
lines changed

2 files changed

+168
-71
lines changed

hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import org.hibernate.engine.spi.CollectionEntry;
4343
import org.hibernate.engine.spi.CollectionKey;
4444
import org.hibernate.engine.spi.EntityEntry;
45-
import org.hibernate.engine.spi.EntityEntryFactory;
4645
import org.hibernate.engine.spi.EntityKey;
4746
import org.hibernate.engine.spi.EntityUniqueKey;
4847
import org.hibernate.engine.spi.ManagedEntity;
@@ -489,13 +488,38 @@ public EntityEntry addEntry(
489488

490489
final EntityEntry e;
491490

492-
if( (entity instanceof ManagedEntity) && ((ManagedEntity) entity).$$_hibernate_getEntityEntry() != null && status == Status.READ_ONLY) {
493-
e = ((ManagedEntity) entity).$$_hibernate_getEntityEntry();
494-
e.setStatus( status );
491+
/*
492+
IMPORTANT!!!
493+
494+
The following instanceof checks and castings are intentional.
495+
496+
DO NOT REFACTOR to make calls through the EntityEntryFactory interface, which would result
497+
in polymorphic call sites which will severely impact performance.
498+
499+
When a virtual method is called via an interface the JVM needs to resolve which concrete
500+
implementation to call. This takes CPU cycles and is a performance penalty. It also prevents method
501+
in-ling which further degrades performance. Casting to an implementation and making a direct method call
502+
removes the virtual call, and allows the methods to be in-lined. In this critical code path, it has a very
503+
large impact on performance to make virtual method calls.
504+
*/
505+
if (persister.getEntityEntryFactory() instanceof MutableEntityEntryFactory) {
506+
//noinspection RedundantCast
507+
e = ( (MutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry(
508+
status,
509+
loadedState,
510+
rowId,
511+
id,
512+
version,
513+
lockMode,
514+
existsInDatabase,
515+
persister,
516+
disableVersionIncrement,
517+
this
518+
);
495519
}
496520
else {
497-
final EntityEntryFactory entityEntryFactory = persister.getEntityEntryFactory();
498-
e = entityEntryFactory.createEntityEntry(
521+
//noinspection RedundantCast
522+
e = ( (ImmutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry(
499523
status,
500524
loadedState,
501525
rowId,
@@ -511,12 +535,22 @@ public EntityEntry addEntry(
511535
}
512536

513537
entityEntryContext.addEntityEntry( entity, e );
514-
// entityEntries.put(entity, e);
515538

516539
setHasNonReadOnlyEnties( status );
517540
return e;
518541
}
519542

543+
public EntityEntry addReferenceEntry(
544+
final Object entity,
545+
final Status status) {
546+
547+
((ManagedEntity)entity).$$_hibernate_getEntityEntry().setStatus( status );
548+
entityEntryContext.addEntityEntry( entity, ((ManagedEntity)entity).$$_hibernate_getEntityEntry() );
549+
550+
setHasNonReadOnlyEnties( status );
551+
return ((ManagedEntity)entity).$$_hibernate_getEntityEntry();
552+
}
553+
520554
@Override
521555
public boolean containsCollection(PersistentCollection collection) {
522556
return collectionEntries.containsKey( collection );

hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java

Lines changed: 127 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020
import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl;
2121
import org.hibernate.cache.spi.entry.StandardCacheEntryImpl;
2222
import org.hibernate.engine.internal.CacheHelper;
23+
import org.hibernate.engine.internal.StatefulPersistenceContext;
2324
import org.hibernate.engine.internal.TwoPhaseLoad;
2425
import org.hibernate.engine.internal.Versioning;
2526
import org.hibernate.engine.spi.EntityEntry;
2627
import org.hibernate.engine.spi.EntityKey;
28+
import org.hibernate.engine.spi.ManagedEntity;
2729
import org.hibernate.engine.spi.PersistenceContext;
2830
import org.hibernate.engine.spi.SessionFactoryImplementor;
2931
import org.hibernate.engine.spi.SessionImplementor;
3032
import org.hibernate.engine.spi.Status;
33+
import org.hibernate.event.service.spi.EventListenerGroup;
3134
import org.hibernate.event.service.spi.EventListenerRegistry;
3235
import org.hibernate.event.spi.EventSource;
3336
import org.hibernate.event.spi.EventType;
@@ -603,19 +606,101 @@ protected Object loadFromSecondLevelCache(
603606
}
604607

605608
CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure( ce, factory );
606-
Object entity = convertCacheEntryToEntity( entry, event.getEntityId(), persister, event, entityKey );
607-
609+
final Object entity;
610+
if(entry.isReferenceEntry()) {
611+
if( event.getInstanceToLoad() != null ) {
612+
throw new HibernateException(
613+
String.format( "Attempt to load entity [%s] from cache using provided object instance, but cache " +
614+
"is storing references: "+ event.getEntityId()));
615+
}
616+
else {
617+
entity = convertCacheReferenceEntryToEntity( (ReferenceCacheEntryImpl) entry,
618+
event.getEntityId(), persister, event.getSession(), entityKey );
619+
}
620+
}
621+
else {
622+
entity = convertCacheEntryToEntity( entry, event.getEntityId(), persister, event, entityKey );
623+
}
624+
608625
if ( !persister.isInstance( entity ) ) {
609626
throw new WrongClassException(
610627
"loaded object was of wrong class " + entity.getClass(),
611628
event.getEntityId(),
612629
persister.getEntityName()
613630
);
614631
}
615-
632+
616633
return entity;
617634
}
618635

636+
private Object convertCacheReferenceEntryToEntity(
637+
ReferenceCacheEntryImpl referenceCacheEntry,
638+
Serializable entityId,
639+
EntityPersister persister,
640+
EventSource session,
641+
EntityKey entityKey) {
642+
final Object entity = referenceCacheEntry.getReference();
643+
644+
if ( entity == null ) {
645+
throw new IllegalStateException(
646+
"Reference cache entry contained null : " + entityId);
647+
}
648+
else {
649+
makeEntityCircularReferenceSafe(referenceCacheEntry, entityId, session, entity, entityKey);
650+
//PostLoad is needed for EJB3
651+
EventListenerGroup<PostLoadEventListener> evenListenerGroup = getEvenListenerGroup(session);
652+
653+
if(!evenListenerGroup.isEmpty()) {
654+
postLoad(session, evenListenerGroup.listeners(), entity, entityId, persister);
655+
}
656+
return entity;
657+
}
658+
}
659+
660+
private void postLoad(EventSource session, Iterable<PostLoadEventListener> listeners,
661+
Object entity, Serializable entityId, EntityPersister persister) {
662+
PostLoadEvent postLoadEvent = new PostLoadEvent(session)
663+
.setEntity(entity)
664+
.setId(entityId)
665+
.setPersister(persister);
666+
667+
for (PostLoadEventListener listener : listeners) {
668+
listener.onPostLoad(postLoadEvent);
669+
}
670+
}
671+
672+
private void makeEntityCircularReferenceSafe(ReferenceCacheEntryImpl referenceCacheEntry,
673+
Serializable entityId,
674+
EventSource session,
675+
Object entity,
676+
EntityKey entityKey) {
677+
678+
final EntityPersister subclassPersister = referenceCacheEntry.getSubclassPersister();
679+
// make it circular-reference safe
680+
final StatefulPersistenceContext statefulPersistenceContext = (StatefulPersistenceContext) session.getPersistenceContext();
681+
682+
if ( (entity instanceof ManagedEntity) ) {
683+
statefulPersistenceContext.addReferenceEntry(
684+
entity,
685+
Status.READ_ONLY
686+
);
687+
}
688+
else {
689+
TwoPhaseLoad.addUninitializedCachedEntity(
690+
entityKey,
691+
entity,
692+
subclassPersister,
693+
LockMode.NONE,
694+
referenceCacheEntry.areLazyPropertiesUnfetched(),
695+
referenceCacheEntry.getVersion(),
696+
session
697+
);
698+
}
699+
700+
subclassPersister.afterInitialize( entity, referenceCacheEntry.areLazyPropertiesUnfetched(), session );
701+
statefulPersistenceContext.initializeNonLazyCollections();
702+
}
703+
619704
private Object convertCacheEntryToEntity(
620705
CacheEntry entry,
621706
Serializable entityId,
@@ -636,38 +721,12 @@ private Object convertCacheEntryToEntity(
636721
}
637722

638723
final Object entity;
639-
if ( entry.isReferenceEntry() ) {
640-
final Object optionalObject = event.getInstanceToLoad();
641-
if ( optionalObject != null ) {
642-
throw new HibernateException(
643-
String.format(
644-
"Attempt to load entity [%s] from cache using provided object instance, but cache " +
645-
"is storing references",
646-
MessageHelper.infoString( persister, entityId, factory )
647-
)
648-
);
649-
}
650724

651-
ReferenceCacheEntryImpl referenceCacheEntry = (ReferenceCacheEntryImpl) entry;
652-
entity = referenceCacheEntry.getReference();
653-
if ( entity == null ) {
654-
throw new IllegalStateException(
655-
"Reference cache entry contained null : " + MessageHelper.infoString(
656-
persister,
657-
entityId,
658-
factory
659-
)
660-
);
661-
}
662-
subclassPersister = referenceCacheEntry.getSubclassPersister();
663-
}
664-
else {
665-
subclassPersister = factory.getEntityPersister( entry.getSubclass() );
666-
final Object optionalObject = event.getInstanceToLoad();
667-
entity = optionalObject == null
668-
? session.instantiate( subclassPersister, entityId )
669-
: optionalObject;
670-
}
725+
subclassPersister = factory.getEntityPersister( entry.getSubclass() );
726+
final Object optionalObject = event.getInstanceToLoad();
727+
entity = optionalObject == null
728+
? session.instantiate( subclassPersister, entityId )
729+
: optionalObject;
671730

672731
// make it circular-reference safe
673732
TwoPhaseLoad.addUninitializedCachedEntity(
@@ -684,38 +743,32 @@ private Object convertCacheEntryToEntity(
684743
final Object[] values;
685744
final Object version;
686745
final boolean isReadOnly;
687-
if ( entry.isReferenceEntry() ) {
688-
values = null;
689-
version = null;
690-
isReadOnly = true;
691-
}
692-
else {
693-
final Type[] types = subclassPersister.getPropertyTypes();
694-
// initializes the entity by (desired) side-effect
695-
values = ( (StandardCacheEntryImpl) entry ).assemble(
696-
entity, entityId, subclassPersister, session.getInterceptor(), session
746+
747+
final Type[] types = subclassPersister.getPropertyTypes();
748+
// initializes the entity by (desired) side-effect
749+
values = ( (StandardCacheEntryImpl) entry ).assemble(
750+
entity, entityId, subclassPersister, session.getInterceptor(), session
751+
);
752+
if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) {
753+
TypeHelper.deepCopy(
754+
values,
755+
types,
756+
subclassPersister.getPropertyUpdateability(),
757+
values,
758+
session
697759
);
698-
if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) {
699-
TypeHelper.deepCopy(
700-
values,
701-
types,
702-
subclassPersister.getPropertyUpdateability(),
703-
values,
704-
session
705-
);
706-
}
707-
version = Versioning.getVersion( values, subclassPersister );
708-
LOG.tracef( "Cached Version : %s", version );
760+
}
761+
version = Versioning.getVersion( values, subclassPersister );
762+
LOG.tracef( "Cached Version : %s", version );
709763

710-
final Object proxy = persistenceContext.getProxy( entityKey );
711-
if ( proxy != null ) {
712-
// there is already a proxy for this impl
713-
// only set the status to read-only if the proxy is read-only
714-
isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly();
715-
}
716-
else {
717-
isReadOnly = session.isDefaultReadOnly();
718-
}
764+
final Object proxy = persistenceContext.getProxy( entityKey );
765+
if ( proxy != null ) {
766+
// there is already a proxy for this impl
767+
// only set the status to read-only if the proxy is read-only
768+
isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly();
769+
}
770+
else {
771+
isReadOnly = session.isDefaultReadOnly();
719772
}
720773

721774
persistenceContext.addEntry(
@@ -852,4 +905,14 @@ private Iterable<PostLoadEventListener> postLoadEventListeners(EventSource sessi
852905
.getEventListenerGroup( EventType.POST_LOAD )
853906
.listeners();
854907
}
908+
909+
private EventListenerGroup<PostLoadEventListener> getEvenListenerGroup(EventSource session) {
910+
return session
911+
.getFactory()
912+
.getServiceRegistry()
913+
.getService( EventListenerRegistry.class)
914+
.getEventListenerGroup( EventType.POST_LOAD);
915+
916+
}
917+
855918
}

0 commit comments

Comments
 (0)