Skip to content

Commit 146fbf9

Browse files
committed
support for lock() operation
supported lock modes: - PESSIMISTIC_LOCK_MODE_WRITE - PESSIMISTIC_LOCK_MODE_FORCE_INCREMENT - PESSIMISTIC_LOCK_MODE_READ not supported: - optimistic lock modes see hibernate#142
1 parent 457cf56 commit 146fbf9

20 files changed

+573
-23
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/impl/ReactiveIntegrator.java

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ private void attachEventContextManagingListenersIfRequired(SessionFactoryService
4949
eventListenerRegistry.getEventListenerGroup( EventType.MERGE ).appendListener( new DefaultReactiveMergeEventListener() );
5050
eventListenerRegistry.getEventListenerGroup( EventType.DELETE ).appendListener( new DefaultReactiveDeleteEventListener() );
5151
eventListenerRegistry.getEventListenerGroup( EventType.REFRESH ).appendListener( new DefaultReactiveRefreshEventListener() );
52+
eventListenerRegistry.getEventListenerGroup( EventType.LOCK ).appendListener( new DefaultReactiveLockEventListener() );
5253
eventListenerRegistry.getEventListenerGroup( EventType.LOAD ).appendListener( new DefaultReactiveLoadEventListener() );
5354
eventListenerRegistry.getEventListenerGroup( EventType.INIT_COLLECTION ).appendListener( new DefaultReactiveInitializeCollectionEventListener() );
5455
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java

+24-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.hibernate.reactive.engine.impl;
77

88
import org.hibernate.HibernateException;
9+
import org.hibernate.LockMode;
910
import org.hibernate.event.internal.MergeContext;
1011
import org.hibernate.event.spi.EventSource;
1112
import org.hibernate.internal.CoreMessageLogger;
@@ -42,7 +43,7 @@ private CascadingActions() {
4243
public static final CascadingAction<IdentitySet> DELETE =
4344
new BaseCascadingAction<IdentitySet>(org.hibernate.engine.spi.CascadingActions.DELETE) {
4445
@Override
45-
public CompletionStage <?> cascade(
46+
public CompletionStage<?> cascade(
4647
EventSource session,
4748
Object child,
4849
String entityName,
@@ -61,7 +62,7 @@ public CompletionStage <?> cascade(
6162
public static final CascadingAction<IdentitySet> PERSIST =
6263
new BaseCascadingAction<IdentitySet>(org.hibernate.engine.spi.CascadingActions.PERSIST) {
6364
@Override
64-
public CompletionStage <?> cascade(
65+
public CompletionStage<?> cascade(
6566
EventSource session,
6667
Object child,
6768
String entityName,
@@ -81,7 +82,7 @@ public CompletionStage <?> cascade(
8182
public static final CascadingAction<IdentitySet> PERSIST_ON_FLUSH =
8283
new BaseCascadingAction<IdentitySet>(org.hibernate.engine.spi.CascadingActions.PERSIST_ON_FLUSH) {
8384
@Override
84-
public CompletionStage <?> cascade(
85+
public CompletionStage<?> cascade(
8586
EventSource session,
8687
Object child,
8788
String entityName,
@@ -99,7 +100,7 @@ public CompletionStage <?> cascade(
99100
public static final CascadingAction<MergeContext> MERGE =
100101
new BaseCascadingAction<MergeContext>(org.hibernate.engine.spi.CascadingActions.MERGE) {
101102
@Override
102-
public CompletionStage <?> cascade(
103+
public CompletionStage<?> cascade(
103104
EventSource session,
104105
Object child,
105106
String entityName,
@@ -118,7 +119,7 @@ public CompletionStage <?> cascade(
118119
public static final CascadingAction<IdentitySet> REFRESH =
119120
new BaseCascadingAction<IdentitySet>(org.hibernate.engine.spi.CascadingActions.REFRESH) {
120121
@Override
121-
public CompletionStage <?> cascade(
122+
public CompletionStage<?> cascade(
122123
EventSource session,
123124
Object child,
124125
String entityName,
@@ -130,6 +131,24 @@ public CompletionStage <?> cascade(
130131
}
131132
};
132133

134+
/**
135+
* @see org.hibernate.Session#lock(Object, org.hibernate.LockMode)
136+
*/
137+
public static final CascadingAction<LockMode> LOCK =
138+
new BaseCascadingAction<LockMode>(org.hibernate.engine.spi.CascadingActions.LOCK) {
139+
@Override
140+
public CompletionStage<?> cascade(
141+
EventSource session,
142+
Object child,
143+
String entityName,
144+
LockMode context,
145+
boolean isCascadeDeleteEnabled)
146+
throws HibernateException {
147+
LOG.tracev("Cascading to lock: {0}", entityName);
148+
return session.unwrap(ReactiveSession.class).reactiveLock(child, context);
149+
}
150+
};
151+
133152
public abstract static class BaseCascadingAction<C> implements CascadingAction<C> {
134153
private final org.hibernate.engine.spi.CascadingAction delegate;
135154

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@
5252
* @see DefaultReactivePersistOnFlushEventListener
5353
* @see DefaultReactiveMergeEventListener
5454
*/
55-
abstract class AbstractReactiveSaveEventListener<C>
56-
implements CallbackRegistryConsumer {
55+
abstract class AbstractReactiveSaveEventListener<C> implements CallbackRegistryConsumer {
5756

5857
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractReactiveSaveEventListener.class );
5958

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424

2525
import org.jboss.logging.Logger;
2626

27-
public class DefaultReactiveAutoFlushEventListener extends AbstractReactiveFlushingEventListener implements ReactiveAutoFlushEventListener, AutoFlushEventListener {
27+
public class DefaultReactiveAutoFlushEventListener extends AbstractReactiveFlushingEventListener
28+
implements ReactiveAutoFlushEventListener, AutoFlushEventListener {
2829

2930
private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, DefaultReactiveAutoFlushEventListener.class.getName() );
3031

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

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
*/
5050
public class DefaultReactiveDeleteEventListener
5151
implements DeleteEventListener, ReactiveDeleteEventListener, CallbackRegistryConsumer, JpaBootstrapSensitive {
52+
5253
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultReactiveDeleteEventListener.class );
5354

5455
private CallbackRegistry callbackRegistry;

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
/**
2020
* A reactific {@link org.hibernate.event.internal.DefaultFlushEventListener}.
2121
*/
22-
public class DefaultReactiveFlushEventListener extends AbstractReactiveFlushingEventListener implements ReactiveFlushEventListener, FlushEventListener {
22+
public class DefaultReactiveFlushEventListener extends AbstractReactiveFlushingEventListener
23+
implements ReactiveFlushEventListener, FlushEventListener {
24+
2325
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
2426
CoreMessageLogger.class,
2527
DefaultReactiveFlushEventListener.class.getName()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: LGPL-2.1-or-later
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.event.impl;
7+
8+
import org.hibernate.HibernateException;
9+
import org.hibernate.LockMode;
10+
import org.hibernate.LockOptions;
11+
import org.hibernate.ObjectDeletedException;
12+
import org.hibernate.TransientObjectException;
13+
import org.hibernate.cache.spi.access.EntityDataAccess;
14+
import org.hibernate.cache.spi.access.SoftLock;
15+
import org.hibernate.engine.internal.CascadePoint;
16+
import org.hibernate.engine.spi.EntityEntry;
17+
import org.hibernate.engine.spi.PersistenceContext;
18+
import org.hibernate.engine.spi.SessionImplementor;
19+
import org.hibernate.engine.spi.Status;
20+
import org.hibernate.event.internal.AbstractReassociateEventListener;
21+
import org.hibernate.event.internal.DefaultLockEventListener;
22+
import org.hibernate.event.spi.EventSource;
23+
import org.hibernate.event.spi.LockEvent;
24+
import org.hibernate.event.spi.LockEventListener;
25+
import org.hibernate.internal.CoreMessageLogger;
26+
import org.hibernate.persister.entity.EntityPersister;
27+
import org.hibernate.pretty.MessageHelper;
28+
import org.hibernate.reactive.engine.impl.Cascade;
29+
import org.hibernate.reactive.engine.impl.CascadingActions;
30+
import org.hibernate.reactive.engine.impl.ForeignKeys;
31+
import org.hibernate.reactive.event.spi.ReactiveLockEventListener;
32+
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
33+
import org.hibernate.reactive.util.impl.CompletionStages;
34+
import org.jboss.logging.Logger;
35+
36+
import java.io.Serializable;
37+
import java.util.concurrent.CompletionStage;
38+
39+
public class DefaultReactiveLockEventListener extends AbstractReassociateEventListener
40+
implements LockEventListener, ReactiveLockEventListener {
41+
42+
private static final CoreMessageLogger log = Logger.getMessageLogger(
43+
CoreMessageLogger.class,
44+
DefaultLockEventListener.class.getName()
45+
);
46+
47+
@Override
48+
public CompletionStage<Void> reactiveOnLock(LockEvent event) throws HibernateException {
49+
if ( event.getObject() == null ) {
50+
throw new NullPointerException( "attempted to lock null" );
51+
}
52+
53+
if ( event.getLockMode() == LockMode.WRITE ) {
54+
throw new HibernateException( "Invalid lock mode for lock()" );
55+
}
56+
57+
if ( event.getLockMode() == LockMode.UPGRADE_SKIPLOCKED ) {
58+
log.explicitSkipLockedLockCombo();
59+
}
60+
61+
SessionImplementor source = event.getSession();
62+
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
63+
Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
64+
//TODO: if object was an uninitialized proxy, this is inefficient,
65+
// resulting in two SQL selects
66+
67+
EntityEntry entry = persistenceContext.getEntry(entity);
68+
CompletionStage<EntityEntry> stage;
69+
if (entry==null) {
70+
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
71+
final Serializable id = persister.getIdentifier( entity, source );
72+
stage = ForeignKeys.isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source )
73+
.thenApply(
74+
trans -> {
75+
if (!trans) {
76+
throw new TransientObjectException(
77+
"cannot lock an unsaved transient instance: " +
78+
persister.getEntityName()
79+
);
80+
}
81+
82+
EntityEntry e = reassociate(event, entity, id, persister);
83+
cascadeOnLock(event, persister, entity);
84+
return e;
85+
} );
86+
87+
}
88+
else {
89+
stage = CompletionStages.completedFuture(entry);
90+
}
91+
92+
return stage.thenCompose( e -> upgradeLock( entity, e, event.getLockOptions(), event.getSession() ) );
93+
}
94+
95+
private void cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) {
96+
EventSource source = event.getSession();
97+
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
98+
persistenceContext.incrementCascadeLevel();
99+
try {
100+
new Cascade(
101+
CascadingActions.LOCK,
102+
CascadePoint.AFTER_LOCK,
103+
persister,
104+
entity,
105+
event.getLockOptions(),
106+
source
107+
).cascade();
108+
}
109+
finally {
110+
persistenceContext.decrementCascadeLevel();
111+
}
112+
}
113+
114+
/**
115+
* Performs a pessimistic lock upgrade on a given entity, if needed.
116+
*
117+
* @param object The entity for which to upgrade the lock.
118+
* @param entry The entity's EntityEntry instance.
119+
* @param lockOptions contains the requested lock mode.
120+
* @param source The session which is the source of the event being processed.
121+
*/
122+
protected CompletionStage<Void> upgradeLock(Object object, EntityEntry entry,
123+
LockOptions lockOptions,
124+
EventSource source) {
125+
126+
LockMode requestedLockMode = lockOptions.getLockMode();
127+
if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) {
128+
// The user requested a "greater" (i.e. more restrictive) form of
129+
// pessimistic lock
130+
131+
if ( entry.getStatus() != Status.MANAGED ) {
132+
throw new ObjectDeletedException(
133+
"attempted to lock a deleted instance",
134+
entry.getId(),
135+
entry.getPersister().getEntityName()
136+
);
137+
}
138+
139+
final EntityPersister persister = entry.getPersister();
140+
141+
if ( log.isTraceEnabled() ) {
142+
log.tracev(
143+
"Locking {0} in mode: {1}",
144+
MessageHelper.infoString( persister, entry.getId(), source.getFactory() ),
145+
requestedLockMode
146+
);
147+
}
148+
149+
final boolean cachingEnabled = persister.canWriteToCache();
150+
final SoftLock lock;
151+
final Object ck;
152+
if ( cachingEnabled ) {
153+
EntityDataAccess cache = persister.getCacheAccessStrategy();
154+
ck = cache.generateCacheKey(
155+
entry.getId(),
156+
persister,
157+
source.getFactory(),
158+
source.getTenantIdentifier()
159+
);
160+
lock = cache.lockItem( source, ck, entry.getVersion() );
161+
}
162+
else {
163+
lock = null;
164+
ck = null;
165+
}
166+
167+
return ((ReactiveEntityPersister) persister).lockReactive(
168+
entry.getId(),
169+
entry.getVersion(),
170+
object,
171+
lockOptions,
172+
source
173+
).thenAccept( v -> entry.setLockMode(requestedLockMode) )
174+
.whenComplete( (r, e) -> {
175+
// the database now holds a lock + the object is flushed from the cache,
176+
// so release the soft lock
177+
if ( cachingEnabled ) {
178+
persister.getCacheAccessStrategy().unlockItem( source, ck, lock );
179+
}
180+
} );
181+
182+
}
183+
else {
184+
return CompletionStages.nullFuture();
185+
}
186+
}
187+
188+
@Override
189+
public void onLock(LockEvent event) throws HibernateException {
190+
throw new UnsupportedOperationException();
191+
}
192+
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
/**
3636
* A reactific {@link org.hibernate.event.internal.DefaultMergeEventListener}.
3737
*/
38-
public class DefaultReactiveMergeEventListener extends AbstractReactiveSaveEventListener<MergeContext> implements ReactiveMergeEventListener, MergeEventListener {
38+
public class DefaultReactiveMergeEventListener extends AbstractReactiveSaveEventListener<MergeContext>
39+
implements ReactiveMergeEventListener, MergeEventListener {
3940

4041
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultReactiveMergeEventListener.class );
4142

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

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
public class DefaultReactivePersistEventListener
4040
extends AbstractReactiveSaveEventListener<IdentitySet>
4141
implements PersistEventListener, ReactivePersistEventListener, CallbackRegistryConsumer {
42+
4243
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultReactivePersistEventListener.class );
4344

4445
@Override

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
/**
3939
* A reactific {@link org.hibernate.event.internal.DefaultRefreshEventListener}.
4040
*/
41-
public class DefaultReactiveRefreshEventListener implements RefreshEventListener, ReactiveRefreshEventListener {
41+
public class DefaultReactiveRefreshEventListener
42+
implements RefreshEventListener, ReactiveRefreshEventListener {
43+
4244
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultReactiveRefreshEventListener.class );
4345

4446
public CompletionStage<Void> reactiveOnRefresh(RefreshEvent event) throws HibernateException {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: LGPL-2.1-or-later
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.event.spi;
7+
8+
import org.hibernate.HibernateException;
9+
import org.hibernate.event.spi.LockEvent;
10+
11+
import java.io.Serializable;
12+
import java.util.concurrent.CompletionStage;
13+
14+
/**
15+
* Defines the contract for handling of lock events generated from a session.
16+
*
17+
* @author Steve Ebersole
18+
*/
19+
public interface ReactiveLockEventListener extends Serializable {
20+
21+
/**
22+
* Handle the given lock event.
23+
*
24+
* @param event The lock event to be handled.
25+
*/
26+
CompletionStage<Void> reactiveOnLock(LockEvent event) throws HibernateException;
27+
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java

+15
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,21 @@ interface Session extends AutoCloseable {
334334
*/
335335
Uni<Session> refresh(Object... entities);
336336

337+
/**
338+
* Obtain the specified lock level upon the given object. For example, this
339+
* may be used to perform a version check with {@link LockMode#READ}, or to
340+
* upgrade to a pessimistic lock with {@link LockMode#PESSIMISTIC_WRITE}.
341+
* This operation cascades to associated instances if the association is
342+
* mapped with {@code cascade="lock"}.
343+
*
344+
* Note that the optimistic lock modes {@link LockMode#OPTIMISTIC} and
345+
* {@link LockMode#OPTIMISTIC_FORCE_INCREMENT} are not currently supported.
346+
*
347+
* @param entity a persistent or transient instance
348+
* @param lockMode the lock level
349+
*/
350+
Uni<Session> lock(Object entity, LockMode lockMode);
351+
337352
/**
338353
* Force this session to flush asynchronously. Must be called at the
339354
* end of a unit of work, before committing the transaction and closing

0 commit comments

Comments
 (0)