Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-9127 Invoked cache update and afterUpdate for forceIncrement #9750

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.internal.OptimisticLockHelper;

/**
* A {@link BeforeTransactionCompletionProcess} implementation to verify and
Expand Down Expand Up @@ -41,8 +41,6 @@ public void doBeforeTransactionCompletion(SessionImplementor session) {
return;
}

final EntityPersister persister = entry.getPersister();
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session );
entry.forceLocked( object, nextVersion );
OptimisticLockHelper.forceVersionIncrement( object, entry, session.asEventSource() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.hibernate.LockMode;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.event.spi.EventSource;
import org.hibernate.internal.OptimisticLockHelper;
import org.hibernate.persister.entity.EntityPersister;

/**
Expand Down Expand Up @@ -44,9 +45,7 @@ public void lock(Object id, Object version, Object object, int timeout, EventSou
throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" );
}
final EntityEntry entry = session.getPersistenceContextInternal().getEntry( object );
final EntityPersister persister = entry.getPersister();
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), false, session );
entry.forceLocked( object, nextVersion );
OptimisticLockHelper.forceVersionIncrement( object, entry, session );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,10 @@ public void beforeTransactionCompletion() {
// Execute completion actions only in transaction owner (aka parent session).
if ( beforeTransactionProcesses != null ) {
beforeTransactionProcesses.beforeTransactionCompletion();
// `beforeTransactionCompletion()` can have added batch operations (e.g. to increment entity version)
session.getJdbcCoordinator().executeBatch();
}
// Make sure to always execute pending batches before the transaction completes.
// One such pending batch could be the pessimistic version increment for an entity
session.getJdbcCoordinator().executeBatch();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.internal.OptimisticLockHelper;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
import org.hibernate.persister.entity.EntityPersister;
Expand Down Expand Up @@ -50,9 +51,7 @@ public void onPostLoad(PostLoadEvent event) {
if ( persister.isVersioned() ) {
switch ( lockMode ) {
case PESSIMISTIC_FORCE_INCREMENT:
final Object nextVersion =
persister.forceVersionIncrement( entry.getId(), entry.getVersion(), false, session );
entry.forceLocked( entity, nextVersion );
OptimisticLockHelper.forceVersionIncrement( entity, entry, session );
break;
case OPTIMISTIC_FORCE_INCREMENT:
session.getActionQueue().registerProcess( new EntityIncrementVersionProcess( entity ) );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.internal;

import org.hibernate.CacheMode;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionEventListenerManager;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.monitor.spi.DiagnosticEvent;
import org.hibernate.event.monitor.spi.EventMonitor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper;
import org.hibernate.stat.spi.StatisticsImplementor;

public final class OptimisticLockHelper {

private OptimisticLockHelper() {
//utility class, not to be constructed
}

public static void forceVersionIncrement(Object object, EntityEntry entry, EventSource session) {
final EntityPersister persister = entry.getPersister();
final Object previousVersion = entry.getVersion();
SoftLock lock = null;
final Object cacheKey;
if ( persister.canWriteToCache() ) {
final EntityDataAccess cache = persister.getCacheAccessStrategy();
cacheKey = cache.generateCacheKey(
entry.getId(),
persister,
session.getFactory(),
session.getTenantIdentifier()
);
lock = cache.lockItem( session, cacheKey, previousVersion );
}
else {
cacheKey = null;
}
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), previousVersion, session );
entry.forceLocked( object, nextVersion );
if ( persister.canWriteToCache() ) {
final Object cacheEntry = updateCacheItem(
object,
previousVersion,
nextVersion,
cacheKey,
entry,
persister,
session
);
session.getActionQueue().registerProcess( new CacheCleanupProcess(
cacheKey,
persister,
previousVersion,
nextVersion,
lock,
cacheEntry
) );
}
}

private static Object updateCacheItem(Object entity, Object previousVersion, Object nextVersion, Object ck, EntityEntry entry, EntityPersister persister, EventSource session) {
if ( isCacheInvalidationRequired( persister, session ) || entry.getStatus() != Status.MANAGED ) {
persister.getCacheAccessStrategy().remove( session, ck );
}
else if ( session.getCacheMode().isPutEnabled() ) {
//TODO: inefficient if that cache is just going to ignore the updated state!
final CacheEntry ce = persister.buildCacheEntry( entity, entry.getLoadedState(), nextVersion, session );
final Object cacheEntry = persister.getCacheEntryStructure().structure( ce );
final boolean put = updateCache( persister, cacheEntry, previousVersion, nextVersion, ck, session );

final StatisticsImplementor statistics = session.getFactory().getStatistics();
if ( put && statistics.isStatisticsEnabled() ) {
statistics.entityCachePut(
StatsHelper.getRootEntityRole( persister ),
persister.getCacheAccessStrategy().getRegion().getName()
);
}
return cacheEntry;
}
return null;
}

private static boolean updateCache(EntityPersister persister, Object cacheEntry, Object previousVersion, Object nextVersion, Object ck, EventSource session) {
final EventMonitor eventMonitor = session.getEventMonitor();
final DiagnosticEvent cachePutEvent = eventMonitor.beginCachePutEvent();
final EntityDataAccess cacheAccessStrategy = persister.getCacheAccessStrategy();
boolean update = false;
try {
session.getEventListenerManager().cachePutStart();
update = cacheAccessStrategy.update( session, ck, cacheEntry, nextVersion, previousVersion );
return update;
}
finally {
eventMonitor.completeCachePutEvent(
cachePutEvent,
session,
cacheAccessStrategy,
persister,
update,
EventMonitor.CacheActionDescription.ENTITY_UPDATE
);
session.getEventListenerManager().cachePutEnd();
}
}

private static boolean isCacheInvalidationRequired(
EntityPersister persister,
SharedSessionContractImplementor session) {
// the cache has to be invalidated when CacheMode is equal to GET or IGNORE
return persister.isCacheInvalidationRequired()
|| session.getCacheMode() == CacheMode.GET
|| session.getCacheMode() == CacheMode.IGNORE;
}

private static class CacheCleanupProcess implements AfterTransactionCompletionProcess {
private final Object cacheKey;
private final EntityPersister persister;
private final Object previousVersion;
private final Object nextVersion;
private final SoftLock lock;
private final Object cacheEntry;

private CacheCleanupProcess(Object cacheKey, EntityPersister persister, Object previousVersion, Object nextVersion, SoftLock lock, Object cacheEntry) {
this.cacheKey = cacheKey;
this.persister = persister;
this.previousVersion = previousVersion;
this.nextVersion = nextVersion;
this.lock = lock;
this.cacheEntry = cacheEntry;
}

@Override
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
final EntityDataAccess cache = persister.getCacheAccessStrategy();
if ( cacheUpdateRequired( success, persister, session ) ) {
cacheAfterUpdate( cache, cacheKey, session );
}
else {
cache.unlockItem( session, cacheKey, lock );
}
}

private static boolean cacheUpdateRequired(boolean success, EntityPersister persister, SharedSessionContractImplementor session) {
return success
&& !persister.isCacheInvalidationRequired()
&& session.getCacheMode().isPutEnabled();
}

protected void cacheAfterUpdate(EntityDataAccess cache, Object ck, SharedSessionContractImplementor session) {
final SessionEventListenerManager eventListenerManager = session.getEventListenerManager();
final EventMonitor eventMonitor = session.getEventMonitor();
final DiagnosticEvent cachePutEvent = eventMonitor.beginCachePutEvent();
boolean put = false;
try {
eventListenerManager.cachePutStart();
put = cache.afterUpdate( session, ck, cacheEntry, nextVersion, previousVersion, lock );
}
finally {
eventMonitor.completeCachePutEvent(
cachePutEvent,
session,
cache,
persister,
put,
EventMonitor.CacheActionDescription.ENTITY_AFTER_UPDATE
);
final StatisticsImplementor statistics = session.getFactory().getStatistics();
if ( put && statistics.isStatisticsEnabled() ) {
statistics.entityCachePut(
StatsHelper.getRootEntityRole( persister ),
cache.getRegion().getName()
);
}
eventListenerManager.cachePutEnd();
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.hibernate.event.monitor.spi.EventMonitor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.monitor.spi.DiagnosticEvent;
import org.hibernate.internal.OptimisticLockHelper;
import org.hibernate.internal.build.AllowReflection;
import org.hibernate.loader.LoaderLogging;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
Expand Down Expand Up @@ -104,9 +105,7 @@ public static void upgradeLock(Object object, EntityEntry entry, LockOptions loc

if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) {
// todo : should we check the current isolation mode explicitly?
final Object nextVersion =
persister.forceVersionIncrement( entry.getId(), entry.getVersion(), false, session );
entry.forceLocked( object, nextVersion );
OptimisticLockHelper.forceVersionIncrement( object, entry, session );
}
else {
if ( entry.isExistsInDatabase() ) {
Expand Down
Loading
Loading