Skip to content

Commit 9ab7b45

Browse files
dreab8DavideD
authored andcommitted
[hibernate#1905] Fix UnsupportedOperationException when using find with lock
1 parent dde7d5b commit 9ab7b45

15 files changed

+313
-27
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java

+86-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.io.Serializable;
99
import java.util.Iterator;
10+
import java.util.List;
1011
import java.util.Map;
1112
import java.util.concurrent.CompletionStage;
1213
import java.util.function.BiConsumer;
@@ -30,18 +31,24 @@
3031
import org.hibernate.engine.spi.SessionImplementor;
3132
import org.hibernate.engine.spi.SharedSessionContractImplementor;
3233
import org.hibernate.engine.spi.Status;
34+
import org.hibernate.event.service.spi.EventListenerGroup;
35+
import org.hibernate.event.spi.PostLoadEvent;
36+
import org.hibernate.event.spi.PostLoadEventListener;
3337
import org.hibernate.persister.collection.CollectionPersister;
3438
import org.hibernate.persister.entity.EntityPersister;
39+
import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl;
3540
import org.hibernate.reactive.logging.impl.Log;
3641
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
3742
import org.hibernate.reactive.session.ReactiveSession;
43+
import org.hibernate.sql.exec.spi.Callback;
3844
import org.hibernate.sql.results.graph.entity.EntityInitializer;
3945
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState;
4046
import org.hibernate.sql.results.spi.LoadContexts;
4147

4248
import static java.lang.invoke.MethodHandles.lookup;
4349
import static org.hibernate.reactive.logging.impl.LoggerFactory.make;
4450
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
51+
import static org.hibernate.reactive.util.impl.CompletionStages.loop;
4552
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
4653

4754
/**
@@ -456,7 +463,7 @@ public EntityHolder removeEntityHolder(EntityKey key) {
456463

457464
@Override
458465
public void postLoad(JdbcValuesSourceProcessingState processingState, Consumer<EntityHolder> loadedConsumer) {
459-
delegate.postLoad( processingState, loadedConsumer );
466+
throw LOG.nonReactiveMethodCall( "reactivePostLoad(JdbcValuesSourceProcessingState, Consumer<EntityHolder>) )" );
460467
}
461468

462469
@Internal
@@ -710,4 +717,82 @@ public Iterator<Object> managedEntitiesIterator() {
710717
public NaturalIdResolutions getNaturalIdResolutions() {
711718
return delegate.getNaturalIdResolutions();
712719
}
720+
721+
/**
722+
* Reactive version of {@link StatefulPersistenceContext#postLoad(JdbcValuesSourceProcessingState, Consumer)}
723+
*/
724+
public CompletionStage<Void> reactivePostLoad(
725+
JdbcValuesSourceProcessingState processingState,
726+
Consumer<EntityHolder> holderConsumer) {
727+
final ReactiveCallbackImpl callback = (ReactiveCallbackImpl) processingState
728+
.getExecutionContext().getCallback();
729+
return processHolders(
730+
holderConsumer,
731+
processingState.getLoadingEntityHolders(),
732+
getSession().getFactory().getEventListenerGroups().eventListenerGroup_POST_LOAD,
733+
processingState.getPostLoadEvent(),
734+
callback
735+
).thenCompose( v -> processHolders(
736+
holderConsumer,
737+
processingState.getReloadedEntityHolders(),
738+
null,
739+
null,
740+
callback
741+
) );
742+
}
743+
744+
private CompletionStage<Void> processHolders(
745+
Consumer<EntityHolder> holderConsumer,
746+
List<EntityHolder> loadingEntityHolders,
747+
EventListenerGroup<PostLoadEventListener> listenerGroup,
748+
PostLoadEvent postLoadEvent,
749+
ReactiveCallbackImpl callback) {
750+
if ( loadingEntityHolders != null ) {
751+
return loop( loadingEntityHolders,
752+
holder -> processLoadedEntityHolder(
753+
holder,
754+
listenerGroup,
755+
postLoadEvent,
756+
callback,
757+
holderConsumer
758+
)
759+
).thenAccept( v -> loadingEntityHolders.clear() );
760+
}
761+
return voidFuture();
762+
}
763+
764+
/**
765+
* Reactive version of {@link StatefulPersistenceContext#processLoadedEntityHolder(EntityHolder, EventListenerGroup, PostLoadEvent, Callback, Consumer)}
766+
*/
767+
private CompletionStage<Void> processLoadedEntityHolder(
768+
EntityHolder holder,
769+
EventListenerGroup<PostLoadEventListener> listenerGroup,
770+
PostLoadEvent postLoadEvent,
771+
ReactiveCallbackImpl callback,
772+
Consumer<EntityHolder> holderConsumer) {
773+
if ( holderConsumer != null ) {
774+
holderConsumer.accept( holder );
775+
}
776+
if ( holder.getEntity() == null ) {
777+
// It's possible that we tried to load an entity and found out it doesn't exist,
778+
// in which case we added an entry with a null proxy and entity.
779+
// Remove that empty entry on post load to avoid unwanted side effects
780+
getEntitiesByKey().remove( holder.getEntityKey() );
781+
}
782+
else {
783+
if ( postLoadEvent != null ) {
784+
postLoadEvent.reset();
785+
postLoadEvent.setEntity( holder.getEntity() )
786+
.setId( holder.getEntityKey().getIdentifier() )
787+
.setPersister( holder.getDescriptor() );
788+
listenerGroup.fireEventOnEachListener( postLoadEvent, PostLoadEventListener::onPostLoad );
789+
if ( callback != null ) {
790+
return callback
791+
.invokeReactiveLoadActions( holder.getEntity(), holder.getDescriptor(), getSession() )
792+
.thenAccept( v -> holder.resetEntityInitialier() );
793+
}
794+
}
795+
}
796+
return voidFuture();
797+
}
713798
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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.engine.impl;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.concurrent.CompletionStage;
11+
12+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
13+
import org.hibernate.loader.ast.spi.AfterLoadAction;
14+
import org.hibernate.metamodel.mapping.EntityMappingType;
15+
import org.hibernate.reactive.loader.ast.spi.ReactiveAfterLoadAction;
16+
import org.hibernate.reactive.logging.impl.Log;
17+
import org.hibernate.sql.exec.spi.Callback;
18+
19+
import static java.lang.invoke.MethodHandles.lookup;
20+
import static org.hibernate.reactive.logging.impl.LoggerFactory.make;
21+
import static org.hibernate.reactive.util.impl.CompletionStages.loop;
22+
23+
/**
24+
* Reactive equivalent of {@link org.hibernate.sql.exec.internal.CallbackImpl}
25+
*/
26+
public class ReactiveCallbackImpl implements Callback {
27+
private static final Log LOG = make( Log.class, lookup() );
28+
29+
private final List<ReactiveAfterLoadAction> afterLoadActions;
30+
31+
public ReactiveCallbackImpl() {
32+
this.afterLoadActions = new ArrayList<>( 1 );
33+
}
34+
35+
@Override
36+
public void registerAfterLoadAction(AfterLoadAction afterLoadAction) {
37+
throw LOG.nonReactiveMethodCall( "registerReactiveAfterLoadAction(ReactiveCallbackImpl)" );
38+
}
39+
40+
public void registerReactiveAfterLoadAction(ReactiveAfterLoadAction afterLoadAction) {
41+
afterLoadActions.add( afterLoadAction );
42+
}
43+
44+
@Override
45+
public void invokeAfterLoadActions(
46+
Object entity,
47+
EntityMappingType entityMappingType,
48+
SharedSessionContractImplementor session) {
49+
throw LOG.nonReactiveMethodCall( "invokeAfterLoadActions(Object, EntityMappingType, SharedSessionContractImplementor)" );
50+
}
51+
52+
/**
53+
* Reactive version of {@link org.hibernate.sql.exec.internal.CallbackImpl#invokeAfterLoadActions(Object, EntityMappingType, SharedSessionContractImplementor)}
54+
*/
55+
public CompletionStage<Void> invokeReactiveLoadActions(
56+
Object entity,
57+
EntityMappingType entityMappingType,
58+
SharedSessionContractImplementor session) {
59+
return loop(
60+
afterLoadActions, afterLoadAction ->
61+
afterLoadAction.reactiveAfterLoad( entity, entityMappingType, session )
62+
);
63+
}
64+
65+
@Override
66+
public boolean hasAfterLoadActions() {
67+
return !afterLoadActions.isEmpty();
68+
}
69+
70+
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.hibernate.metamodel.mapping.NaturalIdMapping;
2828
import org.hibernate.query.internal.SimpleQueryOptions;
2929
import org.hibernate.query.spi.QueryOptions;
30+
import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl;
3031
import org.hibernate.reactive.loader.ast.spi.ReactiveNaturalIdLoader;
3132
import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor;
3233
import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer;
@@ -38,7 +39,6 @@
3839
import org.hibernate.sql.ast.tree.select.QuerySpec;
3940
import org.hibernate.sql.ast.tree.select.SelectStatement;
4041
import org.hibernate.sql.exec.internal.BaseExecutionContext;
41-
import org.hibernate.sql.exec.internal.CallbackImpl;
4242
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
4343
import org.hibernate.sql.exec.spi.Callback;
4444
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
@@ -335,7 +335,7 @@ public NaturalIdLoaderWithOptionsExecutionContext(
335335
QueryOptions queryOptions) {
336336
super( session );
337337
this.queryOptions = queryOptions;
338-
callback = new CallbackImpl();
338+
callback = new ReactiveCallbackImpl();
339339
}
340340

341341
@Override

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

+20-11
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@
2020
import org.hibernate.query.internal.SimpleQueryOptions;
2121
import org.hibernate.query.spi.QueryOptions;
2222
import org.hibernate.query.spi.QueryParameterBindings;
23+
import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl;
2324
import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor;
2425
import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer;
2526
import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor;
2627
import org.hibernate.sql.ast.tree.select.SelectStatement;
27-
import org.hibernate.sql.exec.internal.CallbackImpl;
2828
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
2929
import org.hibernate.sql.exec.spi.Callback;
3030
import org.hibernate.sql.exec.spi.ExecutionContext;
3131
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
3232
import org.hibernate.sql.exec.spi.JdbcParametersList;
33+
import org.hibernate.sql.results.spi.RowTransformer;
34+
35+
import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
3336

3437
public class ReactiveSingleIdLoadPlan<T> extends SingleIdLoadPlan<CompletionStage<T>> {
3538

@@ -61,7 +64,7 @@ public CompletionStage<T> load(Object restrictedValue, Object entityInstance, Bo
6164
}
6265
assert offset == getJdbcParameters().size();
6366
final QueryOptions queryOptions = new SimpleQueryOptions( getLockOptions(), readOnly );
64-
final Callback callback = new CallbackImpl();
67+
final ReactiveCallbackImpl callback = new ReactiveCallbackImpl();
6568
EntityMappingType loadable = (EntityMappingType) getLoadable();
6669
ExecutionContext executionContext = executionContext(
6770
restrictedValue,
@@ -73,21 +76,27 @@ public CompletionStage<T> load(Object restrictedValue, Object entityInstance, Bo
7376
);
7477
// FIXME: Should we get this from jdbcServices.getSelectExecutor()?
7578
return StandardReactiveSelectExecutor.INSTANCE
76-
.list( getJdbcSelect(), jdbcParameterBindings, executionContext, getRowTransformer(), resultConsumer( singleResultExpected ) )
79+
.list( getJdbcSelect(), jdbcParameterBindings, executionContext, rowTransformer(), resultConsumer( singleResultExpected ) )
7780
.thenApply( this::extractEntity )
78-
.thenApply( entity -> {
79-
invokeAfterLoadActions( callback, session, entity );
80-
return (T) entity;
81-
} );
81+
.thenCompose( entity -> invokeAfterLoadActions( callback, session, entity ) );
82+
}
83+
84+
private RowTransformer<T> rowTransformer() {
85+
// Because of the generics, the compiler expect this to return RowTransformer<CompletionStage<T>>
86+
// but it actually returns RowTransformer<T>. I don't know at the moment how to fix this in a cleaner way
87+
return (RowTransformer<T>) getRowTransformer();
8288
}
8389

84-
private <G> void invokeAfterLoadActions(Callback callback, SharedSessionContractImplementor session, G entity) {
85-
if ( entity != null && getLoadable() != null) {
86-
callback.invokeAfterLoadActions( entity, (EntityMappingType) getLoadable(), session );
90+
private CompletionStage<T> invokeAfterLoadActions(ReactiveCallbackImpl callback, SharedSessionContractImplementor session, T entity) {
91+
if ( entity != null && getLoadable() != null ) {
92+
return callback
93+
.invokeReactiveLoadActions( entity, (EntityMappingType) getLoadable(), session )
94+
.thenApply( v -> entity );
8795
}
96+
return nullFuture();
8897
}
8998

90-
private Object extractEntity(List<?> list) {
99+
private T extractEntity(List<T> list) {
91100
return list.isEmpty() ? null : list.get( 0 );
92101
}
93102

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
2525
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
2626
import org.hibernate.query.spi.QueryOptions;
27+
import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl;
2728
import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader;
2829
import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor;
2930
import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer;
3031
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
3132
import org.hibernate.sql.ast.tree.select.SelectStatement;
3233
import org.hibernate.sql.exec.internal.BaseExecutionContext;
33-
import org.hibernate.sql.exec.internal.CallbackImpl;
3434
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
3535
import org.hibernate.sql.exec.spi.Callback;
3636
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
@@ -179,7 +179,7 @@ public SingleUKEntityLoaderExecutionContext(SharedSessionContractImplementor ses
179179
super( session );
180180
//Careful, readOnly is possibly null
181181
this.queryOptions = readOnly == null ? QueryOptions.NONE : readOnly ? QueryOptions.READ_ONLY : QueryOptions.READ_WRITE;
182-
callback = new CallbackImpl();
182+
callback = new ReactiveCallbackImpl();
183183
}
184184

185185
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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.ast.spi;
7+
8+
import java.util.concurrent.CompletionStage;
9+
10+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
11+
import org.hibernate.metamodel.mapping.EntityMappingType;
12+
13+
/**
14+
* Reactive version of {@link org.hibernate.loader.ast.spi.AfterLoadAction}
15+
*/
16+
public interface ReactiveAfterLoadAction {
17+
/**
18+
* @see org.hibernate.loader.ast.spi.AfterLoadAction#afterLoad(Object, EntityMappingType, SharedSessionContractImplementor)
19+
*
20+
* The action trigger - the {@code entity} is being loaded
21+
*/
22+
CompletionStage<Void> reactiveAfterLoad(
23+
Object entity,
24+
EntityMappingType entityMappingType,
25+
SharedSessionContractImplementor session);
26+
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
package org.hibernate.reactive.query.spi;
77

88
import java.lang.invoke.MethodHandles;
9-
import java.util.*;
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Optional;
12+
import java.util.Set;
1013
import java.util.concurrent.CompletionException;
1114
import java.util.concurrent.CompletionStage;
1215
import java.util.function.Consumer;
@@ -30,12 +33,14 @@
3033
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
3134
import org.hibernate.query.sqm.tree.SqmStatement;
3235
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
36+
import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl;
3337
import org.hibernate.reactive.logging.impl.Log;
3438
import org.hibernate.reactive.logging.impl.LoggerFactory;
3539
import org.hibernate.reactive.query.sqm.internal.AggregatedSelectReactiveQueryPlan;
3640
import org.hibernate.reactive.query.sqm.internal.ConcreteSqmSelectReactiveQueryPlan;
3741
import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan;
3842
import org.hibernate.reactive.sql.results.spi.ReactiveSingleResultConsumer;
43+
import org.hibernate.sql.exec.spi.Callback;
3944
import org.hibernate.sql.results.internal.TupleMetadata;
4045

4146
import jakarta.persistence.NoResultException;
@@ -76,6 +81,8 @@ public class ReactiveAbstractSelectionQuery<R> {
7681
private final Function<List<R>, R> uniqueElement;
7782
private final InterpretationsKeySource interpretationsKeySource;
7883

84+
private Callback callback;
85+
7986
// I'm sure we can avoid some of this by making some methods public in ORM,
8087
// but this allows me to prototype faster. We can refactor the code later.
8188
public ReactiveAbstractSelectionQuery(
@@ -363,4 +370,11 @@ public void enableFetchProfile(String profileName) {
363370
}
364371
fetchProfiles.add( profileName );
365372
}
373+
374+
public Callback getCallback() {
375+
if ( callback == null ) {
376+
callback = new ReactiveCallbackImpl();
377+
}
378+
return callback;
379+
}
366380
}

0 commit comments

Comments
 (0)