From 25584933d939ad7551f4616981bf29d2bc39b267 Mon Sep 17 00:00:00 2001 From: Gavin Date: Sun, 9 Apr 2023 20:05:10 +0200 Subject: [PATCH] update DefaultReactiveListeners --- .../impl/ReactiveCollectionRemoveAction.java | 17 + .../impl/ReactiveEntityDeleteAction.java | 127 ++-- ...DefaultReactiveAutoFlushEventListener.java | 2 +- .../DefaultReactiveDeleteEventListener.java | 338 +++++++--- ...faultReactiveFlushEntityEventListener.java | 628 +++++++++--------- .../DefaultReactiveFlushEventListener.java | 17 +- ...tiveInitializeCollectionEventListener.java | 68 +- .../DefaultReactiveLoadEventListener.java | 151 +++-- .../DefaultReactiveLockEventListener.java | 67 +- .../DefaultReactiveMergeEventListener.java | 241 ++++--- .../DefaultReactivePersistEventListener.java | 121 ++-- .../DefaultReactivePostLoadEventListener.java | 28 +- .../DefaultReactiveRefreshEventListener.java | 115 ++-- ...ReactiveResolveNaturalIdEventListener.java | 32 +- .../session/impl/ReactiveSessionImpl.java | 4 +- 15 files changed, 1092 insertions(+), 864 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java index 9742a5e04a..525c8d92fb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java @@ -49,6 +49,23 @@ public ReactiveCollectionRemoveAction( this.affectedOwner = session.getPersistenceContextInternal().getLoadedCollectionOwnerOrNull( collection ); } + /** + * Removes a persistent collection for an unloaded proxy. + * + * Use this constructor when the owning entity is has not been loaded. + * @param persister The collection's persister + * @param id The collection key + * @param session The session + */ + public ReactiveCollectionRemoveAction( + final CollectionPersister persister, + final Object id, + final EventSource session) { + super( persister, null, id, session ); + emptySnapshot = false; + affectedOwner = null; + } + @Override public CompletionStage reactiveExecute() { final Object key = getKey(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityDeleteAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityDeleteAction.java index 78978dadef..8d036eb566 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityDeleteAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityDeleteAction.java @@ -13,6 +13,7 @@ import org.hibernate.action.internal.EntityDeleteAction; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; @@ -43,66 +44,45 @@ public ReactiveEntityDeleteAction( super( id, state, version, instance, persister, isCascadeDeleteEnabled, session ); } + public ReactiveEntityDeleteAction(Object id, EntityPersister persister, EventSource session) { + super( id, persister, session ); + } + @Override public void execute() throws HibernateException { throw LOG.nonReactiveMethodCall( "reactiveExecute" ); } + private boolean isInstanceLoaded() { + // A null instance signals that we're deleting an unloaded proxy. + return getInstance() != null; + } + @Override public CompletionStage reactiveExecute() throws HibernateException { final Object id = getId(); + final Object version = getCurrentVersion(); final EntityPersister persister = getPersister(); final SharedSessionContractImplementor session = getSession(); final Object instance = getInstance(); - final boolean veto = preDelete(); + final boolean veto = isInstanceLoaded() && preDelete(); - Object version = getVersion(); - if ( persister.isVersionPropertyGenerated() ) { - // we need to grab the version value from the entity, otherwise - // we have issues with generated-version entities that may have - // multiple actions queued during the same flush - version = persister.getVersion( instance ); - } + final Object ck = lockCacheItem(); - final Object ck; - if ( persister.canWriteToCache() ) { - final EntityDataAccess cache = persister.getCacheAccessStrategy(); - ck = cache.generateCacheKey( id, persister, session.getFactory(), session.getTenantIdentifier() ); - setLock( cache.lockItem( session, ck, version ) ); - } - else { - ck = null; - } - - CompletionStage deleteStep = !isCascadeDeleteEnabled() && !veto + final CompletionStage deleteStep = !isCascadeDeleteEnabled() && !veto ? ( (ReactiveEntityPersister) persister ).deleteReactive( id, version, instance, session ) : voidFuture(); return deleteStep.thenAccept( v -> { - //postDelete: - // After actually deleting a row, record the fact that the instance no longer - // exists on the database (needed for identity-column key generation), and - // remove it from the session cache - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final EntityEntry entry = persistenceContext.removeEntry( instance ); - if ( entry == null ) { - throw new AssertionFailure( "possible non-threadsafe access to session" ); + if ( isInstanceLoaded() ) { + postDeleteLoaded( id, persister, session, instance, ck ); } - entry.postDelete(); - - persistenceContext.removeEntity( entry.getEntityKey() ); - persistenceContext.removeProxy( entry.getEntityKey() ); - - if ( persister.canWriteToCache() ) { - persister.getCacheAccessStrategy().remove( session, ck ); + else { + // we're deleting an unloaded proxy + postDeleteUnloaded( id, persister, session, ck ); } - persistenceContext.getNaturalIdResolutions() - .removeSharedResolution( id, getNaturalIdValues(), persister ); - - postDelete(); - final StatisticsImplementor statistics = getSession().getFactory().getStatistics(); if ( statistics.isStatisticsEnabled() && !veto ) { statistics.deleteEntity( getPersister().getEntityName() ); @@ -110,4 +90,73 @@ public CompletionStage reactiveExecute() throws HibernateException { } ); } + //TODO: copy/paste from superclass (make it protected!) + private Object getCurrentVersion() { + return getPersister().isVersionPropertyGenerated() + // skip if we're deleting an unloaded proxy, no need for the version + && isInstanceLoaded() + // we need to grab the version value from the entity, otherwise + // we have issues with generated-version entities that may have + // multiple actions queued during the same flush + ? getPersister().getVersion( getInstance() ) + : getVersion(); + } + + //TODO: copy/paste of postDeleteLoaded() from superclass (make it protected!) + private void postDeleteLoaded( + Object id, + EntityPersister persister, + SharedSessionContractImplementor session, + Object instance, + Object ck) { + // After actually deleting a row, record the fact that the instance no longer + // exists on the database (needed for identity-column key generation), and + // remove it from the session cache + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityEntry entry = persistenceContext.removeEntry(instance); + if ( entry == null ) { + throw new AssertionFailure( "possible non-threadsafe access to session" ); + } + entry.postDelete(); + final EntityKey key = entry.getEntityKey(); + persistenceContext.removeEntity( key ); + persistenceContext.removeProxy( key ); + removeCacheItem( ck ); + persistenceContext.getNaturalIdResolutions().removeSharedResolution( id, getNaturalIdValues(), persister ); + postDelete(); + } + + //TODO: copy/paste of postDeleteUnloaded() from superclass (make it protected!) + private void postDeleteUnloaded(Object id, EntityPersister persister, SharedSessionContractImplementor session, Object ck) { + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityKey key = session.generateEntityKey( id, persister ); + if ( !persistenceContext.containsDeletedUnloadedEntityKey( key ) ) { + throw new AssertionFailure( "deleted proxy should be for an unloaded entity: " + key ); + } + persistenceContext.removeProxy( key ); + removeCacheItem( ck ); + } + + //TODO: copy/paste from superclass (make it protected!) + private Object lockCacheItem() { + final EntityPersister persister = getPersister(); + if ( persister.canWriteToCache() ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + final SharedSessionContractImplementor session = getSession(); + final Object ck = cache.generateCacheKey( getId(), persister, session.getFactory(), session.getTenantIdentifier() ); + setLock( cache.lockItem( session, ck, getCurrentVersion() ) ); + return ck; + } + else { + return null; + } + } + + //TODO: copy/paste from superclass (make it protected!) + private void removeCacheItem(Object ck) { + final EntityPersister persister = getPersister(); + if ( persister.canWriteToCache() ) { + persister.getCacheAccessStrategy().remove( getSession(), ck); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveAutoFlushEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveAutoFlushEventListener.java index 46e0cd609c..ce6c12e65f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveAutoFlushEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveAutoFlushEventListener.java @@ -81,7 +81,7 @@ private ReactiveActionQueue reactiveActionQueue(AutoFlushEvent event) { private boolean flushIsReallyNeeded(AutoFlushEvent event, final EventSource source) { return source.getHibernateFlushMode() == FlushMode.ALWAYS - || reactiveActionQueue( source ).areTablesToBeUpdated( event.getQuerySpaces() ); + || reactiveActionQueue( source ).areTablesToBeUpdated( event.getQuerySpaces() ); } private ReactiveActionQueue reactiveActionQueue(EventSource source) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java index 971c3b722c..1d8a039511 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java @@ -15,6 +15,8 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.TransientObjectException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.spi.EntityEntry; @@ -22,24 +24,36 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.Status; import org.hibernate.event.internal.OnUpdateVisitor; +import org.hibernate.event.internal.PostDeleteEventListenerStandardImpl; import org.hibernate.event.service.spi.JpaBootstrapSensitive; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.DeleteEventListener; import org.hibernate.event.spi.EventSource; +import org.hibernate.internal.EmptyInterceptor; +import org.hibernate.internal.FastSessionServices; import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.impl.Cascade; import org.hibernate.reactive.engine.impl.CascadingActions; import org.hibernate.reactive.engine.impl.ForeignKeys; +import org.hibernate.reactive.engine.impl.ReactiveCollectionRemoveAction; import org.hibernate.reactive.engine.impl.ReactiveEntityDeleteAction; import org.hibernate.reactive.engine.impl.ReactiveOrphanRemovalAction; import org.hibernate.reactive.event.ReactiveDeleteEventListener; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.type.CollectionType; +import org.hibernate.type.CompositeType; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; @@ -107,14 +121,73 @@ public CompletionStage reactiveOnDelete(DeleteEvent event) throws Hibernat */ @Override public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) throws HibernateException { - EventSource source = event.getSession(); - if ( event.getObject() instanceof CompletionStage ) { - CompletionStage objectStage = (CompletionStage) event.getObject(); - return objectStage - .thenCompose( objectEvent -> fetchAndDelete( event, transientEntities, source, objectEvent ) ); + if ( optimizeUnloadedDelete( event ) ) { + return voidFuture(); } + else { + final EventSource source = event.getSession(); + Object object = event.getObject(); + if ( object instanceof CompletionStage ) { + final CompletionStage objectStage = (CompletionStage) object; + return objectStage.thenCompose( objectEvent -> fetchAndDelete( event, transientEntities, source, objectEvent ) ); + } + else { + return fetchAndDelete( event, transientEntities, source, object); + } + } + } + + private boolean optimizeUnloadedDelete(DeleteEvent event) { + final Object object = event.getObject(); + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( object ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.isUninitialized() ) { + final EventSource source = event.getSession(); + final EntityPersister persister = source.getFactory().getMappingMetamodel() + .findEntityDescriptor( lazyInitializer.getEntityName() ); + final Object id = lazyInitializer.getIdentifier(); + final EntityKey key = source.generateEntityKey( id, persister ); + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); + if ( !persistenceContext.containsEntity( key ) + && canBeDeletedWithoutLoading( source, persister ) ) { + // optimization for deleting certain entities without loading them + persistenceContext.reassociateProxy( object, id ); + if ( !persistenceContext.containsDeletedUnloadedEntityKey( key ) ) { + persistenceContext.registerDeletedUnloadedEntityKey( key ); + + if ( persister.hasOwnedCollections() ) { + // we're deleting an unloaded proxy with collections + for ( Type type : persister.getPropertyTypes() ) { //TODO: when we enable this for subclasses use getSubclassPropertyTypeClosure() + deleteOwnedCollections( type, id, source ); + } + } - return fetchAndDelete( event, transientEntities, source, event.getObject() ); + ((ReactiveSession) source).getReactiveActionQueue() + .addAction( new ReactiveEntityDeleteAction( id, persister, source ) ); + } + return true; + } + } + } + return false; + } + + private static void deleteOwnedCollections(Type type, Object key, EventSource session) { + final MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel(); + final ReactiveActionQueue actionQueue = ((ReactiveSession) session).getReactiveActionQueue(); + if ( type.isCollectionType() ) { + final String role = ( (CollectionType) type ).getRole(); + final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor(role); + if ( !persister.isInverse() ) { + actionQueue.addAction( new ReactiveCollectionRemoveAction( persister, key, session ) ); + } + } + else if ( type.isComponentType() ) { + final Type[] subtypes = ( (CompositeType) type ).getSubtypes(); + for ( Type subtype : subtypes ) { + deleteOwnedCollections( subtype, key, session ); + } + } } private CompletionStage fetchAndDelete( @@ -137,51 +210,50 @@ private CompletionStage fetchAndDelete( } private CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities, Object entity) { + final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + if ( entityEntry == null ) { + return deleteTransientInstance( event, transientEntities, entity ); + } + else { + return deletePersistentInstance( event, transientEntities, entity, entityEntry ); + } + } - EventSource source = event.getSession(); - PersistenceContext persistenceContext = source.getPersistenceContextInternal(); + private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { + LOG.trace( "Entity was not persistent in delete processing" ); - EntityEntry entityEntry = persistenceContext.getEntry( entity ); + final EventSource source = event.getSession(); - if ( entityEntry == null ) { - LOG.trace( "Entity was not persistent in delete processing" ); - - final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - - return ForeignKeys.isTransient( persister.getEntityName(), entity, null, source.getSession() ) - .thenCompose( trans -> { - if ( trans ) { - // EARLY EXIT!!! - return deleteTransientEntity( - source, - entity, - event.isCascadeDeleteEnabled(), - persister, - transientEntities - ); - } - performDetachedEntityDeletionCheck(event); - - final Object id = persister.getIdentifier( entity, source); + final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity); + return ForeignKeys.isTransient( persister.getEntityName(), entity, null, source.getSession() ) + .thenCompose( trans -> { + if ( trans ) { + return deleteTransientEntity( source, entity, persister, transientEntities ); + } + else { + performDetachedEntityDeletionCheck( event ); + final Object id = persister.getIdentifier( entity, source ); if ( id == null ) { throw new TransientObjectException( "the detached instance passed to delete() had a null identifier" ); } + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); final EntityKey key = source.generateEntityKey( id, persister ); persistenceContext.checkUniqueness( key, entity ); - new OnUpdateVisitor(source, id, entity ).process( entity, persister ); + new OnUpdateVisitor( source, id, entity ).process( entity, persister ); final Object version = persister.getVersion( entity ); - EntityEntry entry = persistenceContext.addEntity( + final EntityEntry entry = persistenceContext.addEntity( entity, - ( persister.isMutable() ? Status.MANAGED : Status.READ_ONLY ), - persister.getPropertyValues( entity ), + persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, + persister.getValues( entity ), key, version, LockMode.NONE, @@ -189,57 +261,97 @@ private CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext persister, false ); - persister.afterReassociate( entity, source); + persister.afterReassociate( entity, source ); - callbackRegistry.preRemove( entity ); + return delete( event, transientEntities, source, entity, persister, id, version, entry ); + } + } ); + } - return deleteEntity( - source, - entity, - entry, - event.isCascadeDeleteEnabled(), - event.isOrphanRemovalBeforeUpdates(), - persister, - transientEntities - ) - .thenAccept( v -> { - if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { - persister.resetIdentifier( entity, id, version, source); - } - } ); - } ); + private CompletionStage deletePersistentInstance( + DeleteEvent event, + DeleteContext transientEntities, + Object entity, + EntityEntry entityEntry) { + LOG.trace( "Deleting a persistent instance" ); + final EventSource source = event.getSession(); + if ( entityEntry.getStatus().isDeletedOrGone() + || source.getPersistenceContextInternal() + .containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { + LOG.trace( "Object was already deleted" ); + return voidFuture(); } else { - LOG.trace( "Deleting a persistent instance" ); - - Status status = entityEntry.getStatus(); - if ( status == Status.DELETED || status == Status.GONE ) { - LOG.trace( "Object was already deleted" ); - return voidFuture(); - } - final EntityPersister persister = entityEntry.getPersister(); - Object id = entityEntry.getId(); - Object version = entityEntry.getVersion(); - - callbackRegistry.preRemove( entity ); - - return deleteEntity( + return delete( + event, + transientEntities, source, entity, - entityEntry, - event.isCascadeDeleteEnabled(), - event.isOrphanRemovalBeforeUpdates(), - persister, - transientEntities - ) - .thenAccept( v -> { - if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { - persister.resetIdentifier( entity, id, version, source); - } - } ); + entityEntry.getPersister(), + entityEntry.getId(), + entityEntry.getVersion(), + entityEntry + ); } } + private CompletionStage delete( + DeleteEvent event, + DeleteContext transientEntities, + EventSource source, + Object entity, + EntityPersister persister, + Object id, + Object version, + EntityEntry entry) { + callbackRegistry.preRemove( entity ); + return deleteEntity( + source, + entity, + entry, + event.isCascadeDeleteEnabled(), + event.isOrphanRemovalBeforeUpdates(), + persister, + transientEntities + ) + .thenAccept( v -> { + if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { + persister.resetIdentifier( entity, id, version, source ); + } + } ); + } + + /** + * Can we delete the row represented by the proxy without loading the entity? + */ + private boolean canBeDeletedWithoutLoading(EventSource source, EntityPersister persister) { + return source.getInterceptor() == EmptyInterceptor.INSTANCE + && !persister.implementsLifecycle() + && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc + && !persister.hasCascadeDelete() + && !persister.hasNaturalIdentifier() + && !persister.hasCollectionNotReferencingPK() + && !hasRegisteredRemoveCallbacks( persister ) + && !hasCustomEventListeners( source ); + } + + private static boolean hasCustomEventListeners(EventSource source) { + final FastSessionServices fss = source.getFactory().getFastSessionServices(); + // Bean Validation adds a PRE_DELETE listener + // and Envers adds a POST_DELETE listener + return fss.eventListenerGroup_PRE_DELETE.count() > 0 + || fss.eventListenerGroup_POST_DELETE.count() > 1 + || fss.eventListenerGroup_POST_DELETE.count() == 1 + && !(fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() + instanceof PostDeleteEventListenerStandardImpl); + } + + private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) { + final Class mappedClass = persister.getMappedClass(); + return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE ) + || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); + } + /** * Called when we have recognized an attempt to delete a detached entity. *

@@ -275,7 +387,6 @@ private void disallowDeletionOfDetached(DeleteEvent event) { * * @param session The session which is the source of the event * @param entity The entity being delete processed - * @param cascadeDeleteEnabled Is cascading of deletes enabled * @param persister The entity persister * @param transientEntities A cache of already visited transient entities * (to avoid infinite recursion). @@ -283,17 +394,17 @@ private void disallowDeletionOfDetached(DeleteEvent event) { protected CompletionStage deleteTransientEntity( EventSource session, Object entity, - boolean cascadeDeleteEnabled, EntityPersister persister, DeleteContext transientEntities) { LOG.handlingTransientEntity(); - if ( !transientEntities.add( entity ) ) { + if ( transientEntities.add( entity ) ) { + return cascadeBeforeDelete( session, persister, entity, transientEntities ) + .thenCompose( v -> cascadeAfterDelete( session, persister, entity, transientEntities ) ); + } + else { LOG.trace( "Already handled transient entity; skipping" ); return voidFuture(); } - transientEntities.add( entity ); - return cascadeBeforeDelete( session, persister, entity, null, transientEntities ) - .thenCompose( v -> cascadeAfterDelete( session, persister, entity, transientEntities ) ); } /** @@ -322,24 +433,28 @@ protected CompletionStage deleteEntity( } final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final Type[] propTypes = persister.getPropertyTypes(); final Object version = entityEntry.getVersion(); final Object[] currentState = entityEntry.getLoadedState() == null ? persister.getValues( entity ) // i.e. the entity came in from update() : entityEntry.getLoadedState(); - final Object[] deletedState = createDeletedState( persister, currentState, session ); + final Object[] deletedState = createDeletedState( persister, entity, currentState, session ); entityEntry.setDeletedState( deletedState ); - session.getInterceptor() - .onDelete( entity, entityEntry.getId(), deletedState, persister.getPropertyNames(), propTypes ); + session.getInterceptor().onDelete( + entity, + entityEntry.getId(), + deletedState, + persister.getPropertyNames(), + persister.getPropertyTypes() + ); - // before any callbacks, etc, so subdeletions see that this deletion happened first + // before any callbacks, etc., so subdeletions see that this deletion happened first persistenceContext.setEntryStatus( entityEntry, Status.DELETED ); final EntityKey key = session.generateEntityKey( entityEntry.getId(), persister ); - return cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities ) + return cascadeBeforeDelete( session, persister, entity, transientEntities ) .thenCompose( v -> new ForeignKeys.Nullifier( entity, true, @@ -355,7 +470,7 @@ protected CompletionStage deleteEntity( ); persistenceContext.registerNullifiableEntityKey( key ); - ReactiveActionQueue actionQueue = actionQueue( session ); + final ReactiveActionQueue actionQueue = actionQueue( session ); if ( isOrphanRemovalBeforeUpdates ) { // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484. This should be removed once action/task @@ -394,12 +509,44 @@ private ReactiveActionQueue actionQueue(EventSource session) { return session.unwrap( ReactiveSession.class ).getReactiveActionQueue(); } - private Object[] createDeletedState(EntityPersister persister, Object[] currentState, EventSource session) { - Type[] propTypes = persister.getPropertyTypes(); - final Object[] deletedState = new Object[propTypes.length]; - boolean[] copyability = new boolean[propTypes.length]; - java.util.Arrays.fill( copyability, true ); - TypeHelper.deepCopy( currentState, propTypes, copyability, deletedState, session ); + private Object[] createDeletedState( + EntityPersister persister, + Object parent, + Object[] currentState, + EventSource eventSource) { + final Type[] types = persister.getPropertyTypes(); + final Object[] deletedState = new Object[types.length]; + if ( !persister.hasCollections() || !persister.hasUninitializedLazyProperties( parent ) ) { + boolean[] copyability = new boolean[types.length]; + java.util.Arrays.fill( copyability, true ); + TypeHelper.deepCopy( currentState, types, copyability, deletedState, eventSource ); + return deletedState; + } + + final String[] propertyNames = persister.getPropertyNames(); + final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata(); + for ( int i = 0; i < types.length; i++) { + if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) { + final CollectionType collectionType = (CollectionType) types[i]; + final CollectionPersister collectionDescriptor = persister.getFactory().getMappingMetamodel() + .getCollectionDescriptor( collectionType.getRole() ); + if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) { + final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() ); + // This will make sure that a CollectionEntry exists + deletedState[i] = collectionType.getCollection( keyOfOwner, eventSource.getSession(), parent, false ); + } + else { + deletedState[i] = currentState[i]; + } + } + else if ( currentState[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY + || currentState[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { + deletedState[i] = currentState[i]; + } + else { + deletedState[i] = types[i].deepCopy( currentState[i], eventSource.getFactory() ); + } + } return deletedState; } @@ -407,12 +554,11 @@ protected CompletionStage cascadeBeforeDelete( EventSource session, EntityPersister persister, Object entity, - EntityEntry entityEntry, DeleteContext transientEntities) throws HibernateException { // cascade-delete to collections BEFORE the collection owner is deleted return fetchLazyAssociationsBeforeCascade( CascadingActions.DELETE, persister, entity, session ) .thenCompose( - v -> new Cascade( + v -> new Cascade<>( CascadingActions.DELETE, CascadePoint.AFTER_INSERT_BEFORE_DELETE, persister, entity, transientEntities, session diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEntityEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEntityEventListener.java index 9f9bfd85e6..0956e8e6ab 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEntityEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEntityEventListener.java @@ -5,26 +5,16 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; - -import java.lang.invoke.MethodHandles; -import java.util.Arrays; - import org.hibernate.AssertionFailure; import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.HibernateException; -import org.hibernate.SessionFactory; import org.hibernate.StaleObjectStateException; import org.hibernate.action.internal.DelayedPostInsertIdentifier; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -41,6 +31,7 @@ import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.pretty.MessageHelper; import org.hibernate.reactive.engine.impl.ReactiveEntityUpdateAction; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -48,6 +39,20 @@ import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; + +import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; +import static org.hibernate.engine.internal.Versioning.getVersion; +import static org.hibernate.engine.internal.Versioning.incrementVersion; +import static org.hibernate.engine.internal.Versioning.setVersion; +import static org.hibernate.pretty.MessageHelper.infoString; + /** * A reactific {@link org.hibernate.event.internal.DefaultFlushEntityEventListener}. * This implementation is almost, but not quite, a line-for-line copy of @@ -74,20 +79,28 @@ public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { public void checkId(Object object, EntityPersister persister, Object id, SessionImplementor session) throws HibernateException { - if (id instanceof DelayedPostInsertIdentifier) { + if ( id instanceof DelayedPostInsertIdentifier ) { // this is a situation where the entity id is assigned by a post-insert generator // and was saved outside the transaction forcing it to be delayed return; } - if ( persister.canExtractIdOutOfEntity() ) { - Object oid = persister.getIdentifier( object, session ); - if ( id == null ) { - throw new AssertionFailure( "null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)" ); - } - if ( !persister.getIdentifierType().isEqual( id, oid, session.getFactory() ) ) { - throw LOG.identifierAltered( persister.getEntityName(), id, oid ); - } + final Object oid = persister.getIdentifier( object, session ); + + if ( id == null ) { + throw new AssertionFailure( "null id in " + persister.getEntityName() + + " entry (don't flush the Session after an exception occurs)" ); + } + + //Small optimisation: always try to avoid getIdentifierType().isEqual(..) when possible. + //(However it's not safe to invoke the equals() method as it might trigger side-effects) + if ( id == oid ) { + //No further checks necessary: + return; + } + + if ( !persister.getIdentifierType().isEqual( id, oid, session.getFactory() ) ) { + throw LOG.identifierAltered( persister.getEntityName(), id, oid ); } } @@ -98,20 +111,22 @@ private void checkNaturalId( Object[] current, Object[] loaded, SessionImplementor session) { - - if ( entity instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptor interceptor = - ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - // EARLY EXIT!!! - // nothing to check - the entity is an un-initialized enhancement-as-proxy reference - return; + if ( !isUninitializedEnhanced( entity ) ) { + final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping(); + if ( naturalIdMapping != null && entry.getStatus() != Status.READ_ONLY ) { + naturalIdMapping.verifyFlushState( entry.getId(), current, loaded, session ); } } + } - final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping(); - if ( naturalIdMapping != null && entry.getStatus() != Status.READ_ONLY ) { - naturalIdMapping.verifyFlushState( entry.getId(), current, loaded, session ); + private static boolean isUninitializedEnhanced(Object entity) { + if ( isPersistentAttributeInterceptable( entity ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor(); + // the entity is an un-initialized enhancement-as-proxy reference + return interceptor instanceof EnhancementAsProxyLazinessInterceptor; + } + else { + return false; } } @@ -123,9 +138,6 @@ public void onFlushEntity(FlushEntityEvent event) throws HibernateException { final Object entity = event.getEntity(); final EntityEntry entry = event.getEntityEntry(); final EventSource session = event.getSession(); - final EntityPersister persister = entry.getPersister(); - final Status status = entry.getStatus(); - final Type[] types = persister.getPropertyTypes(); final boolean mightBeDirty = entry.requiresDirtyCheck( entity ); @@ -134,14 +146,17 @@ public void onFlushEntity(FlushEntityEvent event) throws HibernateException { event.setPropertyValues( values ); //TODO: avoid this for non-new instances where mightBeDirty==false - boolean substitute = wrapCollections( session, persister, entity, entry.getId(), types, values ); + + boolean substitute = wrapCollections( event, values ); if ( isUpdateNecessary( event, mightBeDirty ) ) { substitute = scheduleUpdate( event ) || substitute; } - if ( status != Status.DELETED ) { - // now update the object .. has to be outside the main if block above (because of collections) + if ( entry.getStatus() != Status.DELETED ) { + final EntityPersister persister = entry.getPersister(); + // now update the object + // has to be outside the main if block above (because of collections) if ( substitute ) { persister.setPropertyValues( entity, values ); } @@ -149,59 +164,47 @@ public void onFlushEntity(FlushEntityEvent event) throws HibernateException { // Search for collections by reachability, updating their role. // We don't want to touch collections reachable from a deleted object if ( persister.hasCollections() ) { - new FlushVisitor( session, entity ).processEntityPropertyValues( values, types ); + new FlushVisitor( session, entity ) + .processEntityPropertyValues( values, persister.getPropertyTypes() ); } } } private Object[] getValues(Object entity, EntityEntry entry, boolean mightBeDirty, SessionImplementor session) { final Object[] loadedState = entry.getLoadedState(); - final Status status = entry.getStatus(); - final EntityPersister persister = entry.getPersister(); - final Object[] values; - if ( status == Status.DELETED ) { + if ( entry.getStatus() == Status.DELETED ) { //grab its state saved at deletion - values = entry.getDeletedState(); + return entry.getDeletedState(); } else if ( !mightBeDirty && loadedState != null ) { - values = loadedState; + return loadedState; } else { + final EntityPersister persister = entry.getPersister(); checkId( entity, persister, entry.getId(), session ); - // grab its current state - values = persister.getValues( entity ); - + Object[] values = persister.getValues( entity ); checkNaturalId( persister, entity, entry, values, loadedState, session ); + return values; } - return values; } - /* - * see DefaultFlushEntityEventListener#wrapCollections + /** + * Wrap up any new collections directly referenced by the object + * or its components. */ - private boolean wrapCollections( - EventSource session, - EntityPersister persister, - Object entity, - Object id, - Type[] types, - Object[] values - ) { + private boolean wrapCollections(FlushEntityEvent event, Object[] values) { + final EntityEntry entry = event.getEntityEntry(); + final EntityPersister persister = entry.getPersister(); if ( persister.hasCollections() ) { - - // wrap up any new collections directly referenced by the object - // or its components - - // NOTE: we need to do the wrap here even if its not "dirty", + // NOTE: we need to do the wrap here even if it's not "dirty", // because collections need wrapping but changes to _them_ // don't dirty the container. Also, for versioned data, we // need to wrap before calling searchForDirtyCollections - - WrapVisitor visitor = new WrapVisitor( entity, id ,session ); - // substitutes into values by side-effect - visitor.processEntityPropertyValues( values, types ); + final WrapVisitor visitor = new WrapVisitor( event.getEntity(), entry.getId(), event.getSession() ); + // substitutes into values by side effect + visitor.processEntityPropertyValues( values, persister.getPropertyTypes() ); return visitor.isSubstitutionRequired(); } else { @@ -210,7 +213,8 @@ private boolean wrapCollections( } private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mightBeDirty) { - final Status status = event.getEntityEntry().getStatus(); + final EntityEntry entityEntry = event.getEntityEntry(); + final Status status = entityEntry.getStatus(); if ( mightBeDirty || status == Status.DELETED ) { // compare to cached state (ignoring collections unless versioned) dirtyCheck( event ); @@ -219,16 +223,16 @@ private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mi } else { final Object entity = event.getEntity(); - ManagedTypeHelper.processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); - event.getSession() - .getFactory() + processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + final EventSource source = event.getSession(); + source.getFactory() .getCustomEntityDirtinessStrategy() - .resetDirty( entity, event.getEntityEntry().getPersister(), event.getSession() ); + .resetDirty( entity, entityEntry.getPersister(), source ); return false; } } else { - return hasDirtyCollections( event, event.getEntityEntry().getPersister(), status ); + return hasDirtyCollections( event ); } } @@ -240,48 +244,14 @@ private boolean scheduleUpdate(final FlushEntityEvent event) { final EntityPersister persister = entry.getPersister(); final Object[] values = event.getPropertyValues(); - if ( LOG.isTraceEnabled() ) { - if ( status == Status.DELETED ) { - if ( !persister.isMutable() ) { - LOG.tracev( - "Updating immutable, deleted entity: {0}", - infoString( persister, entry.getId(), session.getFactory() ) - ); - } - else if ( !entry.isModifiableEntity() ) { - LOG.tracev( - "Updating non-modifiable, deleted entity: {0}", - infoString( persister, entry.getId(), session.getFactory() ) - ); - } - else { - LOG.tracev( - "Updating deleted entity: ", - infoString( persister, entry.getId(), session.getFactory() ) - ); - } - } - else { - LOG.tracev( - "Updating entity: {0}", - infoString( persister, entry.getId(), session.getFactory() ) - ); - } - } + logScheduleUpdate( entry, session, status, persister ); final boolean intercepted = !entry.isBeingReplicated() && handleInterception( event ); // increment the version number (if necessary) final Object nextVersion = getNextVersion( event ); - // if it was dirtied by a collection only - int[] dirtyProperties = event.getDirtyProperties(); - if ( event.isDirtyCheckPossible() && dirtyProperties == null ) { - if ( !intercepted && !event.hasDirtyCollection() ) { - throw new AssertionFailure( "dirty, but no dirty properties" ); - } - dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY; - } + int[] dirtyProperties = getDirtyProperties( event, intercepted ); // check nullability but do not doAfterTransactionCompletion command execute // we'll use scheduled updates for that. @@ -295,9 +265,9 @@ else if ( !entry.isModifiableEntity() ) { values, dirtyProperties, event.hasDirtyCollection(), - ( status == Status.DELETED && !entry.isModifiableEntity() ? - persister.getPropertyValues( entity ) : - entry.getLoadedState() ), + status == Status.DELETED && !entry.isModifiableEntity() + ? persister.getPropertyValues( entity ) + : entry.getLoadedState(), entry.getVersion(), nextVersion, entity, @@ -310,63 +280,97 @@ else if ( !entry.isModifiableEntity() ) { return intercepted; } - protected boolean handleInterception(FlushEntityEvent event) { - SessionImplementor session = event.getSession(); - EntityEntry entry = event.getEntityEntry(); - EntityPersister persister = entry.getPersister(); - Object entity = event.getEntity(); + private static int[] getDirtyProperties(FlushEntityEvent event, boolean intercepted) { + int[] dirtyProperties = event.getDirtyProperties(); + if ( event.isDirtyCheckPossible() && dirtyProperties == null ) { + if ( !intercepted && !event.hasDirtyCollection() ) { + throw new AssertionFailure( "dirty, but no dirty properties" ); + } + else { + // it was dirtied by a collection only + return ArrayHelper.EMPTY_INT_ARRAY; + } + } + else { + return dirtyProperties; + } + } - //give the Interceptor a chance to modify property values - final Object[] values = event.getPropertyValues(); - final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister ); + private static void logScheduleUpdate(EntityEntry entry, EventSource session, Status status, EntityPersister persister) { + if ( LOG.isTraceEnabled() ) { + if ( status == Status.DELETED ) { + if ( !persister.isMutable() ) { + LOG.tracev( + "Updating immutable, deleted entity: {0}", + MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + ); + } + else if ( !entry.isModifiableEntity() ) { + LOG.tracev( + "Updating non-modifiable, deleted entity: {0}", + MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + ); + } + else { + LOG.tracev( + "Updating deleted entity: {0}", + MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + ); + } + } + else { + LOG.tracev( + "Updating entity: {0}", + MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + ); + } + } + } + protected boolean handleInterception(FlushEntityEvent event) { + //give the Interceptor a chance to modify property values + final boolean intercepted = invokeInterceptor( event ); //now we might need to recalculate the dirtyProperties array if ( intercepted && event.isDirtyCheckPossible() ) { dirtyCheck( event ); } - return intercepted; } - protected boolean invokeInterceptor( - SessionImplementor session, - Object entity, - EntityEntry entry, - final Object[] values, - EntityPersister persister) { + protected boolean invokeInterceptor(FlushEntityEvent event) { + final EntityEntry entry = event.getEntityEntry(); + final Object entity = event.getEntity(); + final Object id = entry.getId(); + final Object[] values = event.getPropertyValues(); + final EntityPersister persister = entry.getPersister(); + final EventSource session = event.getSession(); + boolean isDirty = false; + if ( entry.getStatus() != Status.DELETED ) { if ( callbackRegistry.preUpdate( entity ) ) { isDirty = copyState( entity, persister.getPropertyTypes(), values, session.getFactory() ); } } - final boolean answerFromInterceptor = session.getInterceptor().onFlushDirty( + final boolean stateModified = session.getInterceptor().onFlushDirty( entity, - entry.getId(), + id, values, entry.getLoadedState(), persister.getPropertyNames(), persister.getPropertyTypes() ); - return answerFromInterceptor || isDirty; + return stateModified || isDirty; } - private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactory sf) { + private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactoryImplementor factory) { // copy the entity state into the state array and return true if the state has changed - final Object[] newState = sf.unwrap( SessionFactoryImplementor.class ) - .getRuntimeMetamodels() - .getEntityMappingType( entity.getClass() ) - .getEntityPersister() - .getValues( entity ); - - int size = newState.length; + final Object[] newState = currentState( entity, factory ); boolean isDirty = false; - for ( int index = 0; index < size; index++ ) { - if ( ( state[index] == LazyPropertyInitializer.UNFETCHED_PROPERTY && - newState[index] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) || - ( state[index] != newState[index] && !types[index].isEqual( state[index], newState[index] ) ) ) { + for ( int index = 0, size = newState.length; index < size; index++ ) { + if ( isDirty( types[index], state[index], newState[index] ) ) { isDirty = true; state[index] = newState[index]; } @@ -374,58 +378,55 @@ private boolean copyState(Object entity, Type[] types, Object[] state, SessionFa return isDirty; } + private static Object[] currentState(Object entity, SessionFactoryImplementor factory) { + return factory.getRuntimeMetamodels() + .getEntityMappingType( entity.getClass() ) + .getEntityPersister() + .getValues( entity ); + } + + private static boolean isDirty(Type types, Object state, Object newState) { + return state == UNFETCHED_PROPERTY && newState != UNFETCHED_PROPERTY + || state != newState && !types.isEqual( state, newState ); + } + /** * Convenience method to retrieve an entities next version value */ private Object getNextVersion(FlushEntityEvent event) throws HibernateException { - - EntityEntry entry = event.getEntityEntry(); - EntityPersister persister = entry.getPersister(); + final EntityEntry entry = event.getEntityEntry(); + final EntityPersister persister = entry.getPersister(); if ( persister.isVersioned() ) { - - Object[] values = event.getPropertyValues(); - + final Object[] values = event.getPropertyValues(); if ( entry.isBeingReplicated() ) { - return Versioning.getVersion( values, persister ); + return getVersion( values, persister ); } else { - int[] dirtyProperties = event.getDirtyProperties(); - - final boolean isVersionIncrementRequired = isVersionIncrementRequired( - event, - entry, - persister, - dirtyProperties - ); - - final Object nextVersion = isVersionIncrementRequired - ? Versioning.increment( entry.getVersion(), persister.getVersionMapping(), event.getSession() ) + final Object nextVersion = isVersionIncrementRequired( event, entry ) + ? incrementVersion( event.getEntity(), entry.getVersion(), persister, event.getSession() ) : entry.getVersion(); //use the current version - - Versioning.setVersion( values, nextVersion, persister ); - + setVersion( values, nextVersion, persister ); return nextVersion; } } else { return null; } - } - private boolean isVersionIncrementRequired( - FlushEntityEvent event, - EntityEntry entry, - EntityPersister persister, - int[] dirtyProperties) { - return entry.getStatus() != Status.DELETED && ( - dirtyProperties == null || - Versioning.isVersionIncrementRequired( - dirtyProperties, - event.hasDirtyCollection(), - persister.getPropertyVersionability() - ) - ); + private boolean isVersionIncrementRequired(FlushEntityEvent event, EntityEntry entry) { + if ( entry.getStatus() == Status.DELETED ) { + return false; + } + else { + int[] dirtyProperties = event.getDirtyProperties(); + return dirtyProperties == null + || Versioning.isVersionIncrementRequired( + dirtyProperties, + event.hasDirtyCollection(), + event.getEntityEntry().getPersister().getPropertyVersionability() + ); + } } /** @@ -434,29 +435,17 @@ private boolean isVersionIncrementRequired( * Note: this method is quite slow, avoid calling if possible! */ protected final boolean isUpdateNecessary(FlushEntityEvent event) throws HibernateException { + return !event.isDirtyCheckPossible() + || event.hasDirtyProperties() + || hasDirtyCollections(event); - EntityPersister persister = event.getEntityEntry().getPersister(); - Status status = event.getEntityEntry().getStatus(); - - if ( !event.isDirtyCheckPossible() ) { - return true; - } - else { - - int[] dirtyProperties = event.getDirtyProperties(); - if ( dirtyProperties != null && dirtyProperties.length != 0 ) { - return true; //TODO: suck into event class - } - else { - return hasDirtyCollections( event, persister, status ); - } - - } } - private boolean hasDirtyCollections(FlushEntityEvent event, EntityPersister persister, Status status) { - if ( isCollectionDirtyCheckNecessary( persister, status ) ) { - DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor( + private boolean hasDirtyCollections(FlushEntityEvent event) { + final EntityEntry entityEntry = event.getEntityEntry(); + final EntityPersister persister = entityEntry.getPersister(); + if ( isCollectionDirtyCheckNecessary( persister, entityEntry.getStatus() ) ) { + final DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor( event.getEntity(), event.getSession(), persister.getPropertyVersionability() @@ -472,136 +461,156 @@ private boolean hasDirtyCollections(FlushEntityEvent event, EntityPersister pers } private boolean isCollectionDirtyCheckNecessary(EntityPersister persister, Status status) { - return ( status == Status.MANAGED || status == Status.READ_ONLY ) && - persister.isVersioned() && - persister.hasCollections(); + return ( status == Status.MANAGED || status == Status.READ_ONLY ) + && persister.isVersioned() + && persister.hasCollections(); } /** * Perform a dirty check, and attach the results to the event */ protected void dirtyCheck(final FlushEntityEvent event) throws HibernateException { + int[] dirtyProperties = getDirtyProperties( event ); + event.setDatabaseSnapshot( null ); + if ( dirtyProperties == null ) { + // do the dirty check the hard way + dirtyProperties = performDirtyCheck( event ); + } + else { + // the Interceptor, SelfDirtinessTracker, or CustomEntityDirtinessStrategy + // already handled the dirty check for us + event.setDirtyProperties( dirtyProperties ); + event.setDirtyCheckHandledByInterceptor( true ); + event.setDirtyCheckPossible( true ); + } + logDirtyProperties( event.getEntityEntry(), dirtyProperties ); + } - final Object entity = event.getEntity(); - final Object[] values = event.getPropertyValues(); + private static int[] performDirtyCheck(FlushEntityEvent event) { final SessionImplementor session = event.getSession(); - final EntityEntry entry = event.getEntityEntry(); - final EntityPersister persister = entry.getPersister(); - final Object id = entry.getId(); - final Object[] loadedState = entry.getLoadedState(); - - int[] dirtyProperties = session.getInterceptor().findDirty( - entity, - id, - values, - loadedState, - persister.getPropertyNames(), - persister.getPropertyTypes() - ); - - if ( dirtyProperties == null ) { - if ( ManagedTypeHelper.isSelfDirtinessTracker( entity ) ) { - final SelfDirtinessTracker tracker = ManagedTypeHelper.asSelfDirtinessTracker( entity ); - if ( tracker.$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) { - dirtyProperties = persister.resolveDirtyAttributeIndexes( - values, - loadedState, - tracker.$$_hibernate_getDirtyAttributes(), - session + boolean dirtyCheckPossible; + int[] dirtyProperties = null; + try { + session.getEventListenerManager().dirtyCalculationStart(); + // object loaded by update() + final EntityEntry entry = event.getEntityEntry(); + final EntityPersister persister = entry.getPersister(); + final Object[] values = event.getPropertyValues(); + final Object[] loadedState = entry.getLoadedState(); + final Object entity = event.getEntity(); + if ( loadedState != null ) { + // dirty check against the usual snapshot of the entity + dirtyProperties = persister.findDirty( values, loadedState, entity, session ); + dirtyCheckPossible = true; + } + else if ( entry.getStatus() == Status.DELETED && !entry.isModifiableEntity() ) { + // A non-modifiable (e.g., read-only or immutable) entity needs to be have + // references to transient entities set to null before being deleted. No other + // fields should be updated. + if ( values != entry.getDeletedState() ) { + throw new IllegalStateException( + "Entity has status Status.DELETED but values != entry.getDeletedState" ); } - else { - dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY; - } + // Even if loadedState == null, we can dirty-check by comparing currentState and + // entry.getDeletedState() because the only fields to be updated are those that + // refer to transient entities that are being set to null. + // - currentState contains the entity's current property values. + // - entry.getDeletedState() contains the entity's current property values with + // references to transient entities set to null. + // - dirtyProperties will only contain properties that refer to transient entities + final Object[] currentState = persister.getValues( event.getEntity() ); + dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session ); + dirtyCheckPossible = true; } else { - // see if the custom dirtiness strategy can tell us... - class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext { - private int[] found; - - @Override - public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) { - found = new DirtyCheckAttributeInfoImpl( event ).visitAttributes( attributeChecker ); - if ( found.length == 0 ) { - found = null; - } - } - } - DirtyCheckContextImpl context = new DirtyCheckContextImpl(); - session.getFactory().getCustomEntityDirtinessStrategy().findDirty( - entity, - persister, - session, - context - ); - dirtyProperties = context.found; - } - } - - event.setDatabaseSnapshot( null ); - - final boolean interceptorHandledDirtyCheck; - //The dirty check is considered possible unless proven otherwise (see below) - boolean dirtyCheckPossible = true; - - if ( dirtyProperties == null ) { - // Interceptor returned null, so do the dirtycheck ourself, if possible - try { - session.getEventListenerManager().dirtyCalculationStart(); - - interceptorHandledDirtyCheck = false; - // object loaded by update() - dirtyCheckPossible = loadedState != null; - if ( dirtyCheckPossible ) { - // dirty check against the usual snapshot of the entity - dirtyProperties = persister.findDirty( values, loadedState, entity, session ); - } - else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModifiableEntity() ) { - // A non-modifiable (e.g., read-only or immutable) entity needs to be have - // references to transient entities set to null before being deleted. No other - // fields should be updated. - if ( values != entry.getDeletedState() ) { - throw LOG.entityDeleteStateIllegal(); - } - // Even if loadedState == null, we can dirty-check by comparing currentState and - // entry.getDeletedState() because the only fields to be updated are those that - // refer to transient entities that are being set to null. - // - currentState contains the entity's current property values. - // - entry.getDeletedState() contains the entity's current property values with - // references to transient entities set to null. - // - dirtyProperties will only contain properties that refer to transient entities - final Object[] currentState = persister.getPropertyValues( event.getEntity() ); - dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session ); + // dirty check against the database snapshot, if possible/necessary + final Object[] databaseSnapshot = getDatabaseSnapshot( persister, entry.getId(), session ); + if ( databaseSnapshot != null ) { + dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session ); dirtyCheckPossible = true; + event.setDatabaseSnapshot( databaseSnapshot ); } else { - // dirty check against the database snapshot, if possible/necessary - final Object[] databaseSnapshot = getDatabaseSnapshot( session, persister, id ); - if ( databaseSnapshot != null ) { - dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session ); - dirtyCheckPossible = true; - event.setDatabaseSnapshot( databaseSnapshot ); - } + dirtyCheckPossible = false; } } - finally { - session.getEventListenerManager().dirtyCalculationEnd( dirtyProperties != null ); - } + event.setDirtyProperties( dirtyProperties ); + event.setDirtyCheckHandledByInterceptor( false ); + event.setDirtyCheckPossible( dirtyCheckPossible ); + } + finally { + session.getEventListenerManager().dirtyCalculationEnd( dirtyProperties != null ); + } + return dirtyProperties; + } + + /** + * Attempt to get the dirty properties from either the Interceptor, + * the bytecode enhancement, or a custom dirtiness strategy. + */ + private static int[] getDirtyProperties(FlushEntityEvent event) { + int[] dirtyProperties = getDirtyPropertiesFromInterceptor( event ); + if ( dirtyProperties != null ) { + return dirtyProperties; } else { - // either the Interceptor, the bytecode enhancement or a custom dirtiness strategy handled the dirty checking - interceptorHandledDirtyCheck = true; + final Object entity = event.getEntity(); + return isSelfDirtinessTracker( entity ) + ? getDirtyPropertiesFromSelfDirtinessTracker( asSelfDirtinessTracker( entity ), event ) + : getDirtyPropertiesFromCustomEntityDirtinessStrategy( event ); } + } - logDirtyProperties( id, dirtyProperties, persister ); + private static int[] getDirtyPropertiesFromInterceptor(FlushEntityEvent event) { + final EntityEntry entry = event.getEntityEntry(); + final EntityPersister persister = entry.getPersister(); + return event.getSession().getInterceptor().findDirty( + event.getEntity(), + entry.getId(), + event.getPropertyValues(), + entry.getLoadedState(), + persister.getPropertyNames(), + persister.getPropertyTypes() + ); + } - event.setDirtyProperties( dirtyProperties ); - event.setDirtyCheckHandledByInterceptor( interceptorHandledDirtyCheck ); - event.setDirtyCheckPossible( dirtyCheckPossible ); + private static int[] getDirtyPropertiesFromCustomEntityDirtinessStrategy(FlushEntityEvent event) { + // see if the custom dirtiness strategy can tell us... + class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext { + private int[] found; + @Override + public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) { + found = new DirtyCheckAttributeInfoImpl( event ).visitAttributes( attributeChecker ); + if ( found.length == 0 ) { + found = null; + } + } + } + final EventSource session = event.getSession(); + final DirtyCheckContextImpl context = new DirtyCheckContextImpl(); + session.getFactory().getCustomEntityDirtinessStrategy() + .findDirty( event.getEntity(), event.getEntityEntry().getPersister(), session, context ); + return context.found; + } + private static int[] getDirtyPropertiesFromSelfDirtinessTracker(SelfDirtinessTracker tracker, FlushEntityEvent event) { + final EntityEntry entry = event.getEntityEntry(); + final EntityPersister persister = entry.getPersister(); + if ( tracker.$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) { + return persister.resolveDirtyAttributeIndexes( + event.getPropertyValues(), + entry.getLoadedState(), + tracker.$$_hibernate_getDirtyAttributes(), + event.getSession() + ); + } + else { + return ArrayHelper.EMPTY_INT_ARRAY; + } } - private class DirtyCheckAttributeInfoImpl implements CustomEntityDirtinessStrategy.AttributeInformation { + private static class DirtyCheckAttributeInfoImpl implements CustomEntityDirtinessStrategy.AttributeInformation { private final FlushEntityEvent event; private final EntityPersister persister; private final int numberOfAttributes; @@ -643,7 +652,7 @@ public Object getCurrentValue() { @Override public Object getLoadedValue() { if ( databaseSnapshot == null ) { - databaseSnapshot = getDatabaseSnapshot( event.getSession(), persister, event.getEntityEntry().getId() ); + databaseSnapshot = getDatabaseSnapshot( persister, event.getEntityEntry().getId(), event.getSession() ); } return databaseSnapshot[index]; } @@ -663,8 +672,9 @@ public int[] visitAttributes(CustomEntityDirtinessStrategy.AttributeChecker attr } } - private void logDirtyProperties(Object id, int[] dirtyProperties, EntityPersister persister) { + private void logDirtyProperties(EntityEntry entry, int[] dirtyProperties) { if ( dirtyProperties != null && dirtyProperties.length > 0 && LOG.isTraceEnabled() ) { + final EntityPersister persister = entry.getPersister(); final String[] allPropertyNames = persister.getPropertyNames(); final String[] dirtyPropertyNames = new String[dirtyProperties.length]; for ( int i = 0; i < dirtyProperties.length; i++ ) { @@ -672,30 +682,28 @@ private void logDirtyProperties(Object id, int[] dirtyProperties, EntityPersiste } LOG.tracev( "Found dirty properties [{0}] : {1}", - infoString( persister.getEntityName(), id ), + infoString( persister.getEntityName(), entry.getId() ), Arrays.toString( dirtyPropertyNames ) ); } } - private Object[] getDatabaseSnapshot(SessionImplementor session, EntityPersister persister, Object id) { + private static Object[] getDatabaseSnapshot(EntityPersister persister, Object id, SessionImplementor session) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); if ( persister.isSelectBeforeUpdateRequired() ) { - Object[] snapshot = persistenceContext.getDatabaseSnapshot( id, persister ); + final Object[] snapshot = persistenceContext.getDatabaseSnapshot( id, persister ); if ( snapshot == null ) { //do we even really need this? the update will fail anyway.... final StatisticsImplementor statistics = session.getFactory().getStatistics(); if ( statistics.isStatisticsEnabled() ) { - statistics - .optimisticFailure( persister.getEntityName() ); + statistics.optimisticFailure( persister.getEntityName() ); } throw new StaleObjectStateException( persister.getEntityName(), id ); } return snapshot; } // TODO: optimize away this lookup for entities w/o unsaved-value="undefined" - final EntityKey entityKey = session.generateEntityKey( id, persister ); - return persistenceContext.getCachedDatabaseSnapshot( entityKey ); + return persistenceContext.getCachedDatabaseSnapshot( session.generateEntityKey( id, persister ) ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEventListener.java index 9b3f67154e..d850ce3e26 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveFlushEventListener.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; @@ -14,8 +13,7 @@ import org.hibernate.event.spi.FlushEvent; import org.hibernate.event.spi.FlushEventListener; import org.hibernate.reactive.event.ReactiveFlushEventListener; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -26,14 +24,13 @@ public class DefaultReactiveFlushEventListener extends AbstractReactiveFlushingEventListener implements ReactiveFlushEventListener, FlushEventListener { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - @Override public CompletionStage reactiveOnFlush(FlushEvent event) throws HibernateException { final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); - if ( persistenceContext.getNumberOfManagedEntities() > 0 || persistenceContext.getCollectionEntriesSize() > 0 ) { + if ( persistenceContext.getNumberOfManagedEntities() > 0 + || persistenceContext.getCollectionEntriesSize() > 0 ) { source.getEventListenerManager().flushStart(); return flushEverythingToExecutions( event ) @@ -51,7 +48,13 @@ public CompletionStage reactiveOnFlush(FlushEvent event) throws HibernateE } } ); } - return voidFuture(); + else if ( ((ReactiveSession) source).getReactiveActionQueue().hasAnyQueuedActions() ) { + // execute any queued unloaded-entity deletions + return performExecutions( source ); + } + else { + return voidFuture(); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java index c7b15c4092..1035075fbb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java @@ -42,9 +42,10 @@ public void onInitializeCollection(InitializeCollectionEvent event) throws Hiber * called by a collection that wants to initialize itself */ public CompletionStage onReactiveInitializeCollection(InitializeCollectionEvent event) throws HibernateException { - PersistentCollection collection = event.getCollection(); - SessionImplementor source = event.getSession(); - CollectionEntry ce = source.getPersistenceContextInternal().getCollectionEntry( collection ); + final PersistentCollection collection = event.getCollection(); + final SessionImplementor source = event.getSession(); + + final CollectionEntry ce = source.getPersistenceContextInternal().getCollectionEntry( collection ); if ( ce == null ) { throw LOG.collectionWasEvicted(); } @@ -55,50 +56,55 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection } final CollectionPersister loadedPersister = ce.getLoadedPersister(); + final Object loadedKey = ce.getLoadedKey(); if ( LOG.isTraceEnabled() ) { - LOG.tracev( "Initializing collection {0}", collectionInfoString( loadedPersister, collection, ce.getLoadedKey(), source ) ); + LOG.tracev( + "Initializing collection {0}", + collectionInfoString( loadedPersister, collection, loadedKey, source ) + ); LOG.trace( "Checking second-level cache" ); } - final boolean foundInCache = initializeCollectionFromCache( ce.getLoadedKey(), loadedPersister, collection, source ); + final boolean foundInCache = initializeCollectionFromCache( loadedKey, loadedPersister, collection, source ); if ( foundInCache ) { if ( LOG.isTraceEnabled() ) { LOG.trace( "Collection initialized from cache" ); } return voidFuture(); } - - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Collection not cached" ); + else { + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Collection not cached" ); + } + return ( (ReactiveCollectionPersister) loadedPersister ) + .reactiveInitialize( loadedKey, source ) + .thenApply( list -> { + handlePotentiallyEmptyCollection( collection, source, ce, loadedPersister ); + return list; + } ) + .thenAccept( list -> { + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Collection initialized" ); + } + + final StatisticsImplementor statistics = source.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.fetchCollection( loadedPersister.getRole() ); + } + } ); } - return ( (ReactiveCollectionPersister) loadedPersister ) - .reactiveInitialize( ce.getLoadedKey(), source ) - .thenApply( list -> { - handlePotentiallyEmptyCollection( collection, source, ce, ce.getLoadedPersister() ); - return list; - } ) - .thenAccept( list -> { - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Collection initialized" ); - } - - final StatisticsImplementor statistics = source.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.fetchCollection( loadedPersister.getRole() ); - } - } ); } private void handlePotentiallyEmptyCollection( PersistentCollection collection, SessionImplementor source, CollectionEntry ce, - CollectionPersister ceLoadedPersister) { + CollectionPersister loadedPersister) { if ( !collection.wasInitialized() ) { - collection.initializeEmptyCollection( ceLoadedPersister ); + collection.initializeEmptyCollection( loadedPersister ); ResultsHelper.finalizeCollectionLoading( source.getPersistenceContext(), - ceLoadedPersister, + loadedPersister, collection, ce.getLoadedKey(), true @@ -120,10 +126,11 @@ private void handlePotentiallyEmptyCollection( private boolean initializeCollectionFromCache( Object id, CollectionPersister persister, - PersistentCollection collection, + PersistentCollection collection, SessionImplementor source) { - if ( source.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( source ) ) { + if ( source.getLoadQueryInfluencers().hasEnabledFilters() + && persister.isAffectedByEnabledFilters( source ) ) { LOG.trace( "Disregarding cached version (if any) of collection due to enabled filters" ); return false; } @@ -153,7 +160,8 @@ private boolean initializeCollectionFromCache( return false; } - CollectionCacheEntry cacheEntry = (CollectionCacheEntry) persister.getCacheEntryStructure().destructure( ce, factory ); + final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) + persister.getCacheEntryStructure().destructure( ce, factory ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); cacheEntry.assemble( collection, persister, persistenceContext.getCollectionOwner( id, persister ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java index 5e70038790..053c395af4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java @@ -21,12 +21,11 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; -import org.hibernate.event.spi.LoadEventListener; +import org.hibernate.event.spi.LoadEventListener;import org.hibernate.event.spi.LoadEventListener.LoadType; import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; @@ -82,9 +81,7 @@ public class DefaultReactiveLoadEventListener implements LoadEventListener, Reac * @throws UnexpectedAccessToTheDatabase if it needs to load the entity from the db */ @Override - public void onLoad( - final LoadEvent event, - final LoadEventListener.LoadType loadType) throws HibernateException { + public void onLoad(LoadEvent event, LoadType loadType) throws HibernateException { final EntityPersister persister = getPersister( event ); if ( persister == null ) { @@ -130,10 +127,7 @@ public void onLoad( * @param event The load event to be handled. */ @Override - public CompletionStage reactiveOnLoad( - final LoadEvent event, - final LoadEventListener.LoadType loadType) throws HibernateException { - + public CompletionStage reactiveOnLoad(LoadEvent event, LoadType loadType) throws HibernateException { final ReactiveEntityPersister persister = (ReactiveEntityPersister) getPersister( event ); if ( persister == null ) { throw LOG.unableToLocatePersister( event.getEntityClassName() ); @@ -154,6 +148,7 @@ public CompletionStage reactiveOnLoad( .thenCompose( v -> { // if a pessimistic version increment was requested, we need // to go back to the database immediately and update the row + // we handle this here instead of in DefaultReactivePostLoadEventListener if ( event.getLockMode() == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { // TODO: should we call CachedDomainDataAccess.lockItem() ? return persister.reactiveLock( @@ -164,7 +159,9 @@ public CompletionStage reactiveOnLoad( event.getSession() ); } - return voidFuture(); + else { + return voidFuture(); + } } ); } @@ -172,7 +169,7 @@ private CompletionStage checkId(LoadEvent event, LoadType loadType, Entity final Class idClass = persister.getIdentifierType().getReturnedClass(); if ( idClass != null && !idClass.isInstance( event.getEntityId() ) - && !(event.getEntityId() instanceof DelayedPostInsertIdentifier) ) { + && !( event.getEntityId() instanceof DelayedPostInsertIdentifier ) ) { return checkIdClass( persister, event, loadType, idClass ); } return voidFuture(); @@ -180,21 +177,21 @@ private CompletionStage checkId(LoadEvent event, LoadType loadType, Entity protected EntityPersister getPersister(final LoadEvent event) { final Object instanceToLoad = event.getInstanceToLoad(); + final EventSource source = event.getSession(); if ( instanceToLoad != null ) { //the load() which takes an entity does not pass an entityName event.setEntityClassName( instanceToLoad.getClass().getName() ); - return event.getSession().getEntityPersister( null, instanceToLoad ); + return source.getEntityPersister( null, instanceToLoad ); } else { - return event.getSession().getFactory().getMappingMetamodel() - .findEntityDescriptor( event.getEntityClassName() ); + return source.getFactory().getMappingMetamodel().getEntityDescriptor( event.getEntityClassName() ); } } private CompletionStage doOnLoad( final EntityPersister persister, final LoadEvent event, - final LoadEventListener.LoadType loadType) { + final LoadType loadType) { final EventSource session = event.getSession(); final EntityKey keyToLoad = session.generateEntityKey( event.getEntityId(), persister ); @@ -204,17 +201,15 @@ private CompletionStage doOnLoad( return load( event, persister, keyToLoad, loadType ); } //return a proxy if appropriate - if ( event.getLockMode() == LockMode.NONE ) { - return proxyOrLoad( event, persister, keyToLoad, loadType ); - } - - return lockAndLoad( event, persister, keyToLoad, loadType, session ); + return event.getLockMode() == LockMode.NONE + ? proxyOrLoad( event, persister, keyToLoad, loadType ) + : lockAndLoad( event, persister, keyToLoad, loadType, session ); } private CompletionStage checkIdClass( final EntityPersister persister, final LoadEvent event, - final LoadEventListener.LoadType loadType, + final LoadType loadType, final Class idClass) { // we may have the kooky jpa requirement of allowing find-by-id where // "id" is the "simple pk value" of a dependent objects parent. This @@ -255,9 +250,13 @@ else if ( idMapping instanceof NonAggregatedIdentifierMapping ) { /* * See DefaultLoadEventListener#loadByDerivedIdentitySimplePkValue */ - private CompletionStage loadByDerivedIdentitySimplePkValue(LoadEvent event, LoadEventListener.LoadType options, - EntityPersister dependentPersister, CompositeIdentifierMapping dependentIdType, EntityPersister parentPersister) { - EventSource session = event.getSession(); + private CompletionStage loadByDerivedIdentitySimplePkValue( + LoadEvent event, + LoadType options, + EntityPersister dependentPersister, + CompositeIdentifierMapping dependentIdType, + EntityPersister parentPersister) { + final EventSource session = event.getSession(); final EntityKey parentEntityKey = session.generateEntityKey( event.getEntityId(), parentPersister ); return doLoad( event, parentPersister, parentEntityKey, options ) .thenApply( parent -> { @@ -317,32 +316,25 @@ else if ( isOptionalInstance && optional != event.getInstanceToLoad() ) { * * @return The result of the proxy/load operation. */ - private CompletionStage proxyOrLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadEventListener.LoadType options) { - final EventSource session = event.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - final boolean traceEnabled = LOG.isTraceEnabled(); - - if ( traceEnabled ) { - LOG.tracev( "Loading entity: {0}", infoString( persister, event.getEntityId(), factory ) ); + private CompletionStage proxyOrLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) { + if ( LOG.isTraceEnabled() ) { + LOG.tracev( + "Loading entity: {0}", + infoString( persister, event.getEntityId(), persister.getFactory() ) + ); } // Check for the case where we can use the entity itself as a proxy if ( hasBytecodeProxy( persister, options ) ) { return loadWithBytecodeProxy( event, persister, keyToLoad, options ); } - - if ( persister.hasProxy() ) { + else if ( persister.hasProxy() ) { return loadWithRegularProxy( event, persister, keyToLoad, options ); } - - // return a newly loaded object - return load( event, persister, keyToLoad, options ); - } - - // Copy of DefaultLoadEventLister#hasBytecodeProxy - private static boolean hasBytecodeProxy(EntityPersister persister, LoadType options) { - return options.isAllowProxyCreation() - && persister.getEntityPersister().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + else { + // no proxies, just return a newly loaded object + return load( event, persister, keyToLoad, options ); + } } private static boolean wasDeleted(PersistenceContext persistenceContext, Object existing) { @@ -360,18 +352,41 @@ private CompletionStage loadWithBytecodeProxy(LoadEvent event, EntityPer ? nullFuture() : completedFuture( managed ); } - - // if the entity defines a HibernateProxy factory, see if there is an - // existing proxy associated with the PC - and if so, use it - if ( persister.getRepresentationStrategy().getProxyFactory() != null ) { + else if ( persister.getRepresentationStrategy().getProxyFactory() != null ) { + // we have a HibernateProxy factory, this case is more complicated return loadWithProxyFactory( event, persister, keyToLoad ); } + else if ( !persister.hasSubclasses() ) { + // the entity class has subclasses and there is no HibernateProxy factory + return load( event, persister, keyToLoad, options ); + } + else { + // no HibernateProxy factory, and no subclasses + return completedFuture( createBatchLoadableEnhancedProxy( persister, keyToLoad, session ) ); + } + } - if ( !persister.hasSubclasses() ) { + private CompletionStage loadWithRegularProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) { + // This is the case where the proxy is a separate object: + // look for a proxy + final Object proxy = event.getSession().getPersistenceContextInternal().getProxy( keyToLoad ); + if ( proxy != null ) { + // narrow the existing proxy to the type we're looking for + return returnNarrowedProxy( event, persister, keyToLoad, options, proxy ); + } + else if ( options.isAllowProxyCreation() ) { + // return a new proxy + // ORM calls DefaultLoadEventListener#proxyOrCache + return completedFuture( createProxyIfNecessary( event, persister, keyToLoad, options ) ); + } + else { return load( event, persister, keyToLoad, options ); } + } - return completedFuture( createBatchLoadableEnhancedProxy( persister, keyToLoad, session ) ); + private static boolean hasBytecodeProxy(EntityPersister persister, LoadType options) { + return options.isAllowProxyCreation() + && persister.getEntityPersister().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } private static CompletionStage loadWithProxyFactory(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) { @@ -387,18 +402,18 @@ private static CompletionStage loadWithProxyFactory(LoadEvent event, Ent return completedFuture( persistenceContext.narrowProxy( proxy, persister, keyToLoad, null ) ); } - - if ( persister.hasSubclasses() ) { + else if ( persister.hasSubclasses() ) { // specialized handling for entities with subclasses with a HibernateProxy factory // entities with subclasses that define a ProxyFactory can create a HibernateProxy // Maybe we can get it from the cache: Check DefaultLoadEventListener#proxyOrCache return completedFuture( createProxy( event, persister, keyToLoad ) ); } - - return completedFuture( createBatchLoadableEnhancedProxy( persister, keyToLoad, session ) ); + else { + // no existing proxy, and no subclasses + return completedFuture( createBatchLoadableEnhancedProxy( persister, keyToLoad, session ) ); + } } - private static PersistentAttributeInterceptable createBatchLoadableEnhancedProxy( EntityPersister persister, EntityKey keyToLoad, @@ -412,21 +427,6 @@ private static PersistentAttributeInterceptable createBatchLoadableEnhancedProxy return persister.getBytecodeEnhancementMetadata().createEnhancedProxy( keyToLoad, true, session ); } - private CompletionStage loadWithRegularProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) { - // look for a proxy - Object proxy = event.getSession().getPersistenceContextInternal().getProxy( keyToLoad ); - if ( proxy != null ) { - return returnNarrowedProxy( event, persister, keyToLoad, options, proxy ); - } - - if ( options.isAllowProxyCreation() ) { - // ORM calls DefaultLoadEventListener#proxyOrCache - return completedFuture( createProxyIfNecessary( event, persister, keyToLoad, options ) ); - } - - return load( event, persister, keyToLoad, options ); - } - private CompletionStage proxyImplementation(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) { return load( event, persister, keyToLoad, options ) .thenApply( optional -> { @@ -461,7 +461,7 @@ private CompletionStage returnNarrowedProxy( LoadEvent event, EntityPersister persister, EntityKey keyToLoad, - LoadEventListener.LoadType options, + LoadType options, Object proxy) { if ( LOG.isTraceEnabled() ) { LOG.trace( "Entity proxy found in session cache" ); @@ -501,7 +501,7 @@ private Object createProxyIfNecessary( LoadEvent event, EntityPersister persister, EntityKey keyToLoad, - LoadEventListener.LoadType options) { + LoadType options) { final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); final Object existing = persistenceContext.getEntity( keyToLoad ); final boolean traceEnabled = LOG.isTraceEnabled(); @@ -553,7 +553,7 @@ private CompletionStage lockAndLoad( LoadEvent event, EntityPersister persister, EntityKey keyToLoad, - LoadEventListener.LoadType options, + LoadType options, SessionImplementor source) { final boolean canWriteToCache = persister.canWriteToCache(); @@ -610,7 +610,7 @@ private CompletionStage doLoad( LoadEvent event, EntityPersister persister, EntityKey keyToLoad, - LoadEventListener.LoadType options) { + LoadType options) { final EventSource session = event.getSession(); final boolean traceEnabled = LOG.isTraceEnabled(); @@ -621,8 +621,8 @@ private CompletionStage doLoad( ); } - CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = ReactiveCacheEntityLoaderHelper.INSTANCE - .loadFromSessionCache( event, keyToLoad, options ); + final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = + ReactiveCacheEntityLoaderHelper.INSTANCE.loadFromSessionCache( event, keyToLoad, options ); Object entity = persistenceContextEntry.getEntity(); if ( entity != null ) { return persistenceContextEntry.isManaged() @@ -660,8 +660,7 @@ private CompletionStage doLoad( private void cacheNaturalId(LoadEvent event, EntityPersister persister, EventSource session, Object entity) { if ( entity != null && persister.hasNaturalIdentifier() ) { - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - persistenceContext.getNaturalIdResolutions() + session.getPersistenceContextInternal().getNaturalIdResolutions() .cacheResolutionFromLoad( event.getEntityId(), persister.getNaturalIdMapping().extractNaturalIdFromEntity( entity ), persister diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java index 7d22d8068c..4a2de4d2ba 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java @@ -25,6 +25,7 @@ import org.hibernate.event.spi.LockEvent; import org.hibernate.event.spi.LockEventListener; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.impl.Cascade; import org.hibernate.reactive.engine.impl.CascadingActions; import org.hibernate.reactive.engine.impl.ForeignKeys; @@ -59,7 +60,7 @@ public CompletionStage reactiveOnLock(LockEvent event) throws HibernateExc LOG.explicitSkipLockedLockCombo(); } - EventSource source = event.getSession(); + final EventSource source = event.getSession(); boolean detached = event.getEntityName() != null ? !source.contains( event.getEntityName(), event.getObject() ) @@ -80,35 +81,35 @@ public CompletionStage reactiveOnLock(LockEvent event) throws HibernateExc private CompletionStage reactiveOnLock(LockEvent event, Object entity) { - SessionImplementor source = event.getSession(); - PersistenceContext persistenceContext = source.getPersistenceContextInternal(); - - EntityEntry entry = persistenceContext.getEntry(entity); - CompletionStage stage; - if (entry==null) { - final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity); - final Object id = persister.getIdentifier(entity, source); - stage = ForeignKeys.isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source).thenApply( - trans -> { - if (!trans) { - throw new TransientObjectException( - "cannot lock an unsaved transient instance: " + - persister.getEntityName() - ); - } + final SessionImplementor source = event.getSession(); + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); - EntityEntry e = reassociate(event, entity, id, persister); - cascadeOnLock(event, persister, entity); - return e; - } - ); + final EntityEntry entry = persistenceContext.getEntry(entity); + final CompletionStage stage; + if ( entry==null ) { + final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); + final Object id = persister.getIdentifier( entity, source ); + stage = ForeignKeys.isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source) + .thenApply( trans -> { + if (!trans) { + throw new TransientObjectException( + "cannot lock an unsaved transient instance: " + + persister.getEntityName() + ); + } + + final EntityEntry e = reassociate( event, entity, id, persister ); + cascadeOnLock( event, persister, entity ); + return e; + } + ); } else { stage = completedFuture( entry ); } - return stage.thenCompose( e -> upgradeLock(entity, e, event.getLockOptions(), event.getSession() ) ); + return stage.thenCompose( e -> upgradeLock( entity, e, event.getLockOptions(), event.getSession() ) ); } private void cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) { @@ -138,11 +139,12 @@ private void cascadeOnLock(LockEvent event, EntityPersister persister, Object en * @param lockOptions contains the requested lock mode. * @param source The session which is the source of the event being processed. */ - protected CompletionStage upgradeLock(Object object, EntityEntry entry, - LockOptions lockOptions, - EventSource source) { - - LockMode requestedLockMode = lockOptions.getLockMode(); + protected CompletionStage upgradeLock( + Object object, + EntityEntry entry, + LockOptions lockOptions, + EventSource source) { + final LockMode requestedLockMode = lockOptions.getLockMode(); if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { // The user requested a "greater" (i.e. more restrictive) form of // pessimistic lock @@ -163,15 +165,14 @@ protected CompletionStage upgradeLock(Object object, EntityEntry entry, ); } - switch (requestedLockMode) { + final ReactiveActionQueue actionQueue = ((ReactiveSession) source).getReactiveActionQueue(); + switch ( requestedLockMode ) { case OPTIMISTIC: - ( (ReactiveSession) source ).getReactiveActionQueue() - .registerProcess( new ReactiveEntityVerifyVersionProcess(object) ); + actionQueue.registerProcess( new ReactiveEntityVerifyVersionProcess( object ) ); entry.setLockMode( requestedLockMode ); return voidFuture(); case OPTIMISTIC_FORCE_INCREMENT: - ( (ReactiveSession) source ).getReactiveActionQueue() - .registerProcess( new ReactiveEntityIncrementVersionProcess(object) ); + actionQueue.registerProcess( new ReactiveEntityIncrementVersionProcess( object ) ); entry.setLockMode( requestedLockMode ); return voidFuture(); default: diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java index 1995f47efd..33dafbc2b1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java @@ -27,13 +27,11 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.internal.EntityState; import org.hibernate.event.internal.EventUtil; import org.hibernate.event.internal.WrapVisitor; import org.hibernate.event.spi.EntityCopyObserver; -import org.hibernate.event.spi.EntityCopyObserverFactory; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.MergeContext; import org.hibernate.event.spi.MergeEvent; @@ -57,9 +55,10 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; -import static org.hibernate.event.internal.EntityState.DETACHED; +import static org.hibernate.event.internal.EntityState.getEntityState; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.type.ForeignKeyDirection.FROM_PARENT; @@ -95,20 +94,19 @@ protected Map getMergeMap(MergeContext context) { */ @Override public CompletionStage reactiveOnMerge(MergeEvent event) throws HibernateException { - final EntityCopyObserver entityCopyObserver = createEntityCopyObserver( event.getSession().getFactory() ); - final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver ); + final EventSource session = event.getSession(); + final EntityCopyObserver entityCopyObserver = createEntityCopyObserver( session ); + final MergeContext mergeContext = new MergeContext( session, entityCopyObserver ); return reactiveOnMerge( event, mergeContext ) - .thenAccept( v -> entityCopyObserver.topLevelMergeComplete( event.getSession() ) ) + .thenAccept( v -> entityCopyObserver.topLevelMergeComplete( session ) ) .whenComplete( (v, e) -> { entityCopyObserver.clear(); mergeContext.clear(); } ); } - private EntityCopyObserver createEntityCopyObserver(SessionFactoryImplementor sessionFactory) { - return sessionFactory.getServiceRegistry() - .getService( EntityCopyObserverFactory.class ) - .createEntityCopyObserver(); + private EntityCopyObserver createEntityCopyObserver(final EventSource session) { + return session.getFactory().getFastSessionServices().entityCopyObserverFactory.createEntityCopyObserver(); } /** @@ -119,23 +117,20 @@ private EntityCopyObserver createEntityCopyObserver(SessionFactoryImplementor se @Override public CompletionStage reactiveOnMerge(MergeEvent event, MergeContext copiedAlready) throws HibernateException { - final EventSource source = event.getSession(); final Object original = event.getOriginal(); - // NOTE : `original` is the value being merged - if ( original != null ) { + final EventSource source = event.getSession(); final Object entity; - if ( original instanceof HibernateProxy ) { - LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer(); - if ( li.isUninitialized() ) { + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( original ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.isUninitialized() ) { LOG.trace( "Ignoring uninitialized proxy" ); - event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) ); - //EARLY EXIT! + event.setResult( source.load( lazyInitializer.getEntityName(), lazyInitializer.getInternalIdentifier() ) ); return voidFuture(); } else { - entity = li.getImplementation(); + entity = lazyInitializer.getImplementation(); } } else if ( isPersistentAttributeInterceptable( original ) ) { @@ -157,76 +152,78 @@ else if ( isPersistentAttributeInterceptable( original ) ) { entity = original; } - if ( copiedAlready.containsKey( entity ) && ( copiedAlready.isOperatedOn( entity ) ) ) { - LOG.trace( "Already in merge process" ); - event.setResult( entity ); - } - else { - if ( copiedAlready.containsKey( entity ) ) { - LOG.trace( "Already in copyCache; setting in merge process" ); - copiedAlready.setOperatedOn( entity, true ); - } - event.setEntity( entity ); - EntityState entityState = null; - - // Check the persistence context for an entry relating to this - // entity to be merged... - final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); - EntityEntry entry = persistenceContext.getEntry( entity ); - if ( entry == null ) { - EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - Object id = persister.getIdentifier( entity, source ); - if ( id != null ) { - final EntityKey key = source.generateEntityKey( id, persister ); - final Object managedEntity = persistenceContext.getEntity( key ); - entry = persistenceContext.getEntry( managedEntity ); - if ( entry != null ) { - // we have specialized case of a detached entity from the - // perspective of the merge operation. Specifically, we - // have an incoming entity instance which has a corresponding - // entry in the current persistence context, but registered - // under a different entity instance - entityState = DETACHED; - } - } - } + return doMerge( event, copiedAlready, entity ); - if ( entityState == null ) { - entityState = EntityState.getEntityState( entity, event.getEntityName(), entry, source, false ); - } + } - switch ( entityState ) { - case DETACHED: - return entityIsDetached( event, copiedAlready ); - case TRANSIENT: - return entityIsTransient( event, copiedAlready ); - case PERSISTENT: - return entityIsPersistent( event, copiedAlready ); - default: //DELETED - throw new ObjectDeletedException( - "deleted instance passed to merge", - null, - EventUtil.getLoggableName( event.getEntityName(), entity ) - ); - } + return voidFuture(); + } + + private CompletionStage doMerge(MergeEvent event, MergeContext copiedAlready, Object entity) { + if ( copiedAlready.containsKey( entity ) && ( copiedAlready.isOperatedOn( entity ) ) ) { + LOG.trace( "Already in merge process" ); + event.setResult( entity ); + return voidFuture(); + } + else { + if ( copiedAlready.containsKey( entity ) ) { + LOG.trace( "Already in copyCache; setting in merge process" ); + copiedAlready.setOperatedOn( entity, true ); } + event.setEntity( entity ); + return merge( event, copiedAlready, entity ); + } + } + private CompletionStage merge(MergeEvent event, MergeContext copiedAlready, Object entity) { + switch ( entityState( event, entity ) ) { + case DETACHED: + return entityIsDetached( event, copiedAlready ); + case TRANSIENT: + return entityIsTransient( event, copiedAlready ); + case PERSISTENT: + return entityIsPersistent( event, copiedAlready ); + default: //DELETED + throw new ObjectDeletedException( + "deleted instance passed to merge", + null, + EventUtil.getLoggableName( event.getEntityName(), entity) + ); } + } - return voidFuture(); + private static EntityState entityState(MergeEvent event, Object entity) { + final EventSource source = event.getSession(); + // Check the persistence context for an entry relating to this + // entity to be merged... + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); + EntityEntry entry = persistenceContext.getEntry( entity ); + if ( entry == null ) { + EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); + Object id = persister.getIdentifier( entity, source ); + if ( id != null ) { + final Object managedEntity = persistenceContext.getEntity( source.generateEntityKey( id, persister ) ); + entry = persistenceContext.getEntry( managedEntity ); + if ( entry != null ) { + // we have a special case of a detached entity from the + // perspective of the merge operation. Specifically, we have + // an incoming entity instance which has a corresponding + // entry in the current persistence context, but registered + // under a different entity instance + return EntityState.DETACHED; + } + } + } + return getEntityState( entity, event.getEntityName(), entry, source, false ); } protected CompletionStage entityIsPersistent(MergeEvent event, MergeContext copyCache) { LOG.trace( "Ignoring persistent instance" ); - //TODO: check that entry.getIdentifier().equals(requestedId) - final Object entity = event.getEntity(); final EventSource source = event.getSession(); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - copyCache.put( entity, entity, true ); //before cascade! - return cascadeOnMerge( source, persister, entity, copyCache ) .thenCompose( v -> fetchAndCopyValues( persister, entity, entity, source, copyCache ) ) .thenAccept( v -> event.setResult( entity ) ); @@ -330,14 +327,10 @@ protected CompletionStage entityIsDetached(MergeEvent event, MergeContext final Object entity = event.getEntity(); final EventSource source = event.getSession(); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - final String entityName = persister.getEntityName(); - Object requestedId = event.getRequestedId(); Object id = getDetachedEntityId( event, entity, persister ); - // we must clone embedded composite identifiers, or we will get back the same instance that we pass in final Object clonedIdentifier = persister.getIdentifierType().deepCopy( id, source.getFactory() ); - return source.getLoadQueryInfluencers() .fromInternalFetchProfile( CascadingFetchProfile.MERGE, () -> source.unwrap( ReactiveSession.class ) .reactiveGet( (Class) persister.getMappedClass(), clonedIdentifier ) @@ -353,20 +346,21 @@ protected CompletionStage entityIsDetached(MergeEvent event, MergeContext // really persistent return entityIsTransient( event, copyCache ); } - - // before cascade! - copyCache.put( entity, result, true ); - final Object target = targetEntity( event, entity, persister, id, result ); - // cascade first, so that all unsaved objects get their - // copy created before we actually copy - return cascadeOnMerge( source, persister, entity, copyCache ) - .thenCompose( v -> fetchAndCopyValues( persister, entity, target, source, copyCache ) ) - .thenAccept( v -> { - // copyValues() (called by fetchAndCopyValues) works by reflection, - // so explicitly mark the entity instance dirty - markInterceptorDirty( entity, target ); - event.setResult( result ); - } ); + else { + // before cascade! + copyCache.put( entity, result, true ); + final Object target = targetEntity( event, entity, persister, id, result ); + // cascade first, so that all unsaved objects get their + // copy created before we actually copy + return cascadeOnMerge( source, persister, entity, copyCache ) + .thenCompose( v -> fetchAndCopyValues( persister, entity, target, source, copyCache ) ) + .thenAccept( v -> { + // copyValues() (called by fetchAndCopyValues) works by reflection, + // so explicitly mark the entity instance dirty + markInterceptorDirty( entity, target ); + event.setResult( result ); + } ); + } } ); } @@ -399,18 +393,17 @@ else if ( isVersionChanged( entity, source, persister, target ) ) { private static Object getDetachedEntityId(MergeEvent event, Object entity, EntityPersister persister) { final EventSource source = event.getSession(); final Object id = event.getRequestedId(); - if ( id == null ) { return persister.getIdentifier( entity, source ); } - - // check that entity id = requestedId - Object entityId = persister.getIdentifier( entity, source ); - if ( !persister.getIdentifierType().isEqual( id, entityId, source.getFactory() ) ) { - throw LOG.mergeRequestedIdNotMatchingIdOfPassedEntity(); + else { + // check that entity id = requestedId + final Object entityId = persister.getIdentifier( entity, source ); + if ( !persister.getIdentifierType().isEqual( id, entityId, source.getFactory() ) ) { + throw LOG.mergeRequestedIdNotMatchingIdOfPassedEntity(); + } + return id; } - - return id; } private static Object unproxyManagedForDetachedMerging( @@ -418,15 +411,17 @@ private static Object unproxyManagedForDetachedMerging( Object managed, EntityPersister persister, EventSource source) { - if ( managed instanceof HibernateProxy ) { + if ( isHibernateProxy( managed ) ) { return source.getPersistenceContextInternal().unproxy( managed ); } if ( isPersistentAttributeInterceptable( incoming ) && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { - final PersistentAttributeInterceptor incomingInterceptor = asPersistentAttributeInterceptable( incoming ).$$_hibernate_getInterceptor(); - final PersistentAttributeInterceptor managedInterceptor = asPersistentAttributeInterceptable( managed ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor incomingInterceptor = + asPersistentAttributeInterceptable( incoming ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor managedInterceptor = + asPersistentAttributeInterceptable( managed ).$$_hibernate_getInterceptor(); // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but // with different attributes initialized? @@ -463,27 +458,29 @@ private static void markInterceptorDirty(final Object entity, final Object targe } private static boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) { - if ( !persister.isVersioned() ) { + if ( persister.isVersioned() ) { + // for merging of versioned entities, we consider the version having + // been changed only when: + // 1) the two version values are different; + // *AND* + // 2) The target actually represents database state! + // + // This second condition is a special case which allows + // an entity to be merged during the same transaction + // (though during a seperate operation) in which it was + // originally persisted/saved + boolean changed = !persister.getVersionType().isSame( + persister.getVersion( target ), + persister.getVersion( entity ) + ); + + // TODO : perhaps we should additionally require that the incoming entity + // version be equivalent to the defined unsaved-value? + return changed && existsInDatabase( target, source, persister ); + } + else { return false; } - // for merging of versioned entities, we consider the version having - // been changed only when: - // 1) the two version values are different; - // *AND* - // 2) The target actually represents database state! - // - // This second condition is a special case which allows - // an entity to be merged during the same transaction - // (though during a seperate operation) in which it was - // originally persisted/saved - boolean changed = !persister.getVersionType().isSame( - persister.getVersion( target ), - persister.getVersion( entity ) - ); - - // TODO : perhaps we should additionally require that the incoming entity - // version be equivalent to the defined unsaved-value? - return changed && existsInDatabase( target, source, persister ); } private static boolean existsInDatabase(Object entity, EventSource source, EntityPersister persister) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePersistEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePersistEventListener.java index 53ae160b05..31ad8a1856 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePersistEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePersistEventListener.java @@ -31,6 +31,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import static org.hibernate.event.internal.EntityState.getEntityState; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -75,36 +76,54 @@ public void onPersist(PersistEvent event, PersistContext createdAlready) throws */ @Override public CompletionStage reactiveOnPersist(PersistEvent event, PersistContext createCache) { - final SessionImplementor source = event.getSession(); final Object object = event.getObject(); - final Object entity; - if ( object instanceof HibernateProxy ) { - LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer(); - if ( li.isUninitialized() ) { - if ( li.getSession() == source ) { - return voidFuture(); //NOTE EARLY EXIT! - } - else { - return failedFuture( new PersistentObjectException( "uninitialized proxy passed to persist()" ) ); - } + LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( object ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.isUninitialized() ) { + return lazyInitializer.getSession() == event.getSession() + ? voidFuture() + : failedFuture( new PersistentObjectException( "uninitialized proxy passed to persist()" ) ); + } + else { + return persist( event, createCache, lazyInitializer.getImplementation() ); } - entity = li.getImplementation(); } else { - entity = object; + return persist( event, createCache, object ); } + } - final String entityName; - if ( event.getEntityName() != null ) { - entityName = event.getEntityName(); - } - else { - entityName = source.bestGuessEntityName( entity ); - event.setEntityName( entityName ); + private CompletionStage persist(PersistEvent event, PersistContext createCache, Object entity) { + final SessionImplementor source = event.getSession(); + final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry(entity); + final String entityName = entityName( event, entity, entityEntry ); + switch ( entityState( event, entity, entityName, entityEntry ) ) { + case DETACHED: + return failedFuture( new PersistentObjectException( + "detached entity passed to persist: " + + EventUtil.getLoggableName(event.getEntityName(), entity) + ) ); + case PERSISTENT: + return entityIsPersistent(event, createCache); + case TRANSIENT: + return entityIsTransient(event, createCache); + case DELETED: + entityEntry.setStatus( Status.MANAGED ); + entityEntry.setDeletedState( null ); + event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() ); + return entityIsDeleted( event, createCache ); + default: + return failedFuture( new ObjectDeletedException( + "deleted entity passed to persist", + null, + EventUtil.getLoggableName( event.getEntityName(), entity ) + ) ); } + } - final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity ); - EntityState entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true ); + private static EntityState entityState(PersistEvent event, Object entity, String entityName, EntityEntry entityEntry) { + final EventSource source = event.getSession(); + EntityState entityState = getEntityState( entity, entityName, entityEntry, source, true ); if ( entityState == EntityState.DETACHED ) { // JPA 2, in its version of a "foreign generated", allows the id attribute value // to be manually set by the user, even though this manual value is irrelevant. @@ -115,59 +134,38 @@ public CompletionStage reactiveOnPersist(PersistEvent event, PersistContex // entity state again. // NOTE: entityEntry must be null to get here, so we cannot use any of its values - final EntityPersister persister = source.getFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityName ); - if (persister.getIdentifierGenerator() instanceof ForeignGenerator) { + final EntityPersister persister = source.getFactory().getMappingMetamodel() + .getEntityDescriptor(entityName); + if ( persister.getGenerator() instanceof ForeignGenerator ) { if ( LOG.isDebugEnabled() && persister.getIdentifier( entity, source ) != null ) { LOG.debug( "Resetting entity id attribute to null for foreign generator" ); } persister.setIdentifier( entity, null, source ); - entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true ); + entityState = getEntityState( entity, entityName, entityEntry, source, true ); } } + return entityState; + } - switch ( entityState ) { - case DETACHED: { - return failedFuture( new PersistentObjectException( - "detached entity passed to persist: " + - EventUtil.getLoggableName( event.getEntityName(), entity ) - ) ); - } - case PERSISTENT: { - return entityIsPersistent( event, createCache ); - } - case TRANSIENT: { - return entityIsTransient( event, createCache ); - } - case DELETED: { - entityEntry.setStatus( Status.MANAGED ); - entityEntry.setDeletedState( null ); - event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() ); - return entityIsDeleted( event, createCache ); - } - default: { - return failedFuture( new ObjectDeletedException( - "deleted entity passed to persist", - null, - EventUtil.getLoggableName( event.getEntityName(), entity ) - ) ); - } + private static String entityName(PersistEvent event, Object entity, EntityEntry entityEntry) { + if ( event.getEntityName() != null ) { + return event.getEntityName(); + } + else { + // changes event.entityName by side effect! + final String entityName = event.getSession().bestGuessEntityName( entity, entityEntry ); + event.setEntityName( entityName ); + return entityName; } } protected CompletionStage entityIsPersistent(PersistEvent event, PersistContext createCache) { LOG.trace( "Ignoring persistent instance" ); final EventSource source = event.getSession(); - //TODO: check that entry.getIdentifier().equals(requestedId) - final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() ); - final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - return createCache.add( entity ) - ? justCascade( createCache, source, entity, persister ) + ? justCascade( createCache, source, entity, source.getEntityPersister( event.getEntityName(), entity ) ) : voidFuture(); } @@ -185,10 +183,8 @@ private CompletionStage justCascade(PersistContext createCache, EventSourc */ protected CompletionStage entityIsTransient(PersistEvent event, PersistContext createCache) { LOG.trace( "Saving transient instance" ); - final EventSource source = event.getSession(); final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() ); - return createCache.add( entity ) ? reactiveSaveWithGeneratedId( entity, event.getEntityName(), createCache, source, false ) : voidFuture(); @@ -196,17 +192,14 @@ protected CompletionStage entityIsTransient(PersistEvent event, PersistCon private CompletionStage entityIsDeleted(PersistEvent event, PersistContext createCache) { final EventSource source = event.getSession(); - final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() ); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - if ( LOG.isTraceEnabled() ) { LOG.tracef( "un-scheduling entity deletion [%s]", infoString( persister, persister.getIdentifier( entity, source ), source.getFactory() ) ); } - return createCache.add( entity ) ? justCascade( createCache, source, entity, persister ) : voidFuture(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java index 8edc585953..49c62c182d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java @@ -6,20 +6,21 @@ package org.hibernate.reactive.event.impl; import org.hibernate.AssertionFailure; -import org.hibernate.LockMode; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.impl.ReactiveEntityIncrementVersionProcess; import org.hibernate.reactive.engine.impl.ReactiveEntityVerifyVersionProcess; import org.hibernate.reactive.session.ReactiveSession; /** - * We do 2 things here:
    + * We do two things here: + *
      *
    • Call {@link Lifecycle} interface if necessary
    • *
    • Perform needed {@link EntityEntry#getLockMode()} related processing
    • *
    @@ -41,20 +42,23 @@ public void onPostLoad(PostLoadEvent event) { callbackRegistry.postLoad( entity ); - final SessionImplementor session = event.getSession(); + final EventSource session = event.getSession(); final EntityEntry entry = session.getPersistenceContextInternal().getEntry( entity ); if ( entry == null ) { throw new AssertionFailure( "possible non-threadsafe access to the session" ); } - LockMode lockMode = entry.getLockMode(); - if ( LockMode.OPTIMISTIC_FORCE_INCREMENT.equals( lockMode ) ) { - ((ReactiveSession) session).getReactiveActionQueue() - .registerProcess( new ReactiveEntityIncrementVersionProcess( entity ) ); - } - else if ( LockMode.OPTIMISTIC.equals( lockMode ) ) { - ((ReactiveSession) session).getReactiveActionQueue() - .registerProcess( new ReactiveEntityVerifyVersionProcess( entity ) ); + final ReactiveActionQueue actionQueue = ((ReactiveSession) session).getReactiveActionQueue(); + switch ( entry.getLockMode() ) { +// case PESSIMISTIC_FORCE_INCREMENT: + // This case is handled by DefaultReactiveLoadEventListener + case OPTIMISTIC_FORCE_INCREMENT: + actionQueue.registerProcess( new ReactiveEntityIncrementVersionProcess( entity ) ); + break; + case OPTIMISTIC: + actionQueue.registerProcess( new ReactiveEntityVerifyVersionProcess( entity ) ); + break; } } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index 10a3167d56..4303aec7c3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -32,7 +32,7 @@ import org.hibernate.event.spi.RefreshEvent; import org.hibernate.event.spi.RefreshEventListener; import org.hibernate.loader.ast.spi.CascadingFetchProfile; -import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.engine.impl.Cascade; @@ -75,8 +75,7 @@ public void onRefresh(RefreshEvent event, RefreshContext refreshedAlready) throw */ @Override public CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContext refreshedAlready) { - - EventSource source = event.getSession(); + final EventSource source = event.getSession(); boolean detached = event.getEntityName() != null ? !source.contains( event.getEntityName(), event.getObject() ) @@ -91,8 +90,8 @@ public CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContex } private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContext refreshedAlready, Object entity) { - EventSource source = event.getSession(); - PersistenceContext persistenceContext = source.getPersistenceContextInternal(); + final EventSource source = event.getSession(); + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); if ( !refreshedAlready.add( entity) ) { LOG.trace( "Already refreshed" ); @@ -100,14 +99,12 @@ private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshConte } final EntityEntry entry = persistenceContext.getEntry( entity ); + final EntityPersister persister; final Object id; - if ( entry == null ) { - persister = source.getEntityPersister( - event.getEntityName(), - entity - ); //refresh() does not pass an entityName + //refresh() does not pass an entityName + persister = source.getEntityPersister( event.getEntityName(), entity ); id = persister.getIdentifier( entity, event.getSession() ); if ( LOG.isTraceEnabled() ) { LOG.tracev( @@ -141,55 +138,59 @@ private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshConte } // cascade the refresh prior to refreshing this entity - refreshedAlready.add( entity ); - - return cascadeRefresh(source, persister, entity, refreshedAlready) + return cascadeRefresh( source, persister, entity, refreshedAlready ) .thenCompose( v -> { if ( entry != null ) { final EntityKey key = source.generateEntityKey( id, persister ); persistenceContext.removeEntity( key ); if ( persister.hasCollections() ) { - new EvictVisitor(source, entity ).process( entity, persister ); + new EvictVisitor( source, entity ).process( entity, persister ); } } - if ( persister.canWriteToCache() ) { - Object previousVersion = null; - if ( persister.isVersionPropertyGenerated() ) { - // we need to grab the version value from the entity, otherwise - // we have issues with generated-version entities that may have - // multiple actions queued during the same flush - previousVersion = persister.getVersion( entity ); - } - final EntityDataAccess cache = persister.getCacheAccessStrategy(); - final Object ck = cache.generateCacheKey( - id, - persister, - source.getFactory(), - source.getTenantIdentifier() - ); - final SoftLock lock = cache.lockItem(source, ck, previousVersion ); - cache.remove(source, ck ); - source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); - } + evictEntity( entity, persister, id, source ); + evictCachedCollections( persister, id, source ); - evictCachedCollections( persister, id, source); - - CompletableFuture refresh = new CompletableFuture<>(); + final CompletableFuture refresh = new CompletableFuture<>(); source.getLoadQueryInfluencers() - .fromInternalFetchProfile( CascadingFetchProfile.REFRESH, () -> doRefresh( event, source, entity, entry, persister, id, persistenceContext ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - refresh.complete( null ); - } - else { - refresh.completeExceptionally( throwable ); - } - } ) ); + .fromInternalFetchProfile( + CascadingFetchProfile.REFRESH, + () -> doRefresh( event, source, entity, entry, persister, id, persistenceContext ) + .whenComplete( (unused, throwable) -> { + if ( throwable == null ) { + refresh.complete( null ); + } + else { + refresh.completeExceptionally( throwable ); + } + } ) + ); return refresh; } ); } + private static void evictEntity(Object entity, EntityPersister persister, Object id, EventSource source) { + if ( persister.canWriteToCache() ) { + Object previousVersion = null; + if ( persister.isVersionPropertyGenerated() ) { + // we need to grab the version value from the entity, otherwise + // we have issues with generated-version entities that may have + // multiple actions queued during the same flush + previousVersion = persister.getVersion( entity ); + } + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + final Object ck = cache.generateCacheKey( + id, + persister, + source.getFactory(), + source.getTenantIdentifier() + ); + final SoftLock lock = cache.lockItem( source, ck, previousVersion ); + cache.remove(source, ck ); + source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); + } + } + private static CompletionStage doRefresh( RefreshEvent event, EventSource source, @@ -198,23 +199,19 @@ private static CompletionStage doRefresh( EntityPersister persister, Object id, PersistenceContext persistenceContext) { - // Handle the requested lock-mode (if one) in relation to the entry's (if one) current lock-mode - LockOptions lockOptionsToUse = event.getLockOptions(); - final LockMode requestedLockMode = lockOptionsToUse.getLockMode(); final LockMode postRefreshLockMode; - if ( entry != null ) { final LockMode currentLockMode = entry.getLockMode(); if ( currentLockMode.greaterThan( requestedLockMode ) ) { // the requested lock-mode is less restrictive than the current one // - pass along the current lock-mode (after accounting for WRITE) - lockOptionsToUse = LockOptions.copy( event.getLockOptions(), new LockOptions() ); - if ( currentLockMode == LockMode.WRITE || - currentLockMode == LockMode.PESSIMISTIC_WRITE || - currentLockMode == LockMode.PESSIMISTIC_READ ) { + lockOptionsToUse = event.getLockOptions().makeCopy(); + if ( currentLockMode == LockMode.WRITE + || currentLockMode == LockMode.PESSIMISTIC_WRITE + || currentLockMode == LockMode.PESSIMISTIC_READ ) { // our transaction should already hold the exclusive lock on // the underlying row - so READ should be sufficient. // @@ -223,7 +220,6 @@ private static CompletionStage doRefresh( // WRITE specially because the Loader/Locker mechanism does not allow for WRITE // locks lockOptionsToUse.setLockMode( LockMode.READ ); - // and prepare to reset the entry lock-mode to the previous lock mode after // the refresh completes postRefreshLockMode = currentLockMode; @@ -247,7 +243,7 @@ private static CompletionStage doRefresh( if ( result != null ) { // apply `postRefreshLockMode`, if needed if ( postRefreshLockMode != null ) { - // if we get here, there was a previous entry and we need to re-set its lock-mode + // if we get here, there was a previous entry, and we need to re-set its lock-mode // - however, the refresh operation actually creates a new entry, so get it persistenceContext.getEntry( result ).setLockMode( postRefreshLockMode ); } @@ -290,10 +286,11 @@ private void evictCachedCollections(Type[] types, Object id, EventSource source) throws HibernateException { final ActionQueue actionQueue = source.getActionQueue(); final SessionFactoryImplementor factory = source.getFactory(); - final MetamodelImplementor metamodel = factory.getMetamodel(); + final MappingMetamodelImplementor metamodel = factory.getRuntimeMetamodels().getMappingMetamodel(); for ( Type type : types ) { if ( type.isCollectionType() ) { - CollectionPersister collectionPersister = metamodel.collectionPersister( ( (CollectionType) type ).getRole() ); + final String role = ((CollectionType) type).getRole(); + final CollectionPersister collectionPersister = metamodel.getCollectionDescriptor( role ); if ( collectionPersister.hasCache() ) { final CollectionDataAccess cache = collectionPersister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( @@ -308,8 +305,8 @@ private void evictCachedCollections(Type[] types, Object id, EventSource source) } } else if ( type.isComponentType() ) { - CompositeType actype = (CompositeType) type; - evictCachedCollections( actype.getSubtypes(), id, source ); + final CompositeType compositeType = (CompositeType) type; + evictCachedCollections( compositeType.getSubtypes(), id, source ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveResolveNaturalIdEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveResolveNaturalIdEventListener.java index 0ddb2d69c0..0cfb695455 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveResolveNaturalIdEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveResolveNaturalIdEventListener.java @@ -55,17 +55,23 @@ public CompletionStage onReactiveResolveNaturalId(ResolveNaturalIdEvent ev * @return The loaded entity, or null. */ protected CompletionStage resolveNaturalId(ResolveNaturalIdEvent event) { - EntityPersister persister = event.getEntityPersister(); + final EntityPersister persister = event.getEntityPersister(); if ( LOG.isTraceEnabled() ) { - LOG.tracev( "Attempting to resolve: {0}#{1}", infoString( persister ), event.getNaturalIdValues() + LOG.tracev( + "Attempting to resolve: {0}#{1}", + infoString( persister ), + event.getNaturalIdValues() ); } - Object entityId = resolveFromCache( event ); + final Object entityId = resolveFromCache( event ); if ( entityId != null ) { if ( LOG.isTraceEnabled() ) { - LOG.tracev( "Resolved object in cache: {0}#{1}", infoString( persister ), event.getNaturalIdValues() ); + LOG.tracev( + "Resolved object in cache: {0}#{1}", + infoString( persister ), + event.getNaturalIdValues() ); } return completedFuture( entityId ); } @@ -100,25 +106,25 @@ protected Object resolveFromCache(ResolveNaturalIdEvent event) { * * @return The object loaded from the datasource, or null if not found. */ - protected CompletionStage loadFromDatasource(final ResolveNaturalIdEvent event) { - EventSource session = event.getSession(); - StatisticsImplementor statistics = session.getFactory().getStatistics(); - boolean statisticsEnabled = statistics.isStatisticsEnabled(); - long startTime = statisticsEnabled ? System.nanoTime() : 0; + protected CompletionStage loadFromDatasource(ResolveNaturalIdEvent event) { + final EventSource session = event.getSession(); + final EntityPersister entityPersister = event.getEntityPersister(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + final boolean statisticsEnabled = statistics.isStatisticsEnabled(); + final long startTime = statisticsEnabled ? System.nanoTime() : 0; - EntityPersister entityPersister = event.getEntityPersister(); return ( (ReactiveEntityPersister) entityPersister) .reactiveLoadEntityIdByNaturalId( event.getOrderedNaturalIdValues(), event.getLockOptions(), session ) .thenApply( pk -> { - if (statisticsEnabled) { + if ( statisticsEnabled ) { long milliseconds = MILLISECONDS.convert( System.nanoTime() - startTime, NANOSECONDS ); statistics.naturalIdQueryExecuted( entityPersister.getRootEntityName(), milliseconds ); } //PK can be null if the entity doesn't exist - if (pk != null) { + if ( pk != null ) { getNaturalIdResolutions( event ) - .cacheResolutionFromLoad( pk, event.getOrderedNaturalIdValues(), event.getEntityPersister() ); + .cacheResolutionFromLoad( pk, event.getOrderedNaturalIdValues(), entityPersister ); } return pk; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 7fe85fa69d..188e02558a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -582,7 +582,7 @@ private static void checkMutationQuery(String hqlString, SqmStatement sqmStat public ReactiveMutationQuery createReactiveMutationQuery(CriteriaUpdate updateQuery) { checkOpen(); try { - return new ReactiveQuerySqmImpl( (SqmUpdateStatement) updateQuery, null, this ); + return new ReactiveQuerySqmImpl<>( (SqmUpdateStatement) updateQuery, null, this ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -593,7 +593,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(CriteriaUpdate u public ReactiveMutationQuery createReactiveMutationQuery(CriteriaDelete deleteQuery) { checkOpen(); try { - return new ReactiveQuerySqmImpl( (SqmDeleteStatement) deleteQuery, null, this ); + return new ReactiveQuerySqmImpl<>( (SqmDeleteStatement) deleteQuery, null, this ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e );