Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Event Listeners - Rollback

I implemented a Hibernate Event Listener like so:

public class AuditListener implements PostInsertEventListener {
  private static final long serialVersionUID = -966368101369878522L;

  @Override
  public void onPostInsert(PostInsertEvent event) {
    if (event.getEntity() instanceof Auditable) {
      StatelessSession session = null;
      try {
        session = event.getPersister().getFactory().openStatelessSession();
        Auditable auditableEntity = (Auditable)event.getEntity();
        session.beginTransaction();
        session.insert(new AuditTrail(auditableEntity.getClass().getSimpleName(),
            auditableEntity.getId(), auditableEntity.getStatus(),
            auditableEntity.getLastModified()));
        session.getTransaction().commit();
      } catch (HibernateException he) {
        System.out.println("Horrible error: " + he.getMessage());
        session.getTransaction().rollback();
      } finally {
        if (session != null) {
          session.close();
        }
      }
    }
  }
}

All it does is insert an AuditTrail object into the database right after inserting any Auditable object.

The problem I am having comes when there is any sort of exceptional situation during the transaction that persists the Auditable object: the transaction is rolled back, but I still get an AuditTrail record inserted.

I tried to turn this:

StatelessSession session = event.getPersister().getFactory().openStatelessSession();

Into this:

Session session = event.getSession();

But when I attempt to use that session it results in a stack trace that ends in the message Session is closed.

The problem seems to be that the event fires in the middle of the transaction, before the exceptional situation that results in the rollback, and since the Event Listener has to use its own session, it doesn't also get rolled back.

Is there some way to make sure the action of the Event Listener is also rolled back? Have I just chosen an event that occurs too early in the transaction? Is there some event I should be catching that happens after the last point at which a rollback could happen, thus ensuring that the insertion of the AuditTrail will not trigger if a rollback occurs?

like image 436
Random Human Avatar asked Aug 27 '12 21:08

Random Human


2 Answers

Since nobody ever replied, I kept researching on my own, and my initial solution is as follows:

public class AuditListener implements PostInsertEventListener {
  private static final long serialVersionUID = -966368101369878522L;

  @Override
  public void onPostInsert(PostInsertEvent event) {
    if (event.getEntity() instanceof Auditable) {
      Session session = null;
      try {
        session = event.getPersister().getFactory().getCurrentSession();
        Auditable auditableEntity = (Auditable)event.getEntity();
        session.save(new AuditTrail(auditableEntity.getClass().getSimpleName(),
            auditableEntity.getId(), auditableEntity.getStatus(),
            auditableEntity.getLastModified()));
      } catch (HibernateException he) {
        System.out.println("Horrible error: " + he.getMessage());
        session.getTransaction().rollback();
      }
    }
  }
}

Note that I'm calling "getCurrentSession()" from the PostInsertEvent's SessionFactoryImplementor. I'm not really sure if this is a potentially dangerous strategy or not, and I'm also not sure if it makes sense to keep that rollback() call in there, but it seems to function and no one else ever supplied a better solution. So there you go.

like image 164
Random Human Avatar answered Sep 19 '22 07:09

Random Human


I had a very similar problem recently. The questions was asked years ago but I think this answer might help others.

The problem I am having comes when there is any sort of exceptional situation during the transaction that persists the Auditable object: the transaction is rolled back, but I still get an AuditTrail record inserted.

The real problem seems to me, is that onPostInsert shouldn't even get fired when the transaction is NOT committed.

It turned out to be a bug of PostInsertEventListener in hibernate (for 8 years! from 2006 to 2014). Ref: https://hibernate.atlassian.net/browse/HHH-1582

For backward compatibility, they fixed the issue by introducing a new PostCommitInsertEventListener. So we should use PostCommitInsertEventListener instead now (for hibernate >= v4.3.5) and onPostInsert should get fired only when transaction is successfully committed

like image 25
Faraway Avatar answered Sep 19 '22 07:09

Faraway