Гибернация: две или более транзакции одновременно: транзакция уже активна

У меня есть REST API, и когда я делаю POST и GET почти одновременно, я получаю это исключение:

 SEVERE: The RuntimeException could not be mapped to a response, re-throwing to the HTTP container
java.lang.IllegalStateException: Transaction already active
    at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:52)
    at org.hibernate.internal.AbstractSharedSessionContract.beginTransaction(AbstractSharedSessionContract.java:409)
    at sun.reflect.GeneratedMethodAccessor89.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:355)
    at com.sun.proxy.$Proxy58.beginTransaction(Unknown Source)
    at utils.HibernateSession.createTransaction(HibernateSession.java:15)
    at api.ConversationsREST.getMessages(ConversationsREST.java:128)

Они находятся в разных классах, поэтому не подразумеваются глобальные атрибуты.

Строка, которая терпит неудачу, такова:

HibernateSession hs = new HibernateSession();
hs.createTransaction(); // Crash

Что относится к моему классу HibernateSession:

public class HibernateSession {

    public Session session;

    public void createTransaction() {

        session = HibernateUtil.getSessionFactory().getCurrentSession(); //THIS WAS WRONG
 //EDIT:session = HibernateUtil.getSessionFactory().openSession(); //THIS IS RIGHT
        session.beginTransaction();
    }

    public void commitclose() {

        session.getTransaction().commit();
        session.close();

    }

    public void rollbackclose() {

        try {
            session.getTransaction().rollback();
            session.close();
        } catch (Exception hibernateexception) {
            hibernateexception.printStackTrace();
        }

    }

}

Исключение на самом деле на линии session.beginTransaction()

Я всегда делаю hs.commitclose (), а в блоках catch () и 404 всегда делаю rollbackclose ();

Проблема в том, что когда я делаю такие сообщения POST:

HibernateSession hs = new HibernateSession();
hs.createTransaction();
hs.session.save(whatever);
hs.commitclose();

Возвращает 200, и все в порядке, но затем GET может дать сбой с исключением, указанным выше. Когда я создаю экземпляр новый для HibernateSession, почему Hibernate, кажется, пытается разделить эту транзакцию?

Это происходит только тогда, когда я выполняю оба запроса за очень короткое время (я думаю, запуск транзакции между началом и фиксацией другого). Итак, я предполагаю, что Hibernate думает, что атрибут сеанса статичен или что-то в этом роде ...

Заранее спасибо за вашу помощь!

Обновлено: По запросу, HibernateUtil.java (проблема не здесь, но может помочь понять):

package utils;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static SessionFactory sessionFactory;

    public static SessionFactory getSessionFactory() {

        if (sessionFactory == null) {

            sessionFactory = build();

        }
        return sessionFactory;

    }

    private static SessionFactory build() {

        try {

            return new Configuration().configure().buildSessionFactory();

        } catch (Throwable ex) {

            System.err.println("Initial SessionFactory creation failed: " + ex);
            throw new ExceptionInInitializerError(ex);
        }

    }

}

Можете ли вы добавить HibernateUtil ...

MyTwoCents 15.06.2018 13:15

@ Ashish451 Конечно

Carlos López Marí 15.06.2018 13:17

Я добавил решение .. вы можете попробовать

MyTwoCents 15.06.2018 13:44
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
3
6 342
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Попробуйте переписать код как

public class HibernateSession {

    public Session session;
    int id;
    Transaction t = null;

    public void createTransaction() {
        session = HibernateUtil.getSessionFactory().getCurrentSession();
        t = session.beginTransaction();
    }

    public void commitclose() {
        t.commit();
        session.close();
    }

    public void rollbackclose() {

        try {
            t.rollback();
            session.close();
        } catch (Exception hibernateexception) {
            hibernateexception.printStackTrace();
        }

    }

}

Конечно, для каждой ссылки t требуются нулевые проверки.

Вызвано то же исключение, но вместо session.beginTransaction (); это было на t = session.beginTransaction ();

Carlos López Marí 15.06.2018 13:33

Последняя попытка. Заменить t = session.beginTransaction(); на t = session.getTransaction();

soufrk 15.06.2018 13:36

Но мне нужно это начать. Он должен быть неактивным, так как в первый раз я делаю GET. Я могу проверить, активен он или нет, но это всего лишь обходной путь, мне действительно нужно, чтобы транзакция была очень новым экземпляром каждый раз, когда создается экземпляр класса.

Carlos López Marí 15.06.2018 13:43

@ CarlosLópez, поэтому вам следует использовать фабрику сеансов! У вас есть только экземпляр один сеанса Hibernate, и он застрял внутри транзакции, потому что вы правильно его закрыли нет.

rwenz3l 15.06.2018 13:45

@ nwenz3l Я сделал, проблема была в getCurrentTransaction () вместо openTransaction, который я хотел сделать.

Carlos López Marí 15.06.2018 14:06

Есть много случаев использования, когда вы не получаете привилегии открывать новую транзакцию для каждой операции. В корпоративных средах вам часто приходится присоединяться к уже существующей транзакции, потому что транзакция очень дорогостоящая. Это действительно обходной путь. Учитывая ваш код, я чувствовал, что это не сильно повредит.

soufrk 15.06.2018 14:16
Ответ принят как подходящий

Согласно вашей логике createTransaction, вы получаете текущий сеанс от SessionFactory и запускаете из него транзакцию.

Вот в чем проблема.

Предположим, вы создали объект HibernateSession и начали транзакцию, но она все еще выполняется. Значит, вы еще не закрыли транзакцию.

Теперь вы создали еще один HibernateSession и попытаетесь запустить транзакцию, при этом будет сгенерировано исключение.

Итак, ваш этот код

public void createTransaction() {
    session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();
}

