简体   繁体   中英

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...

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 );
      }
  }
}

Well,here are my two cents on this,as this also pertains to JPA :

In Spring Data JPA you could just use the following :

1.@Transactional annotation before making a repository call (handles the rollback)

2.Use JPA repository saveAndFlush method ,ie :

@Service
public class ProductionOrderServiceImpl extends ProductionOrderService{

    @Autowired
    ProductionOrderRepository jpaRepository;

    @Transactional
    public void cutoffOrders( Timestamp fooTime ){

    ... Code that gets fooList ...
      if( fooList != null ) {
          for( Foo foo: fooList ) {
              for( Bar bar : foo.getBarList() ) {
                   ... Code that does things with existing Barlist ...
                   {Call another similar method with @transactional..}//saveAndFlush(BarList);
                   if( ... ) {
                       ... Code that makes new Bar object ...
                       foo.getBarList().add(bar2);
                   }
              }
              jpaRepository.saveAndFlush(foo);
          }
      }

    }

}

What save and flush internally does is this:

/*
     * (non-Javadoc)
     * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
     */
@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

and then em.flush();

And in case ,after this,if you run into @Audited versioning issue,where deleted record does not show up ,then set org.hibernate.envers.store_data_at_delete=true.

Hope that adds perspective to the solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM