Skip to content

Commit 25792bc

Browse files
committed
HHH-9127 Invoked cache update and afterUpdate for forceIncrement
1 parent ddc8708 commit 25792bc

12 files changed

+246
-32
lines changed

hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
88
import org.hibernate.engine.spi.EntityEntry;
99
import org.hibernate.engine.spi.SessionImplementor;
10-
import org.hibernate.persister.entity.EntityPersister;
10+
import org.hibernate.internal.OptimisticLockHelper;
1111

1212
/**
1313
* A {@link BeforeTransactionCompletionProcess} implementation to verify and
@@ -41,8 +41,6 @@ public void doBeforeTransactionCompletion(SessionImplementor session) {
4141
return;
4242
}
4343

44-
final EntityPersister persister = entry.getPersister();
45-
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session );
46-
entry.forceLocked( object, nextVersion );
44+
OptimisticLockHelper.forceVersionIncrement( object, entry, session.asEventSource() );
4745
}
4846
}

hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.hibernate.LockMode;
99
import org.hibernate.engine.spi.EntityEntry;
1010
import org.hibernate.engine.spi.SharedSessionContractImplementor;
11+
import org.hibernate.internal.OptimisticLockHelper;
1112
import org.hibernate.persister.entity.EntityPersister;
1213

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

5251
/**

hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,10 @@ public void beforeTransactionCompletion() {
552552
// Execute completion actions only in transaction owner (aka parent session).
553553
if ( beforeTransactionProcesses != null ) {
554554
beforeTransactionProcesses.beforeTransactionCompletion();
555-
// `beforeTransactionCompletion()` can have added batch operations (e.g. to increment entity version)
556-
session.getJdbcCoordinator().executeBatch();
557555
}
556+
// Make sure to always execute pending batches before the transaction completes.
557+
// One such pending batch could be the pessimistic version increment for an entity
558+
session.getJdbcCoordinator().executeBatch();
558559
}
559560
}
560561

hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.hibernate.SimpleNaturalIdLoadAccess;
3131
import org.hibernate.Transaction;
3232
import org.hibernate.UnknownProfileException;
33+
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
3334
import org.hibernate.cache.spi.CacheTransactionSynchronization;
3435
import org.hibernate.collection.spi.PersistentCollection;
3536
import org.hibernate.engine.jdbc.LobCreator;
@@ -1152,6 +1153,11 @@ public ActionQueue getActionQueue() {
11521153
return delegate.getActionQueue();
11531154
}
11541155

1156+
@Override
1157+
public void registerProcess(AfterTransactionCompletionProcess process) {
1158+
delegate.registerProcess( process );
1159+
}
1160+
11551161
@Override
11561162
public Object instantiate(EntityPersister persister, Object id) throws HibernateException {
11571163
return delegate.instantiate( persister, id );

hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hibernate.LockMode;
1818
import org.hibernate.LockOptions;
1919
import org.hibernate.StatelessSession;
20+
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
2021
import org.hibernate.boot.spi.SessionFactoryOptions;
2122
import org.hibernate.dialect.Dialect;
2223
import org.hibernate.event.spi.EventSource;
@@ -592,6 +593,15 @@ default boolean isStatelessSession() {
592593
*/
593594
void lock(String entityName, Object child, LockOptions lockOptions);
594595

