TransactionalDriver and Hibernate (on Tomcat) - Don't forget to flush!
talawahdotnet Feb 17, 2013 7:13 PMRan into this issue TransactionalDriver and Hibernate (on Tomcat) so I figured I would post it here in case anyone else had s similar problem. I have been using TransactionalDriver with a modified version of the approach outlined here. TransactionalDriver handles the process of enlisting you XA Datasource as an XA Resource for a given transaction whenever a connection is made to the database. However Hibernate has a way of optimizing database access that can result in very confusing behavior when inserting or updating a database using this approach. My initial code looked something like this:
tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); Session session = sessionFactory.openSession(); tx.begin(); List<Event> result = session.createQuery( "from Event", Event.class ).getResultList(); tx.commit(); session.close();
which works fine for reading from the database (once you deal with all the other issues) however when I started testing updates, i saw inconsistencies
This code did not work. No errors were thrown, but the database just wasn't being updated.
tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); Session session = sessionFactory.openSession(); tx.begin(); session.save(new Event("Our very first event!", new Date())); tx.commit(); session.close();
But this code did. Updates were successfully persisted
tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); Session session = sessionFactory.openSession(); tx.begin(); session.save(new MyCustomEvent("Our very first event!", new Date())); tx.commit(); session.close();
I found this quite puzzling as there appeared to be only superficial differences in the entities. After stepping through the Hibernate code a number of times, I realized what was happening. Hibernate tries to delay connecting to the database until as late as possible (most likely during commit) to optimize performance, but because TransactionalDriver only enlists XA Resource when a DB connection is established you get a chicken and egg problem. If the connection has not yet been made at the point when tx.commit() is called, the corresponding XA Resource would not yet have been enlisted; as such Hibernate is never actually instructed by the Transaction Manager to commit() the transaction (because the TM is not aware of Hibernate) and no database connection is ever established.
In the case of MyCustomerEvent, I had annotated it as follows
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
This means that it was relying on the MySQL database to provide the auto-generated value for the ID field of that entity. In this case Hibernate has to forgo its optimization and connect to the database as soon as session.save() is called so that I can retrieve the database generated ID value and provide it to the user. When that happens, enlistment happens before commit() and the transaction gets successfully committed.
In order to enforce uniform behavior across the board, I had to manually flush the session and force the connection before tx.commit();
tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); Session session = sessionFactory.openSession(); tx.begin(); session.save(new Event("Our very first event!", new Date())); session.flush(); tx.commit(); session.close();