Resolving Concurrent Exceptions in Hibernate logger

In which I found and resolved a fix for an issue with the Hibernate debug logger causing entity relationship saves to fail with a ConcurrentModificationException.

Recently while working on a Hibernate ORM project, I ran into an interesting issue when an entity with relationships is saved inside a transaction.

transaction{
  var myEntity = entityLoadByPK( "Person", theEntityID );

  myEntity.setAddress( otherEntity );
  myEntity.save();

// throws "ConcurrentModificationException" on transaction end.
}

Note this is not a complete test case - simply an example of when the issue occurs.

When the transaction completes, Lucee tries to flush the entity modifications, but this fails with the following error:

lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493)
at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1526)
at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1524)
at org.hibernate.internal.util.EntityPrinter.toString(EntityPrinter.java:112)
at org.hibernate.event.internal.AbstractFlushingEventListener.logFlushResults(AbstractFlushingEventListener.java:128)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:104)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:93)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1362)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1349)
at org.lucee.extension.orm.hibernate.HibernateORMTransaction.end(HibernateORMTransaction.java:57)
at lucee.runtime.orm.ORMConnection.setAutoCommit(ORMConnection.java:213)
at lucee.runtime.orm.ORMDatasourceConnection.setAutoCommit(ORMDatasourceConnection.java:336)
at lucee.runtime.db.DatasourceManagerImpl.end(DatasourceManagerImpl.java:350)
at lucee.runtime.db.DatasourceManagerImpl.end(DatasourceManagerImpl.java:330)
at lucee.runtime.tag.Transaction.doFinally(Transaction.java:160)

It turns out this issue is related to the Hibernate debug logger. In Lucee, Hibernate's log4j logger is set to a DEBUG level by default. That's fine, but it seems the logger actually mutates the logged entities during logging. Thus we get a concurrent modification exception.

To resolve this, all we have to do is de-escalate the logging level from DEBUG to WARN.

/**
 * Resolves Hibernate ConcurrentModificationException when flushing an entity save with one-to-many relationships.
 * 
 * @see https://access.redhat.com/solutions/29774
 */
var Logger = createObject( "java", "org.apache.log4j.Logger" );
var level = createObject( "java", "org.apache.log4j.Level" );
var log = Logger.getLogger( "org.hibernate" );
log.setLevel( level.WARN );

This should be done in the Application.cfc before ORM initializes - say, just below the this.ormSettings{} configuration block.

All thanks to @jclausen for this awesome find and easy fix!

August 20, 2021

« Cross-engine transaction detection in Hibernate v3+ - Redirecting Hibernate Logs to the CommandBox Console »