Hibernate 5.2.x Транзакция уже активна, странное поведение

При переходе с Hibernate 4.x на последнюю версию Hibernate 5 я столкнулся с проблемой, связанной с управлением транзакциями.

В моем коде есть диспетчер транзакций, который начинает транзакцию JTA, за которой следует вызов Session.beginTransaction. Ниже приведен пример, который воспроизводит проблему (сценарий не использует Spring или любое другое управление транзакциями, управляемыми контейнером):

transactionManager.begin();
saveOrUpdate(entity1);
saveOrUpdate(entity2);
...
transactionManager.commit();

private void saveOrUpdate(SomeEntity entity) {
    try (Session session = sessionFactory.openSession()) {
        session.setFlushMode(FlushMode.AUTO);
        session.beginTransaction();   // throws IllegalStateException "Transaction already active"
        try {
            session.saveOrUpdate(entity);
            session.getTransaction().commit();
        } catch (Exception ex) {
            session.getTransaction().rollback();
            throw RuntimeException(ex);
        }
    }
}

Это вызывает появление IllegalStateException с сообщением "Transaction already active". Похоже, такое поведение было введено в Hibernate 5.2.0 (). Ранее Hibernate просто игнорировал начало самой физической транзакции, потому что знал, что вложенная транзакция присутствует: он просто создавал оболочку JtaTransaction с isInitiator, установленным в false.

Это исключение возникает в org.hibernate.engine.transaction.internal.TransactionImpl, в частности, в методе begin():

@Override
public void begin() {
    if ( !session.isOpen() ) {
        throw new IllegalStateException( "Cannot begin Transaction on closed Session/EntityManager" );
    }

    if ( transactionDriverControl == null ) {
        transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
    }

    // per-JPA
    if ( isActive() ) {   // *** This is the problematic part *** //
        throw new IllegalStateException( "Transaction already active" );
    }

    LOG.debug( "begin" );

    this.transactionDriverControl.begin();
}

Это также противоречит руководству пользователя, где говорится следующее:

// Note: depending on the JtaPlatform used and some optional settings,
// the underlying transactions here will be controlled through either
// the JTA TransactionManager or UserTransaction

Session session = sessionFactory.openSession();
try {
    // Assuming a JTA transaction is not already active,
    // this call the TM/UT begin method.  If a JTA
    // transaction is already active, we remember that
    // the Transaction associated with the Session did
    // not "initiate" the JTA transaction and will later
    // nop-op the commit and rollback calls...
    session.getTransaction().begin();

Это ошибка в Hibernate? И что означает комментарий «per-JPA» именно в коде, выдающем исключение? Есть ли способ восстановить старое поведение?


person M A    schedule 02.11.2018    source источник
comment
Не могли бы вы опубликовать весь код вашего метода, где вы используете транзакцию? И какая среда выполнения у вас есть?   -  person Simon Martinelli    schedule 02.11.2018
comment
@SimonMartinelli Спасибо за комментарий. Я уточнил код. Среда представляет собой отдельное приложение, использующее Hibernate с пользовательской TM.   -  person M A    schedule 02.11.2018
comment
Хорошо, но почему вы вызываете session.beginTransaction() после transactionManager.begin()? У вас уже есть транзакция от TransactionManager, поэтому Hibernate прав, когда жалуется.   -  person Simon Martinelli    schedule 02.11.2018
comment
На самом деле код является частью модульного теста, и код, который открывает и закрывает сеанс, находится в методе, который вызывается повторно (код снова обновлен). Цель состоит в том, чтобы убедиться, что все вызовы повторно используют одно и то же соединение XA, поддерживаемое TransactionManager. Я понимаю, что это может не иметь совершенного смысла, но я не могу понять, как это объясняется в руководстве пользователя, тогда как на самом деле это не работает, когда транзакция JTA уже активна.   -  person M A    schedule 02.11.2018
comment
Похоже, что изменение в Hibernate 5.2.0 было сделано для соответствия EntityTransaction. Кажется, JPA требует этой ошибки? Но странно то, что опять же... это противоречит примеру в руководстве пользователя.   -  person M A    schedule 02.11.2018
comment
Я рекомендую зарегистрировать проблему в Hibernate JIRA hibernate.atlassian.net/secure/Dashboard.jspa   -  person Simon Martinelli    schedule 02.11.2018


Ответы (2)


Поведение соответствует ожидаемому. Вы начали транзакцию в диспетчере транзакций. Я предполагаю, что он работает в соответствии с JTA. Поэтому ожидается, что соединение, которое вы получили для openSession, примет участие в уже запущенной транзакции менеджеров транзакций.

Запуск дополнительной транзакции должен привести к этому Исключению.

person aschoerk    schedule 02.11.2018
comment
Спасибо за ответ. Я тоже думаю, что это ожидаемо. Однако раньше он работал в Hibernate 4 (фактически до Hibernate 5.2.0), и раздел руководства пользователя, указанный в вопросе, как бы подразумевает, что мой код должен работать (т.е. что session.beginTransaction() знает, что он находится внутри уже открытой транзакции ). Я также не смог найти ссылку (например, в JTA), подтверждающую, что исключение действительно ожидается. К сведению, я вставил hibernate.atlassian.net/browse/HHH-13076 в проект Hibernate, чтобы отслеживать это. - person M A; 05.11.2018

На самом деле это был «регресс», о котором я сообщил в https://hibernate.atlassian.net/browse/HHH-13076, который теперь исправлен (вот ссылка для PR).

person M A    schedule 19.11.2018