Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA/Hibernate - Undesired Partial Rollback and Session Handling

I am using a stateless EJB class to update a persistence entity located in a database. A method within the EJB calls an implementation class where the work is done. I believe what is causing the issue is that an entity called Foo, has a oneToMany relationship with an entity Bar. Things are done, and the Session is updated with Foo which 'cascades' to Bar. When a StaleObjectStateException occurs, the transaction is not being fully rolled back which is causing errors for obvious reasons.

EJB:

private Session getSession() throws BusinessException {

    if( this.sess == null ) {
            ServiceLocator locator = new ServiceLocator();
            SessionFactory sf = locator.getHibernateSessionFactory();
            this.sess = sf.openSession();
    }
    return this.sess;

}

private ProductionOrderImpl getImpl() throws BusinessException {

    if( this.impl == null ) {
        this.impl = new ProductionOrderImpl( getSession() );
    }
    return this.impl;

}

public void cutoffOrders(  ) throws Exception {

    Transaction tx = null;
    try {
        tx = getSession().beginTransaction();
        getImpl().cutOffFoos(fooTime);
        tx.commit();
    } catch (StaleObjectStateException e1){
        if (tx != null) tx.rollback();
        logger.error( "Failed to cutoff order : " + e1 );
        throw new Exception( LocaleMgr.getMessage());
    } 
      finally {
        // reset implementation object, close session,
        // and reset session object
        impl = null;
        sess.close();
        sess = null;
    }   
}

Implementation:

public ProductionOrderImpl(Session sess) {
    this.sess = sess;
}

public void cutoffFoos(  Timestamp fooTime) throws Exception {
    ... Code that gets fooList ...
    if( fooList != null ) {
        for( Foo foo: fooList ) {
            for( Bar bar : foo.getBarList() ) {
                 ... Code that does things with existing Barlist ...
                 if( ... ) {
                     ... Code that makes new Bar object ...
                     foo.getBarList().add(bar2);
                 }
            }
            sess.update( foo );
        }
    }
}

Relevant Foo code:

@OneToMany(cascade=CascadeType.ALL, mappedBy="foo")
@OrderBy("startTime DESC")
Set<Bar> barList;

So basically, when the Transaction tries to rollback, the Bar parts that were changed get rolled back, but the new Bar (bar2 in code) records remain..

Any guidance would be appreciated. Like I said I believe the error here has to do with the sess.update(foo); possibly something to do with autocommit, but by default it should be off.

I believe that what is happening, is that the Session.Update(foo) is in turn creating two separate transactions. Specifically, the Foo is updated (SQL UPDATE), but the Bar is saved (SQL INSERT). Since the transaction context would only really see the SQL UPDATE, that is all it reverses. Will have to look into this more..

I have tried changing the Session.FlushMode to COMMIT but it still doesn't seem to fix the issue. It does however, partially fix the problem.. It will rollback the entries properly except for the particular entry which causes the StaleObjectStateException. That particular entry is actually deleted right out of the database...

like image 284
ballBreaker Avatar asked Jul 20 '15 19:07

ballBreaker


People also ask

What is the difference between JPA and Hibernate?

Conclusion: The major difference between Hibernate and JPA is that Hibernate is a framework while JPA is API specifications. Hibernate is the implementation of all the JPA guidelines.

What is the use of @transactional in Hibernate?

The @Transactional annotation is the metadata that specifies the semantics of the transactions on a method. We have two ways to rollback a transaction: declarative and programmatic. In the declarative approach, we annotate the methods with the @Transactional annotation.

How Hibernate handle multiple transactions?

Therefore, you can run multiple transactions on the same Hibernate Session, but there's a catch. Once an exception is thrown you can no longer reuse that Session. My advice is to divide-and-conquer. Just split all items, construct a Command object for each of those and send them to an ExecutorService#invokeAll .


1 Answers

Well I managed to fix my issue.. I will wait to accept it in case someone else posts something better, something more... bounty worthy.

Basically, by changing the FlushMode to manual, and manually flushing throughout, I can catch the StaleObjectException earlier, which backs the code out sooner. I still have artifacts of the partially rolled-back records.. However, this method runs every 2 minutes on a schedule, so during the second pass it fixes all of the issues.

I changed my EJB to have the following:

public void cutoffOrders(  ) throws Exception {
  Transaction tx = null;
  try {
      tx = getSession().beginTransaction();
      getSession().setFlushMode(FlushMode.MANUAL);
      getImpl().cutOffFoos(fooTime);
      getSession().flush();
      tx.commit();
  } catch (StaleObjectStateException e1){
      if (tx != null) tx.rollback();
      logger.error( "Failed to cutoff order : " + e1 );
      throw new Exception( LocaleMgr.getMessage());
  } 
    finally {
      // reset implementation object, close session,
      // and reset session object
      impl = null;
      sess.close();
      sess = null;
  }   
}

Then the Implementation code to have the following:

public void cutoffFoos(  Timestamp fooTime) throws Exception {
  ... Code that gets fooList ...
  if( fooList != null ) {
      for( Foo foo: fooList ) {
          for( Bar bar : foo.getBarList() ) {
               ... Code that does things with existing Barlist ...
               sess.flush();
               if( ... ) {
                   ... Code that makes new Bar object ...
                   foo.getBarList().add(bar2);
               }
          }
          sess.flush();
          sess.update( foo );
      }
  }
}
like image 102
ballBreaker Avatar answered Oct 04 '22 15:10

ballBreaker