How to Get Rid of Hibernate's GenericJDBCException

When working with hibernate did you ever run into the GenericJDBCException: Could not execute JDBC batch update without any further information about the root cause? If you’re looking for a solution this post is for you.

To give you a clearer picture what I’m talking about here’s a stacktrace:

Hibernate: insert into foo_bar_map (foo_id, bar_id) values (?, ?)
2012-09-13 16:18:22,693 W [main] o.h.u.JDBCExceptionReporter SQL Error: 0, SQLState: null
2012-09-13 16:18:22,693 E [main] o.h.u.JDBCExceptionReporter failed batch
2012-09-13 16:18:22,724 E [main] o.h.e.d.AbstractFlushingEventListener
Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
    at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:140)
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:128)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:187)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    ...
Caused by: java.sql.BatchUpdateException: failed batch
    at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
    ... 46 common frames omitted

The problem here is the BatchUpdateException: failed batch - it does’t tell you why the given statement could not be executed.

How to proceed in this case?

One solution is to use BatchUpdateException.getNextException to get the reason of the batch exception. However, it may happen that this just returns null, e.g. because the jdbc driver doesn’t support it.

Therefore I want to present you an alternative solution.

Do not batch

When we don’t get any information about the reason of the batch update failure, we have to find a way to prevent hibernate from batching. This is useful when running in an isolated environment like a testcase for example.

For changing the execution strategy hibernate provides the hibernate.jdbc.factory_class configuration property which accepts implementations of org.hibernate.jdbc.BatcherFactory (see hibernate documentation on session configuration for more). Hibernate already provides the NonBatchingBatcherFactory, which is what we want in our case.

The appropriate hibernate configuration might look like this:

final Configuration config = new Configuration();
// ... more config here
config.setProperty( "hibernate.jdbc.factory_class", "org.hibernate.jdbc.NonBatchingBatcherFactory" );

The code that produced the BatchUpdateException above would now create the following stacktrace:

Hibernate: insert into foo_bar_map (foo_id, bar_id) values (?, ?)
2012-09-13 16:25:53,394 W [main] o.h.u.JDBCExceptionReporter SQL Error: -104, SQLState: 23000
2012-09-13 16:25:53,394 E [main] o.h.u.JDBCExceptionReporter Violation of unique constraint SYS_CT_445: duplicate value(s) for column(s) BAR_ID in statement [insert into foo_bar_map (foo_id, bar_id) values (?, ?)]
2012-09-13 16:25:53,752 E [main] o.h.e.d.AbstractFlushingEventListener Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: could not insert collection rows:
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.persister.collection.AbstractCollectionPersister.insertRows(AbstractCollectionPersister.java:1454)
    at org.hibernate.action.CollectionUpdateAction.execute(CollectionUpdateAction.java:86)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:187)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    ...
Caused by: java.sql.SQLException: Violation of unique constraint SYS_CT_445: duplicate value(s) for column(s) BAR_ID in statement [insert into foo_bar_map (foo_id, bar_id) values (?, ?)]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:46)
    at org.hibernate.persister.collection.AbstractCollectionPersister.insertRows(AbstractCollectionPersister.java:1427)
    ... 48 common frames omitted

This information is in fact much more helpful, and helps to solve the original issue.

Just to make it clear: it’s helpful to change the batcher strategy once for a test run to see individual sql exceptions. But you should not change the strategy for all tests or for continuous integration while production settings are different.

Comments