After a failed ORM flush, every later ORM transaction in the request throws "Transaction already active"

When an ORM flush fails inside transaction{} (for example, a value too long for its column), the extension never ends the Hibernate transaction. Every later ORM operation inside a transaction{} in the same request then throws java.lang.IllegalStateException: Transaction already active. Neither ormClearSession() nor transactionRollback() clears it. The connection is not leaked, and the next request works.

This is a behavior change from prior ORM extensions and poses an obstacle with upgrading legacy code.

Setup:
OS: Amazon Linux 2023
Java Version: 21
Tomcat Version: 9.0.2.2
Lucee Version: 6.2.9.4 with ORM Extension 5.6.15.21

Repro:

// Item.cfc: persistent="true" table="item"
//   id:   fieldtype="id" generator="assigned" type="string" length="32"
//   name: type="string" length="64"
function newItem( string name = "ok" ) {
    var item = entityNew( "Item" );
    item.setId( replace( createUUID(), "-", "", "all" ) );
    item.setName( arguments.name );
    return item;
}

try {
    transaction {
        // the flush at the end of the block fails: Data too long for column
        entitySave( newItem( repeatString( "x", 5000 ) ) );
    }
} catch ( any e ) {}

ormClearSession();

transaction {
    entitySave( newItem() );   // IllegalStateException: Transaction already active
}

Expected: the failed block’s Hibernate transaction is rolled back and ended, so later transactions in the request work. With the Ortus ORM extension 6.5.4, ormClearSession() recovers the session.

Actual (stack from 6.2.9.4; 7.1.2.191 throws the same message):

java.lang.IllegalStateException: Transaction already active
  at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:74)
  at org.lucee.extension.orm.hibernate.HibernateORMTransaction.begin(HibernateORMTransaction.java:58)
  at lucee.runtime.orm.ORMConnection.<init>(ORMConnection.java:66)
  at lucee.runtime.orm.ORMDatasourceConnection.<init>(ORMDatasourceConnection.java:76)
  at org.lucee.extension.orm.hibernate.util.ORMUtil.getSession(ORMUtil.java:52)
  at org.lucee.extension.orm.hibernate.functions.EntitySave.call(EntitySave.java:44)

Thanks for the clear report and the repro, that made this straightforward to pin down.

We’ve opened LDEV-6935 for it. When the flush at the end of transaction{} fails, the Hibernate transaction is marked rollback-only but never actually ended, so the next transaction{} in the same request hits “Transaction already active”. ormClearSession() only clears the session, and a later transactionRollback() no longer sees the original transaction, which is why neither recovers it. The next request works because the session is closed at the end of the request.

We’ll fix this so a failed flush rolls that Hibernate transaction back, and we’ll also look at ormClearSession() so it can recover the same way the Ortus extension does.