Guide to Hibernate Interceptors

Interceptors, as the name suggests, provide callbacks to certain events that occur inside Hibernate. It helps in implementing AOP style cross-cutting concerns and the extension of Hibernate functionality.

1. Creating an Interceptor

To create a new Interceptor in Hibernate, we need to implement the org.hibernate.Interceptor interface. This interface provides methods to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.

Before Hibernate 6.0, extending the EmptyInterceptor was a preferred way to override only the necessary methods because to implement Interceptor, we have to implement all 14 methods in the interface. This was obviously not suitable until we had a very strong reason to do it.

Since Hibernate 6.0, EmptyInterceptor has been deprecated. And the methods inside Interceptor interface have been made default methods, so we only need to override only the necessary method now.

public class AuditInterceptor implements Interceptor {

} 

2. Overriding Interceptor Methods

Interceptor interface provides the following important methods for intercepting specific events:

  • afterTransactionBegin(): Called when a Hibernate transaction is begun.
  • afterTransactionCompletion(): Called after a transaction is committed or rolled back.
  • beforeTransactionCompletion(): Called before a transaction is committed (but not before rollback).
  • onCollectionRecreate(): Called before a collection is (re)created.
  • onCollectionRemove(): Called before a collection is deleted.
  • onCollectionUpdate(): Called before a collection is updated.
  • onDelete(): Called before an object is deleted.
  • onFlushDirty(): Called when an object is detected to be dirty, during a flush.
  • onLoad(): Called just before an object is initialized.
  • onSave(): Called before an object is saved.
  • postFlush(): Called after a flush.
  • preFlush(): Called before a flush.

Lets override onFlushDirty() method from the Interceptor interface. This method is invoked when an entity updates are flushed into the database. This method can help in identifying the changes in the entity in each flush operation.

In our example, we are simply logging the audit info in the logs. We can also insert this audit entry in the database if required. Basically, we can do a lot of things in these overridden methods based on business needs.

@Slf4j
public class AuditInterceptor implements Interceptor {

  @Override
  public boolean onFlushDirty(Object entity,
                              Object id,
                              Object[] currentState,
                              Object[] previousState,
                              String[] propertyNames,
                              Type[] types) throws CallbackException {

    if (log.isDebugEnabled()) {
      log.debug("********************AUDIT INFO START*******************");
      log.debug("Entity Name    :: " + entity.getClass());
      log.debug("Previous state :: " + Arrays.deepToString(previousState));
      log.debug("Current  state :: " + Arrays.deepToString(currentState));
      log.debug("propertyNames  :: " + Arrays.deepToString(propertyNames));
      log.debug("********************AUDIT INFO END*******************");
    }

    return Interceptor.super.onFlushDirty(entity,
        id,
        currentState,
        previousState,
        propertyNames,
        types);
  }
}

3. Registering Interceptor

We can register the Interceptor with the persistent context in two ways.

3.1. With Session

If we need to use the interceptor in only a couple of places in the application, we can register it with the Session instances in those places.

try (Session session = sessionFactory.withOptions()
        .interceptor(new AuditInterceptor()).openSession()) {
      session.getTransaction().begin();

      //...
}

3.2. With SessionFactory

To enable the interceptor in all the sessions created in the application, we can add the interceptor in the SessionFactory itself.

try {
  StandardServiceRegistry standardRegistry
      = new StandardServiceRegistryBuilder()
      .configure("hibernate-test.cfg.xml")
      .build();

  Metadata metadata = new MetadataSources(standardRegistry)
      .addAnnotatedClass(EmployeeEntity.class)
      .getMetadataBuilder()
      .build();

  sessionFactory = metadata
      .getSessionFactoryBuilder()
      .applyInterceptor(new AuditInterceptor())
      .build();

} catch (Throwable ex) {
  throw new ExceptionInInitializerError(ex);
}

Make sure we do not store any kind of state information in the Interceptor because it will be shared by multiple threads. To make it even safer from accidental uses, we can make the session context to thread-local.

hibernate.current_session_context_class=org.hibernate.context.internal.ThreadLocalSessionContext

4. Demo

To demonstrate, let us create an entity and update its information.

@Test
public void testAuditLogging() {
  try (Session session = sessionFactory.withOptions()
      .interceptor(new AuditInterceptor()).openSession()) {
    session.getTransaction().begin();

    EmployeeEntity employee = new EmployeeEntity();
    employee.setFirstName("Lokesh");
    employee.setLastName("Gupta");
    employee.setEmail("howtodoinjava@gmail.com");

    //Save here
    session.persist(employee);
    session.flush();

    //Update here
    employee.setFirstName("Akash");

    session.getTransaction().commit();
  }
}

Notice the logs generated by Hibernate. Clearly, we are able to see the entity state before and after the flush.

2022-05-10_11:11:28.662 DEBUG  c.h.basics.entity.AuditInterceptor - ********************AUDIT INFO START*******************
2022-05-10_11:11:28.662 DEBUG  c.h.basics.entity.AuditInterceptor - Entity Name    :: class com.howtodoinjava.basics.entity.EmployeeEntity
2022-05-10_11:11:28.662 DEBUG  c.h.basics.entity.AuditInterceptor - Previous state :: [howtodoinjava@gmail.com, Lokesh, Gupta]
2022-05-10_11:11:28.662 DEBUG  c.h.basics.entity.AuditInterceptor - Current  state :: [howtodoinjava@gmail.com, Akash, Gupta]
2022-05-10_11:11:28.662 DEBUG  c.h.basics.entity.AuditInterceptor - propertyNames  :: [email, firstName, lastName]
2022-05-10_11:11:28.662 DEBUG  c.h.basics.entity.AuditInterceptor - ********************AUDIT INFO END*******************

5. Conclusion

In this tutorial, we learned to use the Hibernate Interceptor interface for getting callbacks on various persitence-related events. We learned to register the Interceptor with Session and SessionFactory interfaces.

Happy Learning !!

Sourcecode on Github

Was this post helpful?

Join 7000+ Awesome Developers

Get the latest updates from industry, awesome resources, blog updates and much more.

* We do not spam !!

Leave a Comment

HowToDoInJava

A blog about Java and related technologies, the best practices, algorithms, and interview questions.