Skip to content

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

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

Closed
wants to merge 2 commits into from
Closed
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.engine.spi.SharedSessionContractImplementor;
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, SharedSe
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 @@ -30,6 +30,7 @@
import org.hibernate.SimpleNaturalIdLoadAccess;
import org.hibernate.Transaction;
import org.hibernate.UnknownProfileException;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.cache.spi.CacheTransactionSynchronization;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.LobCreator;
Expand Down Expand Up @@ -1152,6 +1153,11 @@ public ActionQueue getActionQueue() {
return delegate.getActionQueue();
}

@Override
public void registerProcess(AfterTransactionCompletionProcess process) {
delegate.registerProcess( process );
}

@Override
public Object instantiate(EntityPersister persister, Object id) throws HibernateException {
return delegate.instantiate( persister, id );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.StatelessSession;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.dialect.Dialect;
import org.hibernate.event.spi.EventSource;
Expand Down Expand Up @@ -592,6 +593,15 @@ default boolean isStatelessSession() {
*/
void lock(String entityName, Object child, LockOptions lockOptions);

/**
* Registers the given process for execution after transaction completion.
*
* @param process The process to register
* @since 7.0
*/
@Incubating
void registerProcess(AfterTransactionCompletionProcess process);

/**
* Attempts to load the entity from the second-level cache.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.hibernate.LockOptions;
import org.hibernate.SharedSessionContract;
import org.hibernate.Transaction;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.cache.spi.CacheTransactionSynchronization;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.LobCreator;
Expand Down Expand Up @@ -699,6 +700,11 @@ public void lock(String entityName, Object child, LockOptions lockOptions) {
delegate.lock( entityName, child, lockOptions );
}

@Override
public void registerProcess(AfterTransactionCompletionProcess process) {
delegate.registerProcess( process );
}

@Override
public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode) {
return delegate.loadFromSecondLevelCache( persister, entityKey, instanceToLoad, lockMode );
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,187 @@
/*
* SPDX-License-Identifier: Apache-2.0
* 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.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, SharedSessionContractImplementor 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.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, SharedSessionContractImplementor 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, SharedSessionContractImplementor 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 @@ -56,6 +56,7 @@
import org.hibernate.TypeMismatchException;
import org.hibernate.UnknownProfileException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.internal.PersistenceContexts;
Expand Down Expand Up @@ -1905,6 +1906,11 @@ public ActionQueue getActionQueue() {
return actionQueue;
}

@Override
public void registerProcess(AfterTransactionCompletionProcess process) {
getActionQueue().registerProcess( process );
}

@Override
public PersistenceContext getPersistenceContext() {
checkOpenOrWaitingForAutoClose();
Expand Down
Loading