Search code examples
quarkusquarkus-panachequarkus-hibernate-reactive

Hibernate Reative Query Lock Mode throwing exception


There are a lot of Timeout/session not being released issues when using Panache-reactive with smallrye-reactive-messaging in quarkus. A slight variant of the timeout issue is observed when using the PanacheRepository.

Based on the knowledge from Panache reactiveTransactional timeout with no stack trace, I have used directly the session factory and created a Mutiny.Session and run my query and to the most part it will works except when I try to add setLockMode(LockMode.OPTIMISTIC_FORCE_INCREMENT) as it will cause

Caused by: java.lang.UnsupportedOperationException
    at org.hibernate.reactive.event.impl.DefaultReactiveLockEventListener.onLock(DefaultReactiveLockEventListener.java:240)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
    at org.hibernate.internal.SessionImpl.fireLock(SessionImpl.java:727)
    at org.hibernate.internal.SessionImpl.fireLock(SessionImpl.java:717)
    at org.hibernate.internal.SessionImpl.access$1000(SessionImpl.java:202)
    at org.hibernate.internal.SessionImpl$LockRequestImpl.lock(SessionImpl.java:2656)
    at org.hibernate.loader.custom.CustomLoader$1.afterLoad(CustomLoader.java:362)
    at org.hibernate.reactive.loader.ReactiveLoaderBasedResultSetProcessor.lambda$reactiveInitializeEntitiesAndCollections$3(ReactiveLoaderBasedResultSetProcessor.java:188)
    at java.base/java.util.concurrent.CompletableFuture.uniAcceptNow(CompletableFuture.java:757)
    at java.base/java.util.concurrent.CompletableFuture.uniAcceptStage(CompletableFuture.java:735)
    at java.base/java.util.concurrent.CompletableFuture.thenAccept(CompletableFuture.java:2182)
    at java.base/java.util.concurrent.CompletableFuture.thenAccept(CompletableFuture.java:144)
    at org.hibernate.reactive.loader.ReactiveLoaderBasedResultSetProcessor.reactiveInitializeEntitiesAndCollections(ReactiveLoaderBasedResultSesor.java:150)
    at org.hibernate.reactive.loader.ReactiveLoaderBasedResultSetProcessor.reactiveExtractResults(ReactiveLoaderBasedResultSetProcessor.java:83)
    at org.hibernate.reactive.loader.ReactiveLoader.reactiveProcessResultSet(ReactiveLoader.java:145)
    at org.hibernate.reactive.loader.ReactiveLoader.lambda$doReactiveQueryAndInitializeNonLazyCollections$0(ReactiveLoader.java:77)
    at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
    ... 63 more

Ideally it should have DefaultReactiveLockEventListener.reativeOnLock but from stack trace it went to DefaultReactiveLockEventListener.OnLock This is my full implementation.

    return sessionfactory.withTransaction((session, tx) -> {
            return session.createNativeQuery("select * from model", Model.class)
                    .setLockMode(LockMode.OPTIMISTIC_FORCE_INCREMENT).getResultList().onItemOrFailure()
                    .transform((existingModels, t) -> {
                        if (t != null) {
                            Log.error(t, t);
                        }

                        List<Model> toSave = Lists.newArrayList();
                        for (Model publish : publishedModels) {
                            boolean exist = false;
                            for (Model existing : existingModels) {
                                if (publish.getName().equals(existing.getName()) {
                                    toSave.add(existing);
                                    exist = true;
                                }
                            }
                            if (!exist) {
                                publish.setId(UUID.randomUUID());
                                publish.setRevision(1L);
                                toSave.add(publish);
                            }
                        }

                        Wrap wrap = new Wrap();
                        wrap.setSession(session);
                        wrap.setModels(toSave);

                        return wrap;
                    }).chain(wrap -> {
                        return wrap.getSession().persistAll(wrap.getModels().toArray()).invoke(v -> session.flush());
                    });

        });

Is this a bug or this is something that I can improve on my code to circumvent this? I needed the Lock to make sure that no two transactions that will update the same Version(Using the JPA @Version) of the record.


Solution

  • This following code seems to work but I am not sure if this is the right way to go.

    for (Model existing : existingModels) {
      if (publish.getName().equals(existing.getName()) {
        toSave.add(existing);
        session.lock(existing, LockMode.PESSIMISTIC_FORCE_INCREMENT);
        exist = true;
      }
    }