Skip to content

Commit da4593d

Browse files
stalepsebersole
authored andcommitted
HHH-10289 - CPU performance regression in StatefulPersistenceContext.addEntry()
1 parent 1568f89 commit da4593d

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;
@@ -485,13 +484,38 @@ public EntityEntry addEntry(
485484
final boolean disableVersionIncrement) {
486485
final EntityEntry e;
487486

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

508532
entityEntryContext.addEntityEntry( entity, e );
509-
// entityEntries.put(entity, e);
510533

511534
setHasNonReadOnlyEnties( status );
512535
return e;
513536
}
514537

538+
public EntityEntry addReferenceEntry(
539+
final Object entity,
540+
final Status status) {
541+
542+
((ManagedEntity)entity).$$_hibernate_getEntityEntry().setStatus( status );
543+
entityEntryContext.addEntityEntry( entity, ((ManagedEntity)entity).$$_hibernate_getEntityEntry() );
544+
545+
setHasNonReadOnlyEnties( status );
546+
return ((ManagedEntity)entity).$$_hibernate_getEntityEntry();
547+
}
548+
515549
@Override
516550
public boolean containsCollection(PersistentCollection collection) {
517551
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(
@@ -683,38 +742,32 @@ private Object convertCacheEntryToEntity(
683742
final Object[] values;
684743
final Object version;
685744
final boolean isReadOnly;
686-
if ( entry.isReferenceEntry() ) {
687-
values = null;
688-
version = null;
689-
isReadOnly = true;
690-
}
691-
else {
692-
final Type[] types = subclassPersister.getPropertyTypes();
693-
// initializes the entity by (desired) side-effect
694-
values = ( (StandardCacheEntryImpl) entry ).assemble(
695-
entity, entityId, subclassPersister, session.getInterceptor(), session
745+
746+
final Type[] types = subclassPersister.getPropertyTypes();
747+
// initializes the entity by (desired) side-effect
748+
values = ( (StandardCacheEntryImpl) entry ).assemble(
749+
entity, entityId, subclassPersister, session.getInterceptor(), session
750+
);
751+
if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) {
752+
TypeHelper.deepCopy(
753+
values,
754+
types,
755+
subclassPersister.getPropertyUpdateability(),
756+
values,
757+
session
696758
);
697-
if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) {
698-
TypeHelper.deepCopy(
699-
values,
700-
types,
701-
subclassPersister.getPropertyUpdateability(),
702-
values,
703-
session
704-
);
705-
}
706-
version = Versioning.getVersion( values, subclassPersister );
707-
LOG.tracef( "Cached Version : %s", version );
759+
}
760+
version = Versioning.getVersion( values, subclassPersister );
761+
LOG.tracef( "Cached Version : %s", version );
708762

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

720773
persistenceContext.addEntry(
@@ -848,4 +901,14 @@ private Iterable<PostLoadEventListener> postLoadEventListeners(EventSource sessi
848901
.getEventListenerGroup( EventType.POST_LOAD )
849902
.listeners();
850903
}
904+
905+
private EventListenerGroup<PostLoadEventListener> getEvenListenerGroup(EventSource session) {
906+
return session
907+
.getFactory()
908+
.getServiceRegistry()
909+
.getService( EventListenerRegistry.class)
910+
.getEventListenerGroup( EventType.POST_LOAD);
911+
912+
}
913+
851914
}

0 commit comments

Comments
 (0)