Skip to content

Commit 0441fef

Browse files
committed
[#2332] SqlException, Reactive doesn't use JDBC when locking previously loaded entity
1 parent f9d4c84 commit 0441fef

File tree

3 files changed

+201
-11
lines changed

3 files changed

+201
-11
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.hibernate.event.spi.EventSource;
2828
import org.hibernate.event.spi.LoadEvent;
2929
import org.hibernate.event.spi.LoadEventListener;
30-
import org.hibernate.loader.internal.CacheLoadHelper;
3130
import org.hibernate.metamodel.mapping.AttributeMapping;
3231
import org.hibernate.metamodel.mapping.AttributeMappingsList;
3332
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
@@ -48,7 +47,7 @@
4847
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
4948
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
5049
import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSecondLevelCache;
51-
import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSessionCache;
50+
import static org.hibernate.reactive.loader.internal.ReactiveCacheLoadHelper.loadFromSessionCache;
5251
import static org.hibernate.pretty.MessageHelper.infoString;
5352
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
5453
import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound;
@@ -653,15 +652,17 @@ private CompletionStage<Object> doLoad(
653652
return nullFuture();
654653
}
655654
else {
656-
final CacheLoadHelper.PersistenceContextEntry persistenceContextEntry =
657-
loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() );
658-
final Object entity = persistenceContextEntry.entity();
659-
if ( entity != null ) {
660-
return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture();
661-
}
662-
else {
663-
return loadFromCacheOrDatasource( event, persister, keyToLoad );
664-
}
655+
return loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() ).thenCompose(
656+
persistenceContextEntry -> {
657+
final Object entity = persistenceContextEntry.entity();
658+
if ( entity != null ) {
659+
return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture();
660+
}
661+
else {
662+
return loadFromCacheOrDatasource( event, persister, keyToLoad );
663+
}
664+
}
665+
);
665666
}
666667
}
667668

hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,23 @@
99
import java.util.List;
1010
import java.util.concurrent.CompletionStage;
1111

12+
import org.hibernate.Hibernate;
13+
import org.hibernate.LockMode;
1214
import org.hibernate.LockOptions;
15+
import org.hibernate.ObjectDeletedException;
16+
import org.hibernate.cache.spi.access.EntityDataAccess;
17+
import org.hibernate.cache.spi.access.SoftLock;
18+
import org.hibernate.engine.spi.EntityEntry;
1319
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1420
import org.hibernate.engine.spi.SubselectFetch;
21+
import org.hibernate.event.monitor.spi.DiagnosticEvent;
22+
import org.hibernate.event.monitor.spi.EventMonitor;
23+
import org.hibernate.event.spi.EventSource;
24+
import org.hibernate.internal.OptimisticLockHelper;
25+
import org.hibernate.loader.LoaderLogging;
1526
import org.hibernate.metamodel.mapping.JdbcMapping;
27+
import org.hibernate.pretty.MessageHelper;
28+
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
1629
import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor;
1730
import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer;
1831
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
@@ -25,6 +38,8 @@
2538
import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
2639

2740
import static java.util.Objects.requireNonNull;
41+
import static org.hibernate.reactive.util.impl.CompletionStages.supplyStage;
42+
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
2843

