From 205eb0dc3808e1c0b4689f5031780571a049d8e9 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 17 Dec 2024 17:38:00 +0100 Subject: [PATCH] HHH-17985 second-level cache invalidation for StatelessSession --- .../internal/StatelessSessionImpl.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index c1cbc184e2..90724f0335 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -19,7 +19,9 @@ import org.hibernate.UnresolvableObjectException; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.StatefulPersistenceContext; @@ -29,6 +31,7 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.event.monitor.spi.EventMonitor; @@ -100,6 +103,7 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen private final LoadQueryInfluencers influencers; private final PersistenceContext temporaryPersistenceContext; private final boolean connectionProvided; + private final List afterCompletions = new ArrayList<>(); public StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); @@ -269,6 +273,7 @@ public void delete(String entityName, Object entity) { if ( !firePreDelete(entity, id, persister) ) { getInterceptor().onDelete( entity, id, persister.getPropertyNames(), persister.getPropertyTypes() ); removeCollections( entity, id, persister ); + final Object ck = lockCacheItem( id, version, persister ); final EventMonitor eventMonitor = getEventMonitor(); final DiagnosticEvent event = eventMonitor.beginEntityDeleteEvent(); boolean success = false; @@ -279,6 +284,7 @@ public void delete(String entityName, Object entity) { finally { eventMonitor.completeEntityDeleteEvent( event, id, persister.getEntityName(), success, this ); } + removeCacheItem( ck, persister ); firePostDelete(entity, id, persister); final StatisticsImplementor statistics = getFactory().getStatistics(); if ( statistics.isStatisticsEnabled() ) { @@ -348,6 +354,7 @@ public void update(String entityName, Object entity) { } if ( !firePreUpdate(entity, id, state, persister) ) { getInterceptor().onUpdate( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() ); + final Object ck = lockCacheItem( id, oldVersion, persister ); final EventMonitor eventMonitor = getEventMonitor(); final DiagnosticEvent event = eventMonitor.beginEntityUpdateEvent(); boolean success = false; @@ -358,6 +365,7 @@ public void update(String entityName, Object entity) { finally { eventMonitor.completeEntityUpdateEvent( event, id, persister.getEntityName(), success, this ); } + removeCacheItem( ck, persister ); removeAndRecreateCollections( entity, id, persister ); firePostUpdate(entity, id, state, persister); final StatisticsImplementor statistics = getFactory().getStatistics(); @@ -417,6 +425,7 @@ public void upsert(String entityName, Object entity) { if ( !firePreUpsert(entity, id, state, persister) ) { getInterceptor().onUpsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() ); final Object oldVersion = versionToUpsert( entity, persister, state ); + final Object ck = lockCacheItem( id, oldVersion, persister ); final EventMonitor eventMonitor = getEventMonitor(); final DiagnosticEvent event = eventMonitor.beginEntityUpsertEvent(); boolean success = false; @@ -427,6 +436,7 @@ public void upsert(String entityName, Object entity) { finally { eventMonitor.completeEntityUpsertEvent( event, id, persister.getEntityName(), success, this ); } + removeCacheItem( ck, persister ); final StatisticsImplementor statistics = getFactory().getStatistics(); if ( statistics.isStatisticsEnabled() ) { statistics.upsertEntity( persister.getEntityName() ); @@ -1114,12 +1124,29 @@ public void beforeTransactionCompletion() { @Override public void afterTransactionCompletion(boolean successful, boolean delayed) { + processAfterCompletions(); afterTransactionCompletionEvents( successful ); if ( shouldAutoClose() && !isClosed() ) { managedClose(); } } + private void processAfterCompletions() { + for ( Runnable completion: afterCompletions ) { + try { + completion.run(); + } + catch (CacheException ce) { + LOG.unableToReleaseCacheLock( ce ); + // continue loop + } + catch (Exception e) { + throw new HibernateException( "Unable to perform afterTransactionCompletion callback: " + e.getMessage(), e ); + } + } + afterCompletions.clear(); + } + @Override public boolean isTransactionInProgress() { return connectionProvided || super.isTransactionInProgress(); @@ -1159,4 +1186,28 @@ public boolean isStatelessSession() { return true; } + protected Object lockCacheItem(Object id, Object previousVersion, EntityPersister persister) { + if ( persister.canWriteToCache() ) { + final SharedSessionContractImplementor session = getSession(); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + final Object ck = cache.generateCacheKey( + id, + persister, + session.getFactory(), + session.getTenantIdentifier() + ); + final SoftLock lock = cache.lockItem( session, ck, previousVersion ); + afterCompletions.add( () -> cache.unlockItem( this, ck, lock ) ); + return ck; + } + else { + return null; + } + } + + protected void removeCacheItem(Object ck, EntityPersister persister) { + if ( persister.canWriteToCache() ) { + persister.getCacheAccessStrategy().remove( this, ck ); + } + } }