From 123103a73ff46c2c49c04a739a6b6ab76d63dc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20W=2E=20Pedersen?= Date: Wed, 11 Nov 2015 20:52:57 +0100 Subject: [PATCH] HHH-10289 - CPU performance regression in StatefulPersistenceContext.addEntry() (cherry picked from commit da4593de1b6b46656dfd1c3612f2310ce4df0471) --- .../internal/StatefulPersistenceContext.java | 48 ++++- .../internal/DefaultLoadEventListener.java | 191 ++++++++++++------ 2 files changed, 168 insertions(+), 71 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index d71c5d8d8e..4597a00acb 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -42,7 +42,6 @@ import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityEntryFactory; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.ManagedEntity; @@ -489,13 +488,38 @@ public class StatefulPersistenceContext implements PersistenceContext { final EntityEntry e; - if( (entity instanceof ManagedEntity) && ((ManagedEntity) entity).$$_hibernate_getEntityEntry() != null && status == Status.READ_ONLY) { - e = ((ManagedEntity) entity).$$_hibernate_getEntityEntry(); - e.setStatus( status ); + /* + IMPORTANT!!! + + The following instanceof checks and castings are intentional. + + DO NOT REFACTOR to make calls through the EntityEntryFactory interface, which would result + in polymorphic call sites which will severely impact performance. + + When a virtual method is called via an interface the JVM needs to resolve which concrete + implementation to call. This takes CPU cycles and is a performance penalty. It also prevents method + in-ling which further degrades performance. Casting to an implementation and making a direct method call + removes the virtual call, and allows the methods to be in-lined. In this critical code path, it has a very + large impact on performance to make virtual method calls. + */ + if (persister.getEntityEntryFactory() instanceof MutableEntityEntryFactory) { + //noinspection RedundantCast + e = ( (MutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry( + status, + loadedState, + rowId, + id, + version, + lockMode, + existsInDatabase, + persister, + disableVersionIncrement, + this + ); } else { - final EntityEntryFactory entityEntryFactory = persister.getEntityEntryFactory(); - e = entityEntryFactory.createEntityEntry( + //noinspection RedundantCast + e = ( (ImmutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry( status, loadedState, rowId, @@ -511,12 +535,22 @@ public class StatefulPersistenceContext implements PersistenceContext { } entityEntryContext.addEntityEntry( entity, e ); -// entityEntries.put(entity, e); setHasNonReadOnlyEnties( status ); return e; } + public EntityEntry addReferenceEntry( + final Object entity, + final Status status) { + + ((ManagedEntity)entity).$$_hibernate_getEntityEntry().setStatus( status ); + entityEntryContext.addEntityEntry( entity, ((ManagedEntity)entity).$$_hibernate_getEntityEntry() ); + + setHasNonReadOnlyEnties( status ); + return ((ManagedEntity)entity).$$_hibernate_getEntityEntry(); + } + @Override public boolean containsCollection(PersistentCollection collection) { return collectionEntries.containsKey( collection ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index c5fee134be..08510f42e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -20,14 +20,17 @@ import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl; import org.hibernate.cache.spi.entry.StandardCacheEntryImpl; import org.hibernate.engine.internal.CacheHelper; +import org.hibernate.engine.internal.StatefulPersistenceContext; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; +import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; @@ -603,8 +606,22 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i } CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure( ce, factory ); - Object entity = convertCacheEntryToEntity( entry, event.getEntityId(), persister, event, entityKey ); - + final Object entity; + if(entry.isReferenceEntry()) { + if( event.getInstanceToLoad() != null ) { + throw new HibernateException( + String.format( "Attempt to load entity [%s] from cache using provided object instance, but cache " + + "is storing references: "+ event.getEntityId())); + } + else { + entity = convertCacheReferenceEntryToEntity( (ReferenceCacheEntryImpl) entry, + event.getEntityId(), persister, event.getSession(), entityKey ); + } + } + else { + entity = convertCacheEntryToEntity( entry, event.getEntityId(), persister, event, entityKey ); + } + if ( !persister.isInstance( entity ) ) { throw new WrongClassException( "loaded object was of wrong class " + entity.getClass(), @@ -612,10 +629,78 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i persister.getEntityName() ); } - + return entity; } + private Object convertCacheReferenceEntryToEntity( + ReferenceCacheEntryImpl referenceCacheEntry, + Serializable entityId, + EntityPersister persister, + EventSource session, + EntityKey entityKey) { + final Object entity = referenceCacheEntry.getReference(); + + if ( entity == null ) { + throw new IllegalStateException( + "Reference cache entry contained null : " + entityId); + } + else { + makeEntityCircularReferenceSafe(referenceCacheEntry, entityId, session, entity, entityKey); + //PostLoad is needed for EJB3 + EventListenerGroup evenListenerGroup = getEvenListenerGroup(session); + + if(!evenListenerGroup.isEmpty()) { + postLoad(session, evenListenerGroup.listeners(), entity, entityId, persister); + } + return entity; + } + } + + private void postLoad(EventSource session, Iterable listeners, + Object entity, Serializable entityId, EntityPersister persister) { + PostLoadEvent postLoadEvent = new PostLoadEvent(session) + .setEntity(entity) + .setId(entityId) + .setPersister(persister); + + for (PostLoadEventListener listener : listeners) { + listener.onPostLoad(postLoadEvent); + } + } + + private void makeEntityCircularReferenceSafe(ReferenceCacheEntryImpl referenceCacheEntry, + Serializable entityId, + EventSource session, + Object entity, + EntityKey entityKey) { + + final EntityPersister subclassPersister = referenceCacheEntry.getSubclassPersister(); + // make it circular-reference safe + final StatefulPersistenceContext statefulPersistenceContext = (StatefulPersistenceContext) session.getPersistenceContext(); + + if ( (entity instanceof ManagedEntity) ) { + statefulPersistenceContext.addReferenceEntry( + entity, + Status.READ_ONLY + ); + } + else { + TwoPhaseLoad.addUninitializedCachedEntity( + entityKey, + entity, + subclassPersister, + LockMode.NONE, + referenceCacheEntry.areLazyPropertiesUnfetched(), + referenceCacheEntry.getVersion(), + session + ); + } + + subclassPersister.afterInitialize( entity, referenceCacheEntry.areLazyPropertiesUnfetched(), session ); + statefulPersistenceContext.initializeNonLazyCollections(); + } + private Object convertCacheEntryToEntity( CacheEntry entry, Serializable entityId, @@ -636,38 +721,12 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i } final Object entity; - if ( entry.isReferenceEntry() ) { - final Object optionalObject = event.getInstanceToLoad(); - if ( optionalObject != null ) { - throw new HibernateException( - String.format( - "Attempt to load entity [%s] from cache using provided object instance, but cache " + - "is storing references", - MessageHelper.infoString( persister, entityId, factory ) - ) - ); - } - ReferenceCacheEntryImpl referenceCacheEntry = (ReferenceCacheEntryImpl) entry; - entity = referenceCacheEntry.getReference(); - if ( entity == null ) { - throw new IllegalStateException( - "Reference cache entry contained null : " + MessageHelper.infoString( - persister, - entityId, - factory - ) - ); - } - subclassPersister = referenceCacheEntry.getSubclassPersister(); - } - else { - subclassPersister = factory.getEntityPersister( entry.getSubclass() ); - final Object optionalObject = event.getInstanceToLoad(); - entity = optionalObject == null - ? session.instantiate( subclassPersister, entityId ) - : optionalObject; - } + subclassPersister = factory.getEntityPersister( entry.getSubclass() ); + final Object optionalObject = event.getInstanceToLoad(); + entity = optionalObject == null + ? session.instantiate( subclassPersister, entityId ) + : optionalObject; // make it circular-reference safe TwoPhaseLoad.addUninitializedCachedEntity( @@ -684,38 +743,32 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i final Object[] values; final Object version; final boolean isReadOnly; - if ( entry.isReferenceEntry() ) { - values = null; - version = null; - isReadOnly = true; + + final Type[] types = subclassPersister.getPropertyTypes(); + // initializes the entity by (desired) side-effect + values = ( (StandardCacheEntryImpl) entry ).assemble( + entity, entityId, subclassPersister, session.getInterceptor(), session + ); + if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) { + TypeHelper.deepCopy( + values, + types, + subclassPersister.getPropertyUpdateability(), + values, + session + ); + } + version = Versioning.getVersion( values, subclassPersister ); + LOG.tracef( "Cached Version : %s", version ); + + final Object proxy = persistenceContext.getProxy( entityKey ); + if ( proxy != null ) { + // there is already a proxy for this impl + // only set the status to read-only if the proxy is read-only + isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly(); } else { - final Type[] types = subclassPersister.getPropertyTypes(); - // initializes the entity by (desired) side-effect - values = ( (StandardCacheEntryImpl) entry ).assemble( - entity, entityId, subclassPersister, session.getInterceptor(), session - ); - if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) { - TypeHelper.deepCopy( - values, - types, - subclassPersister.getPropertyUpdateability(), - values, - session - ); - } - version = Versioning.getVersion( values, subclassPersister ); - LOG.tracef( "Cached Version : %s", version ); - - final Object proxy = persistenceContext.getProxy( entityKey ); - if ( proxy != null ) { - // there is already a proxy for this impl - // only set the status to read-only if the proxy is read-only - isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly(); - } - else { - isReadOnly = session.isDefaultReadOnly(); - } + isReadOnly = session.isDefaultReadOnly(); } persistenceContext.addEntry( @@ -852,4 +905,14 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i .getEventListenerGroup( EventType.POST_LOAD ) .listeners(); } + + private EventListenerGroup getEvenListenerGroup(EventSource session) { + return session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class) + .getEventListenerGroup( EventType.POST_LOAD); + + } + }