4 Replies Latest reply on Feb 21, 2013 4:13 AM by Mauro Molinari

    TransactionalDriver and Hibernate (on Tomcat) - Don't forget to flush!

    talawahdotnet Newbie

      Ran 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();
      
        • 2. Re: TransactionalDriver and Hibernate (on Tomcat) - Don't forget to flush!
          Mauro Molinari Novice

          We are following a slightly different approach that never caused us such a problem:

           

          tx.begin();
          // do all the code handling persistence
          tx.commit();
          

           

          So your code would become:

           

          tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
          tx.begin();Session session = sessionFactory.openSession();
          session.save(new Event("Our very first event!", new Date()));
          session.close();
          tx.commit();
          

           

          Hibernate is configured not to control transactions, so session.close() does not commit anything, just flushes changes if needed.

           

          This has the additional advantages that you may even do something else (not bound to Hibernate) within the scope of the global transaction and enclose the Hibernate code in a template method. Something like this (where doInHibernate prepares the Session, executes the closure and then closes the Session):

           

          tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
          tx.begin();
          doInHibernate(new Closure() {
            public void call(Session session)
            {
              session.save(new Event("Our very first event!", new Date()));
            }
          });
          // do something else without Hibernate, like updating a different database with plain JDBC or using another ORM
          tx.commit(); // this commits everything!
          

           

          Hope this helps.

          Mauro.

          • 3. Re: TransactionalDriver and Hibernate (on Tomcat) - Don't forget to flush!
            talawahdotnet Newbie
            tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
            tx.begin();Session session = sessionFactory.openSession();
            session.save(new Event("Our very first event!", new Date()));
            session.close();
            tx.commit();
            

             

            This approach would have been problematic for me based on my current setup.  Closing the connection before tx.commit() would have either resulted in no connection being available for commit or the connection never being properly closed. See my recent post here.  I suspect you have your own custom connection pooling so you don't have the same issue.

             

             

            tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
            tx.begin();
            doInHibernate(new Closure() {  public void call(Session session)
              {    session.save(new Event("Our very first event!", new Date()));
              }});
            // do something else without Hibernate, like updating a different database with plain JDBC or using another ORMtx.commit(); // this commits everything!

             

            Hmmm, that "closure" approach just gave me an idea as to how to remove some of the redundant boilerplate code I have in my DAOs.  Since I don't have the benefit of the magical @Transactional attribute, right now almost all my methods look like this:

             

                 public User createUser(String id, String firstName, String lastName, int age){
                      Session session = sessionFactory.openSession();
                      try {
                           tx.begin();
                           User newUser = new User(id, firstName, lastName, age);   
                           session.save(newUser);
                           session.flush(); // Manually flush otherwise TM enlistment won't happen
                           tx.commit();
                      } catch (Exception e) {
                           try {
                                if (tx != null) { tx.rollback(); }
                           } catch (SystemException e1) {
                                throw new RuntimeException("Excption thrown while attempting rollback", e1);
                           }
                           throw new RuntimeException("Excption thrown while attempting transaction", e);
                      }
                      finally {
                           session.close();
                      }
                      return newUser;
                 }
            

             

            Which is obviously a whole lot of repetition for the sake of tx.begin/commit/rollback with try/catch/finally. But now I am thinking about replacing it using a simple interface for passing the Hibernate commands to a single method that wraps them with the relevant transactional code. e.g

             

             

                 private interface HibernateCommands {
                      Object execute(Session session);
                 }
            
            
                 private Object txWrapper(HibernateCommands commandsToExecute){
                      Object result = null;
                      Session session = sessionFactory.openSession();
                      try {
                           tx.begin();
                           result = commandsToExecute.execute(session);
                           tx.commit();
                       } catch (Exception e) {
                           try {
                                 if (tx != null) { tx.rollback(); }
                           } catch (SystemException e1) {
                                throw new RuntimeException("Excption occured while attempting rollback", e1);
                           }
                           throw new RuntimeException("Excption occured while attempting transaction", e);
                      } 
                      finally {
                           session.close();
                      } 
                      return result;
                 }
            
            

             

            Then I could replace the original block of code with this:

             

                 public User createUser(String id, String firstName, String lastName, int age){
                                                       
                      HibernateCommands commandsToExecute = new HibernateCommands() { 
                           public Object execute(Session session){                                  
                                User newUser = new User(id, firstName, lastName, age);   
                                session.save(newUser);
                                session.flush(); // Manually flush otherwise TM enlistment won't happen     
                                return newUser;
                           }
                      }; 
                      return (User) txWrapper(commandsToExecute);
                 }
            

             

            Still kinda ugly, but at least there is less repetition that what I have now.

            • 4. Re: TransactionalDriver and Hibernate (on Tomcat) - Don't forget to flush!
              Mauro Molinari Novice

              The subject is complex, so please forgive me if I say something wrong, I'm going by heart right now. Anyway:

               

               

              talawahdotnet ha scritto:

               

              tx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
              tx.begin();Session session = sessionFactory.openSession();
              session.save(new Event("Our very first event!", new Date()));
              session.close();
              tx.commit();
              

               

              This approach would have been problematic for me based on my current setup.  Closing the connection before tx.commit() would have either resulted in no connection being available for commit or the connection never being properly closed. See my recent post here.  I suspect you have your own custom connection pooling so you don't have the same issue.

               

              Well, strictly speaking, since we're talking about a global XA transaction (and not a JDBC local transaction), it's not necessary that the physical connection you see from your code is actually open or not: the TranscationalDriver should handle the lifecycle of the XA (and physical) connection when you begin/commit/rollback your transaction. Looking at the other discussion, I think you've hit bug https://issues.jboss.org/browse/JBTM-789, for which we patched JBossTS internally (well, all the other issues Tom has pointed you  at have actually been discovered by myself and patched internally ;-)).

              This said, yes, we're now doing connection pooling, but we're currently pooling XA connections below the TransactionalDriver (at the DynamicClass level), while on top of the TransactionalDriver we're using a DataSource which does not do any pooling. On the other hand, the Spring TransactionalAwareDataSourceProxy is used to bind connections to transactions, but as far as I understand it (this is the most critical part of the whole picture for me), it is used just because we're opening and closing JDBC connections multiple times in our code within the scope of the same  global transaction, so it's important for the system not to give you connections associated to different transactions when pooling is involved (althuogh, as I said, we're not doing connection pooling on top of the TransactionalDriver, so the actual need of this proxy is not currently very clear to me and might be related to the Spring infrastructure we're using on top of JBossTS only).

              Anyway, in the past we also invested in doing connection pooling above the TransactionalDriver, instead of below it, and the whole thing did work as well, but some complex and not-straightfoward use cases involving transaction propagation did not work  so well.

               

              As for the future of JBossTS and the JCA connector approach: I'm still waiting to see some documentation on how it is supposed to work compared to the TransactionalDriver approach.  I'm still concerned about the fact that this would require us to embed some JEE-related tecnologies (like JNDI, other?) we currently don't use in our webapp since we don't need at all.