5

I'm trying to use the quarkus-hibernate-reactive extension with the quarkus-vertx extension and I have issues persisting data. My project looks roughly like this:

FruitResource:

@Inject
EventBus eventBus;

@POST
public Uni<Response> create(Fruit fruit) {
    if (fruit == null || fruit.getId() != null) {
        throw new WebApplicationException("Id was invalidly set on request.", 422);
    }

    return eventBus.<Void>request("create-fruit", fruit)
        .map(ignore -> Response.ok(fruit).status(201).build());
}

FruitService:

@Inject
FruitRepository fruitRepository;

@ConsumeEvent("create-fruit")
public Uni<Void> createFruit(final Fruit fruit) {
    return fruitRepository.create(fruit);
}

FruitRepository:

@Inject
Mutiny.Session mutinySession;

public Uni<Void> create(final Fruit fruit) {
    return mutinySession
        .persist(fruit)
        .chain(mutinySession::flush);
}

The exception I get is a java.lang.IllegalStateException: Session/EntityManager is closed, which I assume happens during the flush(). I guess my session closes somewhere down the way but I have no idea where and how to prevent this.

The full example can be found here: https://github.com/bamling/quarkus-hibernate-reactive-test FruitsEndpointTest simulates the behaviour!

tsegismont
  • 8,591
  • 1
  • 17
  • 27
user2090339
  • 51
  • 1
  • 3

3 Answers3

1

I think this was a bug in Quarkus that's now being fixed: https://github.com/quarkusio/quarkus/issues/14595

I've tested your project by changing the version to quarkus 1.12.1.Final and it worked.

Davide D'Alto
  • 7,421
  • 2
  • 16
  • 30
0

Ok, I know this answer won't be exactly what you are looking for, but this is the only working solution I found up until this point. I will keep on looking for a way to implement it with all the Hibernate-goodness, but here is a solution without:

@ApplicationScoped
public class FruitRepository {

    @Inject
    PgPool client;

    public Multi<Fruit> findAll() {
        return client.query("SELECT id, name FROM known_fruits ORDER BY name ASC").execute()
                .onItem().transformToMulti(set -> Multi.createFrom().iterable(set))
                .onItem().transform(FruitRepository::map);
    }

    public Uni<Void> create(final Fruit fruit) {
        return client.preparedQuery("INSERT INTO known_fruits (id, name) VALUES ($1, $2)")
                .execute(Tuple.tuple().addInteger(fruit.getId()).addString(fruit.getName()))
                .chain(e -> Uni.createFrom().voidItem());
    }

    public static Fruit map(Row row) {
        Fruit fruit = new Fruit();
        fruit.setId(row.getInteger("id"));
        fruit.setName(row.getString("name"));
        return fruit;
    }

}

It works, it is reactive, but it also means manually managing the Object Relations. Not ideal.

UPDATE:

Here is a working example with Hibernate. BUT(!) it is far from perfect. Although it is closer to answering your question, I would always prefer to use the above example over this one. The session should be closed. I would like to do a 'withSession()' for the findAll method as well, but this returns a Uni, while we need a Multi. Try with resources did not work, because that will close the session before the Multi has been read. So don't use this code, but maybe it will help others to come up with a better answer:

@ApplicationScoped
public class FruitRepository {

    private final Mutiny.SessionFactory sessionFactory;

    public FruitRepository(EntityManagerFactory entityManagerFactory) {
        this.sessionFactory = entityManagerFactory.unwrap(Mutiny.SessionFactory.class);
    }

    public Multi<Fruit> findAll() {
        Mutiny.Session session = sessionFactory.openSession();
        return session.createNamedQuery("Fruits.findAll", Fruit.class).getResults();
    }

    public Uni<Void> create(final Fruit fruit) {
        return sessionFactory.withSession(
                session -> session.persist(fruit)
                .chain(e -> Uni.createFrom().voidItem())
        );
    }
}
Robert van der Spek
  • 1,017
  • 3
  • 16
  • 37
0

Just do this:

public Uni<Void> create(final Fruit fruit) {
    return sessionFactory.withTransaction((session, tx) -> session.persist(fruit));
}

In order to save anything to the DB via Hibernate, you need to have:

  • a session
  • a transaction

Hibernate Reactive has a slightly different API, since it's now non-blocking, so the best way to do this is to inject a Mutiny.SessionFactory. By using withTransaction() you'll make sure that you have an open session and a transaction.

Taras Boychuk
  • 2,059
  • 13
  • 21