While migrating from Hibernate 4.x to latest Hibernate 5 version, I'm encountering an issue with regards to transaction management.
In my code, there is a transaction manager that begins a JTA transaction, followed by a call to a Session.beginTransaction
. Below is an example that reproduces the issue (the scenario is not using Spring or any other container managed transaction management):
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);
}
}
}
This is causing an IllegalStateException
to be thrown with the message "Transaction already active"
. This behavior seems to have been introduced in Hibernate 5.2.0 (this is the commit). Previously, Hibernate just ignored the beginning the physical transaction itself because it knows an enclosing transaction is present: it just creates a wrapper JtaTransaction
with isInitiator
set to false.
This exception is thrown in org.hibernate.engine.transaction.internal.TransactionImpl
, specifically the begin()
method:
@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();
}
This also contradicts with the user manual, where it says the below:
// 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();
Is this a bug in Hibernate? And what does the "per-JPA" comment mean exactly in the code that throws the exception? Is there a way to restore the old behavior?
This was actually a "regression" that I reported in https://hibernate.atlassian.net/browse/HHH-13076 which is now fixed (here's the PR link).