должно быть это

public void createTransaction() {
    session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();
}

getSessionFactory (). openSession () всегда открывает новый сеанс, который необходимо закрыть после завершения операций.

getSessionFactory (). getCurrentSession () возвращает сеанс, привязанный к контексту - вам не нужно его закрывать.

Использование getSessionFactory().openSession() будет работать, но при этом будет открываться сеанс для каждого вызова, это плохая практика, постарайтесь этого не делать. Это просто убьет приложение, когда будет много звонков.

cнŝdk 15.06.2018 14:05

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

MyTwoCents 15.06.2018 14:08

@ cнŝdk потратил много времени, чтобы обойти его. opensession не регистрируется в threadlocal. ужасный дизайн. все на самом деле в спячке. Я решил это, используя свои собственные реализации, но мне интересно. когда вы действительно получаете новый сеанс с помощью openSession () ... и начинаете транзакцию ... будет ли весь новый код, такой как добавление элементов, принадлежать новой транзакции? когда мы его закроем, вернемся ли мы к предыдущему? потому что кто получает работу? что решает это? это стек, который зависит от того, когда транзакция начинается и заканчивается?

mmm 20.03.2021 18:26

Да, это немного сбивает с толку и слишком сложная часть гибернации, но, как только вы закрываете Transaction, вы должны открыть новый, иначе вы не сможете сохранить данные, но когда вы откроете новый Transaction, вы должны убедиться, что чтобы закрыть его, а также да, все новые изменения кода (добавление, редактирование сущностей) будут храниться в этой транзакции, пока вы ее не commit.

cнŝdk 21.04.2021 10:29

Но всегда имейте в виду, что: Сеанс не является потокобезопасным и должен использоваться только одним потоком. Обычно это гарантируется SessionFactory, как указано в Глава Обработка сеансов и транзакций в документации Hibernate. И нет необходимости реализовывать свой собственный дизайн, если вы используете Spring или EJB, Transaction будет отлично обрабатываться этими Framework.

cнŝdk 21.04.2021 10:29

На самом деле в вашем реальном коде много недостатков:

  • Вы создаете новый экземпляр своего класса HibernateSession каждый раз, когда вызываете свой код, поэтому у вас будет много его экземпляров, пытающихся получить доступ к одному и тому же сеансу.
  • Каждый из этих экземпляров HibernateSession будет пытаться создать новую транзакцию, когда вы вызываете hs.createTransaction();, и поэтому вы получаете исключение в этой строке, потому что уже есть открытые транзакции, и вы пытаетесь открыть новую, потому что вы звонить session.beginTransaction(); каждый раз, не звоня в transaction.close();.

Здесь вы можете сделать свой HibernateSessionclass синглтоном, чтобы был доступен только один экземпляр, вы можете проверить этот Реализация одноэлементного шаблона для получения дополнительных сведений о том, как его реализовать.

И в методе createTransaction, который лучше называть getTransaction, а не просто вызывать session.beginTransaction();, вам нужно получить текущую транзакцию, если она существует, вы можете проверить ее с помощью метода session.getTransaction() и убедиться, что вы заключили свой код в блок try ...catch, вы можете проверить это Сеанс гибернации учебник для получения дополнительной информации.

Как видно из его редактирования, у него есть Singleton Factory, которую он может использовать для создания новых сессий, что было бы совершенно нормально. Его настоящая ошибка состоит в том, что в его методе фиксации нет ни finally { session.close() }, ни даже try{ .. }. Это оставило его сеанс открытым в какой-то момент, и теперь он застрял.

rwenz3l 15.06.2018 13:43

@ rwenz3l Да, я знаю, что код за ним следует одноэлементному шаблону, но поскольку он создает новый класс для обработки этого, ему лучше сделать это и на уровне своего класса.

cнŝdk 15.06.2018 13:45

У вас есть несколько недостатков, связанных с дизайном:

  1. Вы не должны использовать getCurrentSession()

Как указано в jboss документация, вы должны использовать следующую идиому:

 Session sess = factory.openSession();
 Transaction tx;
 try {
     tx = sess.beginTransaction();
     //do some work
     ...
     tx.commit();
 }
 catch (Exception e) {
     if (tx!=null) tx.rollback();
     throw e;
 }
 finally {
     sess.close();
 }
  1. Вы должны следовать шаблону CRUD

Для каждой операции (создание, чтение, обновление, удаление) создайте метод в своем классе. Каждый метод должен создать свой сеанс собственный и транзакцию для работы, потому что, как указано в этот ответ:

session is not a thread safe object - cannot be shared by multiple threads. You should always use "one session per request" or "one session per transaction"

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

Решение:

Причина сбоя вашего кода заключается просто в том, что транзакция все еще выполняется после вашего предыдущего взаимодействия с базой данных. Вот почему вы делаете finally{ session.close() } - чтобы сделать Конечно, чтобы сессия закрывалась при выходе из метода. Я предполагаю, что в какой-то момент ваш commit() не был успешным и оставил вашу гибернацию в транзакции / сеансе, которая с тех пор не закрывалась.

Чтобы исправить это, вы должны один раз вставить session.close () в свой код, выполнить его, а затем реализовать предложенный мной блок try-catch-finally, чтобы убедиться, что он закрыт в будущем.

Я изменил getCurrentSession () на openSession ()

Carlos López Marí 15.06.2018 13:57

Я только что изменился

session = HibernateUtil.getSessionFactory().getCurrentSession();

с участием

session = HibernateUtil.getSessionFactory().openSession();

Я уже открывал сеанс Фабрики.

Это будет работать, но на самом деле это откроет сеанс для каждого вызова, это плохая практика, постарайтесь не делать этого.

cнŝdk 15.06.2018 14:03

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