2

I am working on a project using Quarkus 3.0.0.CR2 and Hibernate Reactive Panache as the ORM for a reactive application. The application retrieves animals from owners using an API resource that makes 5 reactive calls to a service, which is forwards to the database repository implementation.

However, when reapeatably running the unit test or making curl calls to "http://localhost:8080/zoo/animals/Melani", I am randomly encountering two different errors:

  • org.hibernate.HibernateException: java.util.concurrent.CompletionException: java.lang.IllegalStateException: Session/EntityManager is closed
  • org.hibernate.HibernateException: java.util.concurrent.CompletionException: java.util.NoSuchElementException

Could someone please help me understand why I am encountering these errors and suggest a solution?

Here is a link to a sample project on Github that demonstrates the error: https://github.com/Lukec1/quarkus-3.0.0.CR2-sample

Thank you in advance for your help.

2 Answers2

2

By design hibernate reactive can not share a session on parallel threads which is blocking using it with Uni or Mutiny.

The problem is that Hibernate Reactive needs operations on the database to happen in a predictable order to avoid errors when managing entities. So, you cannot share the same session among different threads or uni streams.

We don't have a solution for this at the moment, and chaining the uni is the only way to be sure that everything will work as expected.

Here is the real source

ozkanpakdil
  • 3,199
  • 31
  • 48
1

I found solution on Quarkus issues page (https://github.com/quarkusio/quarkus/issues/28808) and commit solution to existing project(https://github.com/Lukec1/quarkus-3.0.0.CR2-sample/commit/98a4791571334a898716d7780a87d993d443f5c6).

Edited: recognized problem is when using Panache Hibernate Reactive and Uni.combine. Because of that i now use chain approach which does not work in parallel as we would like with the Uni.combine.

ZooService

  public Uni<List<Dog>> getDogs(String owner) {
    return zooRepository.getDogs(owner);
  }

  public Uni<List<Cat>> getCats(String owner) {
    return zooRepository.getCats(owner);
  }

  public Uni<List<Rat>> getRats(String owner) {
    return zooRepository.getRats(owner);
  }

  public Uni<List<Dolphin>> getDoplphins(String owner) {
    return zooRepository.getDoplphins(owner);
  }

ZooRepository

private Uni<List<AnimalEntity>> getAnimals(String ownerId, AnimalType animalType) {
    return list("ownerId = ?1 AND animalType = ?2", ownerId, animalType).log()
        .onItem()
        .invoke(
            result ->
                log.debug(
                    "Found {} entities for parameters '{}' - '{}'",
                    result.size(),
                    ownerId,
                    animalType))
        .onFailure()
        .invoke(
            throwable ->
                log.error(
                    "Error while getting accounts for parameters '{}' - '{}'",
                    ownerId,
                    animalType,
                    throwable));
  }

  @WithSession
  public Uni<List<Dog>> getDogs(String ownerId) {
    return getAnimals(ownerId, AnimalType.DOG).map(animalEntityMapper::mapToDogs);
  }

  @WithSession
  public Uni<List<Cat>> getCats(String ownerId) {
    return getAnimals(ownerId, AnimalType.CAT).map(animalEntityMapper::mapToCats);
  }

  @WithSession
  public Uni<List<Rat>> getRats(String ownerId) {
    return getAnimals(ownerId, AnimalType.RAT).map(animalEntityMapper::mapToRats);
  }

  @WithSession
  public Uni<List<Dolphin>> getDoplphins(String ownerId) {
    return getAnimals(ownerId, AnimalType.DOLPHIN).map(animalEntityMapper::mapToDolphins);
  }

Code which need to work, but not:

    Uni<List<Dog>> dogs = zooService.getDogs(owner);
    Uni<List<Cat>> cats = zooService.getCats(owner);
    Uni<List<Rat>> rats = zooService.getRats(owner);
    Uni<List<Dolphin>> dolphins = zooService.getDoplphins(owner);
    
    final AnimalsDto summary = new AnimalsDto();
    
    return Uni.combine().all().unis(dogs, cats, rats, 
    dolphins).asTuple().map(animal -> {
         summary.setDogs(animal.getItem1());
         summary.setCats(animal.getItem2());
         summary.setRats(animal.getItem3());
         summary.setDolphins(animal.getItem4());
         return summary;
    });

and implemented working solution founded from Quarkus github issues:

     return dogs.invoke(summary::setDogs)
        .chain(() -> cats.invoke(summary::setCats))
        .chain(() -> rats.invoke(summary::setRats))
        .chain(() -> dolphins.invoke(summary::setDolphins))
        .replaceWith(summary);
  • 1
    that code with chains looks like calling dogs and cats sequential not parallel, isn't it the whole idea of using **Uni** to make it all parallel ? – ozkanpakdil Apr 25 '23 at 19:39
  • 1
    @ozkanpakdil yes you are right, but i need sadly say for now i don't found other working solution. if anyone has a better solution, especially parallel, I will be extremely happy if they share it with us – Luka Juršnik Apr 26 '23 at 05:48
  • Your answer could be improved by adding a code example. Please [edit] to add a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example), so that others can confirm that your answer is correct. – Matthieu H Apr 27 '23 at 11:48