596+
/**
597+
* Registers the given process for execution after transaction completion.
598+
*
599+
* @param process The process to register
600+
* @since 7.0
601+
*/
602+
@Incubating
603+
void registerProcess(AfterTransactionCompletionProcess process);
604+
595605
/**
596606
* Attempts to load the entity from the second-level cache.
597607
*

hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.hibernate.LockOptions;
2222
import org.hibernate.SharedSessionContract;
2323
import org.hibernate.Transaction;
24+
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
2425
import org.hibernate.cache.spi.CacheTransactionSynchronization;
2526
import org.hibernate.collection.spi.PersistentCollection;
2627
import org.hibernate.engine.jdbc.LobCreator;
@@ -699,6 +700,11 @@ public void lock(String entityName, Object child, LockOptions lockOptions) {
699700
delegate.lock( entityName, child, lockOptions );
700701
}
701702

703+
@Override
704+
public void registerProcess(AfterTransactionCompletionProcess process) {
705+
delegate.registerProcess( process );
706+
}
707+
702708
@Override
703709
public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode) {
704710
return delegate.loadFromSecondLevelCache( persister, entityKey, instanceToLoad, lockMode );

hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.event.spi.EventSource;
1515
import org.hibernate.event.spi.PostLoadEvent;
1616
import org.hibernate.event.spi.PostLoadEventListener;
17+
import org.hibernate.internal.OptimisticLockHelper;
1718
import org.hibernate.jpa.event.spi.CallbackRegistry;
1819
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
1920
import org.hibernate.persister.entity.EntityPersister;
@@ -50,9 +51,7 @@ public void onPostLoad(PostLoadEvent event) {
5051
if ( persister.isVersioned() ) {
5152
switch ( lockMode ) {
5253
case PESSIMISTIC_FORCE_INCREMENT:
53-
final Object nextVersion =
54-
persister.forceVersionIncrement( entry.getId(), entry.getVersion(), false, session );
55-
entry.forceLocked( entity, nextVersion );
54+
OptimisticLockHelper.forceVersionIncrement( entity, entry, session );
5655
break;
5756
case OPTIMISTIC_FORCE_INCREMENT:
5857
session.getActionQueue().registerProcess( new EntityIncrementVersionProcess( entity ) );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal;
6+
7+
import org.hibernate.CacheMode;
8+
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
9+
import org.hibernate.cache.spi.access.EntityDataAccess;
10+
import org.hibernate.cache.spi.access.SoftLock;
11+
import org.hibernate.cache.spi.entry.CacheEntry;
12+
import org.hibernate.engine.spi.EntityEntry;
13+
import org.hibernate.engine.spi.SessionEventListenerManager;
14+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
15+
import org.hibernate.engine.spi.Status;
16+
import org.hibernate.event.monitor.spi.DiagnosticEvent;
17+
import org.hibernate.event.monitor.spi.EventMonitor;
18+
import org.hibernate.persister.entity.EntityPersister;
19+
import org.hibernate.stat.internal.StatsHelper;
20+
import org.hibernate.stat.spi.StatisticsImplementor;
21+
22+
public final class OptimisticLockHelper {
23+
24+
private OptimisticLockHelper() {
25+
//utility class, not to be constructed
26+
}
27+
28+
public static void forceVersionIncrement(Object object, EntityEntry entry, SharedSessionContractImplementor session) {
29+
final EntityPersister persister = entry.getPersister();
30+
final Object previousVersion = entry.getVersion();
31+
SoftLock lock = null;
32+
final Object cacheKey;
33+
if ( persister.canWriteToCache() ) {
34+
final EntityDataAccess cache = persister.getCacheAccessStrategy();
35+
cacheKey = cache.generateCacheKey(
36+
entry.getId(),
37+
persister,
38+
session.getFactory(),
39+
session.getTenantIdentifier()
40+
);
41+
lock = cache.lockItem( session, cacheKey, previousVersion );
42+
}
43+
else {
44+
cacheKey = null;
45+
}
46+
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), previousVersion, session );
47+
entry.forceLocked( object, nextVersion );
48+
if ( persister.canWriteToCache() ) {
49+
final Object cacheEntry = updateCacheItem(
50+
object,
51+
previousVersion,
52+
nextVersion,
53+
cacheKey,
54+
entry,
55+
persister,
56+
session
57+
);
58+
session.registerProcess( new CacheCleanupProcess(
59+
cacheKey,
60+
persister,
61+
previousVersion,
62+
nextVersion,
63+
lock,
64+
cacheEntry
65+
) );
66+
}
67+
}
68+
69+
private static Object updateCacheItem(Object entity, Object previousVersion, Object nextVersion, Object ck, EntityEntry entry, EntityPersister persister, SharedSessionContractImplementor session) {
70+
if ( isCacheInvalidationRequired( persister, session ) || entry.getStatus() != Status.MANAGED ) {
71+
persister.getCacheAccessStrategy().remove( session, ck );
72+
}
73+
else if ( session.getCacheMode().isPutEnabled() ) {
74+
//TODO: inefficient if that cache is just going to ignore the updated state!
75+
final CacheEntry ce = persister.buildCacheEntry( entity, entry.getLoadedState(), nextVersion, session );
76+
final Object cacheEntry = persister.getCacheEntryStructure().structure( ce );
77+
final boolean put = updateCache( persister, cacheEntry, previousVersion, nextVersion, ck, session );
78+
79+
final StatisticsImplementor statistics = session.getFactory().getStatistics();
80+
if ( put && statistics.isStatisticsEnabled() ) {
81+
statistics.entityCachePut(
82+
StatsHelper.getRootEntityRole( persister ),
83+
persister.getCacheAccessStrategy().getRegion().getName()
84+
);
85+
}
86+
return cacheEntry;
87+
}
88+
return null;
89+
}
90+
91+
private static boolean updateCache(EntityPersister persister, Object cacheEntry, Object previousVersion, Object nextVersion, Object ck, SharedSessionContractImplementor session) {
92+
final EventMonitor eventMonitor = session.getEventMonitor();
93+
final DiagnosticEvent cachePutEvent = eventMonitor.beginCachePutEvent();
94+
final EntityDataAccess cacheAccessStrategy = persister.getCacheAccessStrategy();
95+
boolean update = false;
96+
try {
97+
session.getEventListenerManager().cachePutStart();
98+
update = cacheAccessStrategy.update( session, ck, cacheEntry, nextVersion, previousVersion );
99+
return update;
100+
}
101+
finally {
102+
eventMonitor.completeCachePutEvent(
103+
cachePutEvent,
104+
session,
105+
cacheAccessStrategy,
106+
persister,
107+
update,
108+
EventMonitor.CacheActionDescription.ENTITY_UPDATE
109+
);
110+
session.getEventListenerManager().cachePutEnd();
111+
}
112+
}
113+
114+
private static boolean isCacheInvalidationRequired(
115+
EntityPersister persister,
116+
SharedSessionContractImplementor session) {
117+
// the cache has to be invalidated when CacheMode is equal to GET or IGNORE
118+
return persister.isCacheInvalidationRequired()
119+
|| session.getCacheMode() == CacheMode.GET
120+
|| session.getCacheMode() == CacheMode.IGNORE;
121+
}
122+
123+
private static class CacheCleanupProcess implements AfterTransactionCompletionProcess {
124+
private final Object cacheKey;
125+
private final EntityPersister persister;
126+
private final Object previousVersion;
127+
private final Object nextVersion;
128+
private final SoftLock lock;
129+
private final Object cacheEntry;
130+
131+
private CacheCleanupProcess(Object cacheKey, EntityPersister persister, Object previousVersion, Object nextVersion, SoftLock lock, Object cacheEntry) {
132+
this.cacheKey = cacheKey;
133+
this.persister = persister;
134+
this.previousVersion = previousVersion;
135+
this.nextVersion = nextVersion;
136+
this.lock = lock;
137+
this.cacheEntry = cacheEntry;
138+
}
139+
140+
@Override
141+
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
142+
final EntityDataAccess cache = persister.getCacheAccessStrategy();
143+
if ( cacheUpdateRequired( success, persister, session ) ) {
144+
cacheAfterUpdate( cache, cacheKey, session );
145+
}
146+
else {
147+
cache.unlockItem( session, cacheKey, lock );
148+
}
149+
}
150+
151+
private static boolean cacheUpdateRequired(boolean success, EntityPersister persister, SharedSessionContractImplementor session) {
152+
return success
153+
&& !persister.isCacheInvalidationRequired()
154+
&& session.getCacheMode().isPutEnabled();
155+
}
156+
157+
protected void cacheAfterUpdate(EntityDataAccess cache, Object ck, SharedSessionContractImplementor session) {
158+
final SessionEventListenerManager eventListenerManager = session.getEventListenerManager();
159+
final EventMonitor eventMonitor = session.getEventMonitor();
160+
final DiagnosticEvent cachePutEvent = eventMonitor.beginCachePutEvent();
161+
boolean put = false;
162+
try {
163+
eventListenerManager.cachePutStart();
164+
put = cache.afterUpdate( session, ck, cacheEntry, nextVersion, previousVersion, lock );
165+
}
166+
finally {
167+
eventMonitor.completeCachePutEvent(
168+
cachePutEvent,
169+
session,
170+
cache,
171+
persister,
172+
put,
173+
EventMonitor.CacheActionDescription.ENTITY_AFTER_UPDATE
174+
);
175+
final StatisticsImplementor statistics = session.getFactory().getStatistics();
176+
if ( put && statistics.isStatisticsEnabled() ) {
177+
statistics.entityCachePut(
178+
StatsHelper.getRootEntityRole( persister ),
179+
cache.getRegion().getName()
180+
);
181+
}
182+
eventListenerManager.cachePutEnd();
183+
}
184+
}
185+
}
186+
187+
}

hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java

+6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.hibernate.TypeMismatchException;
5757
import org.hibernate.UnknownProfileException;
5858
import org.hibernate.UnresolvableObjectException;
59+
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
5960
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
6061
import org.hibernate.collection.spi.PersistentCollection;
6162
import org.hibernate.engine.internal.PersistenceContexts;
@@ -1905,6 +1906,11 @@ public ActionQueue getActionQueue() {
19051906
return actionQueue;
19061907
}
19071908

1909+
@Override
1910+
public void registerProcess(AfterTransactionCompletionProcess process) {
1911+
getActionQueue().registerProcess( process );
1912+
}
1913+
19081914
@Override
19091915
public PersistenceContext getPersistenceContext() {
19101916
checkOpenOrWaitingForAutoClose();

0 commit comments

Comments
 (0)