2944
/**
3045
* @see org.hibernate.loader.ast.internal.LoaderHelper
@@ -92,4 +107,132 @@ public static <R, K> CompletionStage<List<R>> loadByArrayParameter(
92107
ReactiveListResultsConsumer.UniqueSemantic.FILTER
93108
);
94109
}
110+
111+
/**
112+
* A Reactive implementation of {@link org.hibernate.loader.ast.internal.LoaderHelper#upgradeLock(Object, EntityEntry, LockOptions, SharedSessionContractImplementor)}
113+
*/
114+
public static CompletionStage<Void> upgradeLock(
115+
Object object,
116+
EntityEntry entry,
117+
LockOptions lockOptions,
118+
SharedSessionContractImplementor session) {
119+
final LockMode requestedLockMode = lockOptions.getLockMode();
120+
if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) {
121+
// Request is for a more restrictive lock than the lock already held
122+
final ReactiveEntityPersister persister = (ReactiveEntityPersister) entry.getPersister();
123+
124+
if ( entry.getStatus().isDeletedOrGone()) {
125+
throw new ObjectDeletedException(
126+
"attempted to lock a deleted instance",
127+
entry.getId(),
128+
persister.getEntityName()
129+
);
130+
}
131+
132+
if ( LoaderLogging.LOADER_LOGGER.isTraceEnabled() ) {
133+
LoaderLogging.LOADER_LOGGER.tracef(
134+
"Locking `%s( %s )` in `%s` lock-mode",
135+
persister.getEntityName(),
136+
entry.getId(),
137+
requestedLockMode
138+
);
139+
}
140+
141+
final boolean cachingEnabled = persister.canWriteToCache();
142+
SoftLock lock = null;
143+
Object ck = null;
144+
try {
145+
if ( cachingEnabled ) {
146+
final EntityDataAccess cache = persister.getCacheAccessStrategy();
147+
ck = cache.generateCacheKey( entry.getId(), persister, session.getFactory(), session.getTenantIdentifier() );
148+
lock = cache.lockItem( session, ck, entry.getVersion() );
149+
}
150+
151+
if ( persister.isVersioned() && entry.getVersion() == null ) {
152+
// This should be an empty entry created for an uninitialized bytecode proxy
153+
if ( !Hibernate.isPropertyInitialized( object, persister.getVersionMapping().getPartName() ) ) {
154+
Hibernate.initialize( object );
155+
entry = session.getPersistenceContextInternal().getEntry( object );
156+
assert entry.getVersion() != null;
157+
}
158+
else {
159+
throw new IllegalStateException( String.format(
160+
"Trying to lock versioned entity %s but found null version",
161+
MessageHelper.infoString( persister.getEntityName(), entry.getId() )
162+
) );
163+
}
164+
}
165+
166+
if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) {
167+
// todo : should we check the current isolation mode explicitly?
168+
OptimisticLockHelper.forceVersionIncrement( object, entry, session );
169+
}
170+
else if ( entry.isExistsInDatabase() ) {
171+
final EventMonitor eventMonitor = session.getEventMonitor();
172+
final DiagnosticEvent entityLockEvent = eventMonitor.beginEntityLockEvent();
173+
return reactiveLock( object, entry, lockOptions, session, persister, eventMonitor, entityLockEvent, cachingEnabled, ck, lock );
174+
}
175+
else {
176+
// should only be possible for a stateful session
177+
if ( session instanceof EventSource eventSource ) {
178+
eventSource.forceFlush( entry );
179+
}
180+
}
181+
entry.setLockMode(requestedLockMode);
182+
}
183+
finally {
184+
// the database now holds a lock + the object is flushed from the cache,
185+
// so release the soft lock
186+
if ( cachingEnabled ) {
187+
persister.getCacheAccessStrategy().unlockItem( session, ck, lock );
188+
}
189+
}
190+
}
191+
return voidFuture();
192+
}
193+
194+
private static CompletionStage<Void> reactiveLock(
195+
Object object,
196+
EntityEntry entry,
197+
LockOptions lockOptions,
198+
SharedSessionContractImplementor session,
199+
ReactiveEntityPersister persister,
200+
EventMonitor eventMonitor,
201+
DiagnosticEvent entityLockEvent,
202+
boolean cachingEnabled,
203+
Object ck,
204+
SoftLock lock) {
205+
return supplyStage( () -> supplyStage( () -> persister.reactiveLock( entry.getId(), entry.getVersion(), object, lockOptions, session ) )
206+
.whenComplete( (v, e) -> completeLockEvent( entry, lockOptions, session, persister, eventMonitor, entityLockEvent, cachingEnabled, ck, lock, e == null ) ) )
207+
.whenComplete( (v, e) -> {
208+
if ( cachingEnabled ) {
209+
persister.getCacheAccessStrategy().unlockItem( session, ck, lock );
210+
}
211+
} );
212+
}
213+
214+
private static void completeLockEvent(
215+
EntityEntry entry,
216+
LockOptions lockOptions,
217+
SharedSessionContractImplementor session,
218+
ReactiveEntityPersister persister,
219+
EventMonitor eventMonitor,
220+
DiagnosticEvent entityLockEvent,
221+
boolean cachingEnabled,
222+
Object ck,
223+
SoftLock lock,
224+
boolean succes) {
225+
eventMonitor.completeEntityLockEvent(
226+
entityLockEvent,
227+
entry.getId(),
228+
persister.getEntityName(),
229+
lockOptions.getLockMode(),
230+
succes,
231+
session
232+
);
233+
if ( cachingEnabled ) {
234+
persister.getCacheAccessStrategy().unlockItem( session, ck, lock );
235+
}
236+
}
237+
95238
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.loader.internal;
7+
8+
import org.hibernate.LockOptions;
9+
import org.hibernate.engine.spi.EntityEntry;
10+
import org.hibernate.engine.spi.EntityKey;
11+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
12+
import org.hibernate.event.spi.LoadEventListener;
13+
import org.hibernate.loader.internal.CacheLoadHelper;
14+
import org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry.EntityStatus;
15+
16+
import java.util.concurrent.CompletionStage;
17+
18+
import static org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry.EntityStatus.MANAGED;
19+
import static org.hibernate.reactive.loader.ast.internal.ReactiveLoaderHelper.upgradeLock;
20+
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
21+
22+
/**
23+
* A reactive implementation of {@link CacheLoadHelper}
24+
*/
25+
public class ReactiveCacheLoadHelper {
26+
27+
public static CompletionStage<CacheLoadHelper.PersistenceContextEntry> loadFromSessionCache(
28+
EntityKey keyToLoad,
29+
LockOptions lockOptions,
30+
LoadEventListener.LoadType options,
31+
SharedSessionContractImplementor session) {
32+
final Object old = session.getEntityUsingInterceptor( keyToLoad );
33+
EntityStatus entityStatus = MANAGED;
34+
if ( old != null ) {
35+
// this object was already loaded
36+
final EntityEntry oldEntry = session.getPersistenceContext().getEntry( old );
37+
entityStatus = CacheLoadHelper.entityStatus( keyToLoad, options, session, oldEntry, old );
38+
if ( entityStatus == MANAGED ) {
39+
return upgradeLock( old, oldEntry, lockOptions, session )
40+
.thenApply(v -> new CacheLoadHelper.PersistenceContextEntry( old, MANAGED ) );
41+
}
42+
}
43+
return completedFuture( new CacheLoadHelper.PersistenceContextEntry( old, entityStatus ) );
44+
}
45+
46+
}

0 commit comments

Comments
 (0)