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

#postgresql #vert.x #quarkus #hibernate-reactive

#postgresql #vert.x #quarkus #гибернация-реактивная

Вопрос:

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

Фруктовый источник:

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

Фруктовый сервис:

 @Inject
FruitRepository fruitRepository;

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

Хранилище фруктов:

 @Inject
Mutiny.Session mutinySession;

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

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

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

Комментарии:

1. Можете ли вы попробовать с @RequestScoped репозиторием?

2. Это, к сожалению, ничего не изменило… такое же поведение.

Ответ №1:

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

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

Ответ №2:

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

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

}
 

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

Обновить:

Вот рабочий пример с гибернацией. НО (!) она далека от совершенства. Хотя это ближе к ответу на ваш вопрос, я бы всегда предпочел использовать приведенный выше пример, а не этот. Сессия должна быть закрыта. Я хотел бы также выполнить ‘withSession ()’ для метода findAll, но это возвращает Uni, в то время как нам нужен 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())
        );
    }
}
 

Ответ №3:

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

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

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

  • сеанс
  • транзакция

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