quarkus-hibernate-reactive + Vert.x EventBus вызывает java.lang.IllegalStateException: сеанс /EntityManager закрыт

Я пытаюсь использовать quarkus-hibernate-reactive расширение с quarkus-vertxextension, и у меня проблемы с сохранением данных. Мой проект выглядит примерно так:

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);
}

Исключение, которое я получаю, это java.lang.IllegalStateException: Session/EntityManager is closed, что, как я предполагаю, происходит во время flush(). Я предполагаю, что моя сессия где-то закрывается, но я не знаю, где и как это предотвратить.

Полный пример можно найти здесь: https://github.com/bamling/quarkus-hibernate-reactive-test FruitsEndpointTest имитирует поведение!

3 ответа

Я думаю, что это была ошибка в Quarkus, которая сейчас исправлена: https://github.com/quarkusio/quarkus/issues/14595 .

Я проверил ваш проект, изменив версию на quarkus 1.12.1.Finalи это сработало.

Хорошо, я знаю, что этот ответ будет не совсем тем, что вы ищете, но это единственное рабочее решение, которое я нашел до этого момента. Я буду продолжать искать способ реализовать его со всеми достоинствами Hibernate, но вот решение без:

      @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;
    }

}

Он работает, он является реактивным, но это также означает ручное управление отношениями объектов. Не идеально.

ОБНОВИТЬ:

Вот рабочий пример с Hibernate. НО (!) Он далек от совершенства. Хотя это ближе к ответу на ваш вопрос, я всегда предпочитаю использовать приведенный выше пример, а не этот. Сессию следует закрыть. Я также хотел бы сделать withSession () для метода findAll, но он возвращает Uni, а нам нужен Multi. Попытка с ресурсами не сработала, потому что это закроет сеанс до того, как Multi будет прочитан. Так что не используйте этот код, но, возможно, он поможет другим придумать лучший ответ:

      @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())
        );
    }
}

Просто сделай это:

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

Чтобы что-либо сохранить в БД через Hibernate, вам необходимо иметь:

  • сессия
  • сделка

Hibernate Reactive имеет немного другой API, поскольку теперь он неблокирующий, поэтому лучший способ сделать это - внедрить Mutiny.SessionFactory. Используя withTransaction() вы убедитесь, что у вас есть открытый сеанс и транзакция.

Другие вопросы по тегам